Skip to content

fix(bin): prevent false watcher alarms during Claude handling - #3

Merged
V4f1k merged 1 commit into
mainfrom
fm/watcher-false-down-long-claude
Aug 15, 2026
Merged

fix(bin): prevent false watcher alarms during Claude handling#3
V4f1k merged 1 commit into
mainfrom
fm/watcher-false-down-long-claude

Conversation

@V4f1k

@V4f1k V4f1k commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Intent

Correct the demonstrated false WATCHER DOWN alarm in the Claude Stop-owned auto-arm model. The authoritative investigation found 854 clean lifecycle closes, disproving the original unexplained-exit premise; do not alter watcher lifecycle around that premise. bin/fm-guard.sh must not label an expected active Claude handling interval as WATCHER DOWN solely because the prior watcher beacon crosses the 300-second age before the next Stop rearm. Preserve the strict PID-and-beacon watcher predicate at turn end, durable wake-queue warnings, and the existing loud recovery path for a genuine auto-arm failure. Scope the tolerance specifically to the lock-owning Claude session and its durable pending handling generation with no failure evidence, so Cursor auto-arm, Pi and pi-signed extension hand-off, and persistent-watcher harness guarantees remain strict. Review and preserve behavior across all supported primary harness supervision models and the tmux, Herdr, Zellij, Orca, and cmux runtime backends. Add executable behavioral regression tests that run the real guard in the same live Claude handling process at the demonstrated 299-second and 301-second boundary, and prove completed handling and genuine failures still alarm; source-grep and prompt-string tests are not acceptable substitutes for behavior. Preserve every existing change. A separate secondmate child-worktree test failure was causally diagnosed: both clean HEAD and the implementation fail only when an unrelated inherited FM_ROOT_OVERRIDE redirects fixture scope, while removing that single condition makes the proven path pass; treat it as demonstrably pre-existing and unrelated, and validate the relevant suites under clean fixture environment. Push and open a PR through no-mistakes, but never merge it.

What Changed

  • Treat stale watcher beacons as healthy during a lock-owned Claude handling generation when no auto-arm failure evidence exists.
  • Keep turn-end checks and Cursor, Pi, pi-signed, and persistent-watcher supervision strict, while preserving queued-wake and genuine-failure alarms.
  • Add executable boundary, failure, harness, and runtime-backend regression coverage and document the Claude handling tolerance.

Risk Assessment

✅ Low: The change is narrowly scoped to lock-owned Claude pending-handling intervals, preserves strict failure and sibling supervision paths, and adds executable boundary regressions without source-content-only assertions.

Testing

Under a clean fixture environment, the real guard stayed healthy for the same live lock-owning Claude handling process at both 299s and 301s, while completed handling and genuine auto-arm failures still alarmed; queued-wake warnings, strict turn-end behavior, Cursor/Pi/persistent supervision, and tmux/Herdr/Zellij/Orca/cmux backend behavior also passed. The initial turn-end run inherited active-session routing variables and failed an unrelated fixture, then passed when those ambient variables were removed as required.

Evidence: Claude guard behavioral regression transcript

Source: Claude guard behavioral regression transcript

ok - fm-guard stale banner: first stale call prints the full actionable banner
ok - fm-guard stale banner: repeated same-episode calls print a concise reminder only
ok - fm-guard stale banner: Pi and pi-signed primaries route themselves to the extension model
ok - fm-guard stale banner: extension-owned hand-off with a live session is healthy
ok - fm-guard stale banner: extension-owned empty lock is genuinely unheld
ok - fm-guard stale banner: held unhealthy extension locks stay loud
ok - fm-guard stale banner: extension model without ownership evidence stays loud
ok - fm-guard stale banner: every extension-ownership signal is load-bearing
ok - fm-guard stale banner: extension model still alarms on a genuinely stale beacon
ok - fm-guard stale banner: queued-wake warning survives the extension hand-off tolerance
ok - fm-guard stale banner: persistent primaries ignore Pi extension evidence
ok - fm-guard stale banner: extension model stays silent for a live watcher
ok - fm-guard stale banner: auto-arm fresh beacon without a live watcher is healthy
ok - fm-guard stale banner: auto-arm stale beacon alarms with the true reason
ok - fm-guard stale banner: auto-arm stale episode stays one episode across calls
ok - fm-guard stale banner: live Claude handling stays healthy across the exact 299s/301s boundary
ok - fm-guard stale banner: completed handling and genuine Claude auto-arm failure stay loud
ok - fm-guard stale banner: Claude handling tolerance preserves the durable queued-wake warning
ok - fm-guard stale banner: Cursor, Pi, and persistent supervision guarantees stay strict
ok - fm-guard stale banner: all supported runtime backends preserve the Claude handling verdict
ok - fm-guard stale banner: persistent no-watcher banner names the true reason
ok - fm-guard stale banner: a no-watcher episode survives a beacon mtime change
ok - fm-guard stale banner: a fresh beacon without a live watcher remains unhealthy
ok - fm-guard stale banner: X-mode polling without a live watcher remains unhealthy
ok - fm-guard stale banner: healthy recovery rearms the next stale episode
ok - fm-guard stale banner: concurrent same-episode calls claim exactly one full banner
ok - fm-guard stale banner: deduplication is isolated per FM_HOME
ok - fm-guard stale banner: queued-wake warning remains independent
ok - fm-guard stale banner: read-only before writable does not consume full banner
ok - fm-guard stale banner: read-only during episode observes without mutating marker
ok - fm-guard stale banner: healthy read-only does not clear marker
ok - fm-guard stale banner: read-only never mutates stale-banner state files
Evidence: Strict turn-end guard transcript

Source: Strict turn-end guard transcript

ok - fm_supervision_unhealthy: false with no state/*.meta at all
ok - fm_supervision_unhealthy: true with in-flight task and no beacon ever
ok - fm_supervision_unhealthy: true with in-flight task and a beacon far outside the grace window
ok - fm_supervision_unhealthy: false with in-flight task and a fresh beacon
ok - fm_supervision_status: FM_SUP_QUEUE_PENDING tracks state/.wake-queue
ok - fm_supervision_needed: X-mode relay poll needs supervision
ok - fm_supervision_unhealthy: source-only home needs supervision
ok - fm-turnend-guard: silent no-op with nothing in flight
ok - fm-turnend-guard: blocks when a fresh beacon has no live watcher lock
ok - fm-turnend-guard: non-Claude path blocks a source-only home
ok - fm-turnend-guard: blocks on a dead watcher lock even when the beacon is fresh
ok - fm-turnend-guard: silent no-op with a live watcher lock and fresh beacon
ok - fm-turnend-guard: healthy non-Claude harness paths ignore Claude episode contention
ok - fm-turnend-guard: blocks on a live watcher lock with an ancient beacon
ok - fm-turnend-guard: blocks with the exact required reason in the primary when unhealthy
ok - fm-turnend-guard: blocks from active FM_HOME state, not only repo-root state
ok - fm-turnend-guard: X-mode repair reason sources the cadence config
ok - fm-turnend-guard: X-mode-only supervision remains guarded in default mode
ok - fm-turnend-guard: ignores stale repo-root state when FM_HOME is set
ok - fm-turnend-guard: uses FM_STATE_OVERRIDE ahead of FM_HOME/state
ok - fm-turnend-guard: stop_hook_active=true always allows the stop (never blocks twice in one turn)
ok - fm-turnend-guard: blocks a blind turn end in a secondmate's own home (.fm-secondmate-home no longer excludes it)
ok - fm-turnend-guard: idle-by-default - silent in a secondmate home with nothing in flight
ok - fm-turnend-guard: stop_hook_active=true allows the stop in a secondmate home (never blocks twice in one turn)
ok - fm-turnend-guard: secondmate deferred-death recovery - silent while watched, forces re-arm once the watcher exits
ok - fm-turnend-guard: inert in a secondmate's own child worktree (linked git worktree) even when unhealthy
ok - fm-turnend-guard: blocks a blind turn end in a treehouse-leased LINKED secondmate home (marker force-include)
ok - fm-turnend-guard: an invalid (empty) marker cannot spoof inclusion; linked worktree stays exempt
ok - fm-turnend-guard: a non-ASCII marker cannot spoof inclusion; linked worktree stays exempt
ok - fm-turnend-guard: inert in a crewmate/scout task worktree (linked git worktree) even when unhealthy
ok - fm-turnend-guard: fails open (never blocks) when jq is missing
ok - fm-turnend-guard: silent no-op on empty stdin
ok - fm-turnend-guard: runs well under the generous timing margin (0s)
ok - fm-turnend-guard-grok: forces one explicitly marked same-session resume when the shared predicate blocks
ok - fm-turnend-guard-grok: legacy environment loop guard prevents a nested resume loop
ok - fm-turnend-guard-grok: native false delegates blocking feedback with zero resume processes
ok - fm-turnend-guard-grok: native true remains bounded and starts no resume process
ok - fm-turnend-guard-grok: both spellings are typed and camelCase has deterministic precedence
ok - fm-turnend-guard-grok: malformed, invalidly typed, and missing-prerequisite payloads start neither path
ok - fm-turnend-guard-grok: missing jq and no-supervision-needed stops stay silent and bounded
ok - tracked .claude/settings.json entries: 5 inert under grok, the documented subagent exception still armed, all live under Claude
ok - .codex/hooks.json: Stop hook uses hook process root when payload cwd is outside
ok - .codex/hooks.json: Stop hook ignores nested git root guard scripts
ok - .opencode primary plugin: guard path is anchored to worktree, not directory
ok - .pi primary extension: no-tool and multi-tool runs each inject exactly one guard follow-up
ok - .pi primary extension: delivery failure resets the logical-run latch
ok - fm-turnend-guard --claude: re-blocks a loop-guarded stop while unhealthy and unclaimed (incident regression)
ok - fm-turnend-guard --claude: pending handling does not relax the strict turn-end predicate
ok - fm-turnend-guard --claude: X-mode-only homes re-block when auto-arm recovery is absent
ok - fm-turnend-guard --claude: a live arming epoch advances once and repeated observation is idempotent
ok - fm-turnend-guard --claude: repeated failed-to-arming races make bounded monotonic progress
ok - fm-turnend-guard --claude: terminal owner boundary excludes a concurrent start without deadlock
ok - fm-turnend-guard --claude: fresh rewake epoch prevents a duplicate continuation for the same event
ok - fm-turnend-guard --claude: fresh failed epochs preserve and advance monotonic fail-open progression
ok - fm-turnend-guard --claude: integrated fresh failures reach one bounded fail-open, stop continuation, and reset on recovery
ok - fm-turnend-guard --claude: reset contention preserves all episode state until retry
ok - fm-turnend-guard --claude: concurrent auto-arm and guard resets are idempotent and deadlock-free
ok - fm-turnend-guard --claude: stale rewake epoch does not allow a blind stop
ok - fm-turnend-guard --claude: budget exhaustion alone cannot permit a blind stop
ok - fm-turnend-guard --claude: verified fail-open is loud, bounded, attended, and non-repeating
ok - fm-turnend-guard --claude: fail-open requires both exhausted retries and consumed notice
ok - fm-turnend-guard --claude: away ownership excludes the Stop-autoarm fail-open
ok - fm-turnend-guard --claude: positive watcher recovery resets failure episode state
ok - fm-turnend-guard --claude: bounded claim wait avoids a token-consuming forced continuation
ok - fm-turnend-guard --claude: secondmate home re-blocks unclaimed and allows auto-arm-claimed stops

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • env -u FM_ROOT_OVERRIDE bash tests/fm-guard-stale-banner.test.sh
  • env -u FM_ROOT_OVERRIDE bash tests/fm-turnend-guard.test.sh (initial attempt exposed inherited FM_HOME/supervision routing contamination)
  • env -u FM_ROOT_OVERRIDE -u FM_HOME -u FM_STATE_OVERRIDE -u FM_CONFIG_OVERRIDE -u FM_DATA_OVERRIDE -u FM_PROJECTS_OVERRIDE -u FM_SUPERVISION_MODEL bash tests/fm-turnend-guard.test.sh
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@V4f1k
V4f1k merged commit 1a56e01 into main Aug 15, 2026
13 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.

1 participant