claude-session-watchdog¶
Standalone Windows Scheduled Task that detects dead Claude Code sessions during an overnight mission window and pages Andrew through the PavlokPager CLI.
Why this exists: on 2026-07-08 Claude was down ~6 hours (~1:50 AM–7:58 AM CDT)
during an overnight mission and nothing paged, because the paging standard lived
inside Claude. This watchdog runs entirely outside the Claude ecosystem — plain
PowerShell + Task Scheduler + the pavlok CLI — so a total Claude outage is exactly
the event it can still report.
How it works¶
Task Scheduler (daily 00:00, repeat 15 min x 8 h)
│
▼
src/watchdog.ps1 ──► Invoke-WatchdogCheck (src/Watchdog.psm1)
│
├─ 1. Inside 00:00–08:00 local window? no → exit
├─ 2. Mission marker armed + unexpired? no → clear incident, exit
├─ 3. Newest *.jsonl write under ~\.claude\projects\
├─ 4. Silence < 120 min? yes → healthy, clear incident
└─ 5. Dead → open/continue incident
├─ first detection → pavlok page --level urgent
├─ still dead ≥30 min after last page → re-page (max 3/incident)
└─ pavlok exit 3 (3-per-8h CLI budget) → stand by
Key design decisions:
- Transcript writes are the liveness signal, not process presence. A hung
claude.exeduring an API outage looks alive by process list — the 07-08 outage shape. Process count is included in the page reason for diagnosis but never suppresses a page. - Armed only when a mission marker exists. No marker (or expired marker) means no pages — quiet nights stay quiet. Corrupt marker fails armed, not disarmed.
- Acknowledgement = transcript activity resumes or the marker is deleted. Either clears the incident.
- Paging budget is enforced twice: max 3 pages per incident locally, and the pavlok CLI's own hard 3-per-8h budget (exit code 3) as backstop.
- This tool never restarts, kills, or reboots anything. Read-only except for its own state/log files and the pavlok call.
Install¶
# 1. Register the scheduled task (current user, runs while logged on)
pwsh -File install\install-task.ps1
# 2. Verify pavlok CLI is set up (one-time; see PavlokPager repo)
pavlok test
# 3. Daytime smoke test (bypasses the window check; pages only if a marker
# is armed AND sessions are actually silent 2 h+)
pwsh -File src\watchdog.ps1 -Force
Uninstall: pwsh -File install\uninstall-task.ps1
Arming / disarming¶
At overnight mission start (Claude session or human):
pwsh -File tools\Start-OvernightMission.ps1 -Mission "WW clear-the-board" -Hours 9
At mission end, or on waking to a page:
pwsh -File tools\Stop-OvernightMission.ps1
Marker file: ~\.claude\overnight-mission.marker.json —
{ mission, startedUtc, expiresUtc }. Markers self-expire (default 10 h, hard cap
16 h without expiresUtc) so a forgotten marker cannot page tomorrow night.
Configuration¶
Defaults live in Get-WatchdogDefaultConfig. Override any subset in
~\.claude-watchdog\config.json:
| Key | Default | Meaning |
|---|---|---|
projectsPath |
~\.claude\projects |
Transcript tree to watch |
markerPath |
~\.claude\overnight-mission.marker.json |
Arming marker |
inactivityThresholdMinutes |
120 |
Silence before "dead" |
repageMinutes |
30 |
Gap between re-pages while still dead |
maxPagesPerIncident |
3 |
Local page cap per incident |
windowStartHour / windowEndHour |
0 / 8 |
Local-time watch window (may cross midnight) |
pavlokLevel |
urgent |
Pavlok ladder (urgent = vibe 80 → beep 100 → zap 100 ×5, the deep-sleeper wake protocol) |
pavlokExe |
pavlok |
CLI executable |
markerMaxAgeHours |
16 |
Staleness cap for markers without expiresUtc |
State & logs¶
~\.claude-watchdog\state.json— current incident (start, pages sent, exit codes)~\.claude-watchdog\watchdog.log.jsonl— one JSON line per pass, every pass~\.pavlok\pages.jsonl— the pavlok CLI's own stimulus log
Exit codes of watchdog.ps1: 0 = ran fine (any non-error outcome), 1 = pavlok
CLI error (send failed / CLI missing).
Tests¶
Invoke-Pester -Path tests # 37 unit tests, Pester 5+, fully mocked/sandboxed
Pure PowerShell tool → per tools-org testing policy this ships with mocked unit coverage of every decision branch (window, marker, activity, incident state machine, budget, pavlok exit codes) in place of mutation testing.
Ops notes¶
- Task runs as the logged-on user (
Interactive),StartWhenAvailablecatches missed ticks, 10-min execution cap, new instances skipped while one runs. - The
pavlok page --level urgentladder blocks ~2 min — well inside the cap. - If
pavlokis missing the pass logspavlok-errorand exits 1; it never throws the task into a retry storm.