Skip to content

Add vigil watch headless hang watchdog - #1

Merged
hematteo merged 3 commits into
mainfrom
claude/compassionate-dirac-4guaxc
Sep 29, 2026
Merged

hematteo merged 3 commits into
mainfrom
claude/compassionate-dirac-4guaxc

Conversation

@hematteo

Copy link
Copy Markdown
Owner

What

Adds vigil watch, a headless mode for always-on machines that alerts when a training run on a rented GPU stops producing output. It combines log silence with nvidia-smi sampling over SSH to tell these cases apart:

Output GPU Alert
Stopped Idle, memory held hang
Stopped Memory released exited: finished or crashed, instance still billing
Stopped for a long time Busy busy_silent: long eval, or an NCCL deadlock
Stopped SSH / nvidia-smi failing unreachable
Resumes after an alert recovered
Instance disappears gone

Every alert includes what the silence has cost. Unresolved alerts repeat with the updated cost. Alerts go to ntfy (new format, for phone push), Slack, Discord or raw webhooks, and --test-alert checks the setup.

Also:

  • Progress bars: logs are streamed in chunks and split on \r as well as \n. tqdm redraws count as activity (one per 10s is logged), so a bar-only run isn't mistaken for a silent one. The TUI also shows live progress now.
  • RunPod key: sent as an Authorization: Bearer header instead of ?api_key=, which httpx echoed into error messages. Keys are also redacted from displayed errors.
  • Opt-out: watch_skip_labels and a per-instance watch: false setting.
  • Start time: read from Vast.ai (start_date) and RunPod (uptimeInSeconds) and used for the total cost in alerts.
  • Titles: webhook titles say "vigil alert" instead of "Vast.ai Alert".
  • --provider: defaults to the config file's provider.
  • Lockfile: uv.lock synced to 0.2.0.

Why

Crash and finish alerts already exist elsewhere (W&B, knockknock). Runs that hang without exiting don't trigger them, and on a rented GPU they bill until someone looks. The TUI's stall alerts only fire while it's open on a laptop, and GPU usage separates real hangs from long evals.

Testing

  • uv run pytest passes (262 tests, up from 221); flake8 clean
  • Manually verified in TUI
  • End-to-end against a local asyncssh server with a fake nvidia-smi:
    • Hang detected about 60–70s after output stopped (stall set to 1 min), followed by recovery
    • No false recovery from tail -n 100 replay on reconnect
    • No false alarm during 3 min of \r-only progress output
  • Not yet run against real Vast.ai / RunPod instances. Still to confirm there: RunPod Bearer auth and the uptimeInSeconds field, Vast.ai start_date, and how Vast.ai's SSH proxy handles one GPU check connection per minute.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TSy8nEHnUh6SLq6y8LB1nz


Generated by Claude Code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSy8nEHnUh6SLq6y8LB1nz
Runs without the TUI on an always-on machine and alerts when a training
run stops producing output. GPU utilization and memory sampled over SSH
distinguish a hung process (idle, memory held), an exited one (memory
released, instance still billing), and a silent-but-busy one (long eval
or NCCL deadlock). Alerts include what the silence has cost, repeat as
reminders, and announce recovery and preempted/destroyed instances.
Log replay on reconnect (`tail -n 100`) is ignored so it can't fake a
recovery.

Also:
- ntfy webhook format for phone push without an account
- instance start time from Vast.ai (start_date) and RunPod (uptime)
- provider-neutral webhook titles (were "Vast.ai Alert" for RunPod)
- --provider defaults to the config file's provider

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSy8nEHnUh6SLq6y8LB1nz
- Stream logs in chunks and split on \r as well as \n. tqdm-style bars
  that only redraw in place were invisible until the next newline, so
  `vigil watch` could report a healthy run as silent. Every redraw now
  counts as activity; at most one per 10s is logged and parsed.
- Send the RunPod API key as an Authorization: Bearer header instead of
  ?api_key=, which httpx echoed into error messages shown in the TUI
  and written to watch logs. Redact API keys from displayed errors too.
- Add watch_skip_labels and per-instance `watch: false` so dev boxes
  and servers without training logs don't page anyone. Instances that
  become excluded stop being watched without a false "gone" alert.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSy8nEHnUh6SLq6y8LB1nz
Copilot AI balanced review requested due to automatic review settings September 29, 2026 19:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@hematteo
hematteo merged commit cf73c8f into main Sep 29, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants