fix(daemon): stop away-mode busy-guard false positives and refuse silent wedge-alarm entry - #2
Merged
Merged
Conversation
Two real away-mode sessions delivered zero escalations for 2+ hours each
because pane_is_busy() misread the captain's idle pane as busy on every
15s cycle. Live measurement in an isolated Herdr lab disproved the
suspected cause (the daemon's own tracked background job wedging its
hosting pane): a real fm-supervise-daemon.sh, launched exactly as
production does and targeting its own hosting pane, delivered a seeded
escalation successfully with a background shell alive the whole time.
The actual cause: pane_is_busy's rendered-tail fallback has no positional
anchor. Claude's busy-shape signature ("…" + a parenthesized elapsed
duration) is meant to catch a live spinner footer, but it matches equally
well against ordinary SETTLED reply text sitting in scrollback (e.g. "...
holding steady… (2h into the soak test)"). With no new output landing
while escalations keep failing, nothing scrolls the line out of view, so
the false match persists for the rest of the session - reproduced live
and confirmed as the trigger.
Fix: pane_is_busy now treats an affirmatively empty composer as positive
proof the turn has ended and short-circuits the rendered scan before it
runs. A genuinely busy pane never reads an empty composer, so this can't
mask a real busy turn. Both the terminal-backed and native-hosted launch
paths share this same check, so neither needed separate handling.
Separately, the wedge alarm's own escape hatch was inert on this Linux
box: no osascript, no org.freedesktop.Notifications D-Bus service, and
even herdr's own notification channel reported {"reason":"disabled"}
rather than posting anything. Extracted the channel-resolution logic into
bin/fm-wedge-alarm-lib.sh (shared by the daemon and the launcher) and
added an entry-time refusal: fm-afk-launch.sh now refuses a fresh away-mode
entry when no channel can plausibly reach the captain, unless the captain
has explicitly configured one (including an explicit "off" acknowledging
marker-only).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019924TgGwpoBc2rKMbFgi67
…es, add preflight tests
…dge-alarm-lib.sh in toolbelt
…ge-alarm preflight test"}
wedge_alarm_reliable_channel_configured matched command:* as a reliable directive even with an empty payload, so a config/wedge-alarm containing only "command:" passed away-mode entry as covered while wedge_alarm_via_command's own non-empty-cmd guard silently no-ops it at alarm time - the exact reassurance-that-doesn't-hold gap this predicate exists to catch. Require at least one character after the colon (command:?*) so an empty payload falls into the same immediate rejection as any other unrecognized directive. osascript and herdr are left unchanged: a missing binary is a per-host environmental fact, the same class of runtime risk this predicate already leaves to wedge_alarm_notify's best-effort logged dispatch for a broken command:<cmd> payload, not a config-only defect like an empty command:. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LnXRSBGuVx1oZEnPdKxm8F
…-lib.sh: `wedge_alarm_reliable_channel_configured` used the case pattern `command:?*`, which requires only one character after the colon — so `command: ` (or any all-whitespace payload) matched and was accepted as a "reliable" channel at away-mode entry. At runtime, `wedge_alarm_via_command`'s `[ -n "$cmd" ]` guard treats whitespace as non-empty too, so `sh -c ' '` would run, do nothing, and exit 0 — silently no-op'ing the alarm with no signal to the captain, reproducing the exact fail-silent gap this PR's user intent targets. Fix: changed the pattern to `command:*[![:space:]]*`, requiring at least one non-whitespace character after the colon, so both a bare `command:` and a whitespace-only `command: ` are rejected at entry (falling into the existing "no active wedge-alarm channel is configured" refusal), while `command:<real cmd>` still passes. Updated the adjacent comment to document the whitespace case. Added a regression test (`unit_wedge_alarm_preflight_refuses_whitespace_command_directive` in tests/fm-afk-launch.test.sh) that pins FM_WEDGE_ALARM_CHANNEL='command: ' being rejected; verified it fails on the pre-fix code and passes after the fix. Confirmed the CI/CD runs for this PR are conclusion=action_required (workflow-approval gating, not a real test failure), so the only concrete code defect to address was the Greptile finding. Full fm-afk-launch.test.sh and fm-daemon.test.sh suites pass (all wedge-alarm preflight + dispatch tests green), and shellcheck is clean on both changed files
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Lands the away-mode fix into this fork so it takes effect locally without waiting on upstream.
Same commits as the upstream contribution at kunchenguid#3476, which remains open and unaffected.
What it fixes:
Evidence: cause established by live measurement, not inference - the suspected daemon-hosting deadlock was disproven end to end, and the real false match was reproduced deliberately. Regression coverage added for both halves. Pipeline review, tests, docs and lint returned zero findings; the PR's automated reviewer passes at 5/5 after catching and confirming the fix of an earlier defect.
Not run: the parent project's own CI matrix, which is held at action_required pending maintainer approval of fork workflows. Reviewed for platform-sensitive constructs before landing; none introduced.
Merges cleanly into this fork's main - verified with a test merge, no conflicts.
Authorized by the captain 2026-09-01.