fix(daemon): stop away-mode daemon wedging on its own busy pane - #1
Merged
Merged
Conversation
2 tasks done
A live tracked background shell (the /afk skill's no-separate-terminal exception) pins Herdr's native agent_status to 'working' for as long as the shell is alive, even once a turn has ended and the pane sits idle. pane_is_busy trusted that native 'busy' verdict alone and short-circuited before ever checking the rendered footer, so the away-mode daemon permanently read its own supervisor pane as busy and could never deliver an escalation for its entire lifetime (data/fm-afk-inject-wedge/report.md). - pane_is_busy now requires the harness-scoped rendered busy footer to corroborate a native busy verdict, falling back to native only when the pane cannot be captured at all. - The max-defer escape now actually escapes: inject_msg/escalate_flush gained a forced mode that drops the busy guard (keeping the composer-empty guard) so unbounded silent non-delivery becomes a bounded worst case of FM_MAX_DEFER_SECS. The wedge alarm now carries the buffered item count and first line instead of only an age and path. - fm_afk_launch_stop's SIGTERM wait now scales with FM_POLL plus a margin instead of a fixed 10s, since the daemon's cleanup trap can legitimately take up to FM_POLL seconds to reap its watcher child.
…tion and forced max-defer escape
Greptile review on PR kunchenguid#3428 caught a real bug in the prior commit: the stop-wait iteration count used bash arithmetic expansion directly on FM_POLL, which errors on a non-integer value. Tests already exercise fractional FM_POLL (0.1, 0.02), so that arithmetic error would silently skip the post-SIGTERM wait and falsely report a still-exiting daemon as hung. Compute the iteration count with awk instead, rounding up.
cm-maple7
force-pushed
the
fm/fm-afk-daemon-delivery-fix-v2
branch
from
September 1, 2026 06:18
64f8375 to
f878b1a
Compare
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.
Intent
Fix the away-mode supervision daemon's injection wedge (data/fm-afk-inject-wedge/report.md, scout-investigated, root cause proven live). The daemon runs inside the captain's own supervisor pane by design (the /afk skill's no-separate-terminal exception for harnesses with a native in-pane background tool). On Herdr, a live tracked background shell pins the pane's native agent_status to 'working' for as long as the shell is alive, even after the turn has ended and the pane sits idle. pane_is_busy trusted that native busy verdict alone and short-circuited before ever checking the rendered footer, so the daemon read its own supervisor pane as permanently busy and could never deliver an away-mode escalation for its entire lifetime - proven against 7+ hours of production logs (3717 busy deferrals, 0 successful max-defer recoveries in the whole log). Implemented the report's three recommended, priority-ordered fixes: (1) root cause - pane_is_busy now requires the harness-scoped rendered busy footer (the same fm_busy_lines_match signature already used elsewhere to confirm a queued-while-busy submit) to corroborate a native busy verdict before treating the pane as unavailable; a native idle/unknown verdict was never trusted alone either, so this only changes the busy case, and falls back to trusting native only when the pane cannot be captured at all. (2) backstop - the max-defer escape must actually escape rather than retrying the identical blocked call forever: inject_msg and escalate_flush gained a forced mode (force=1) that drops only the busy guard while keeping the composer-empty guard, bounding worst-case silent non-delivery to FM_MAX_DEFER_SECS; also improved the wedge alarm's active-alert banner to carry the buffered item count and first line instead of only an age and a marker path. (3) independent SIGTERM budget fix - fm_afk_launch_stop's wait for the daemon to exit after SIGTERM was a fixed 10s, but the daemon's cleanup trap can legitimately need up to FM_POLL seconds (15s default) plus flush time to reap its watcher child; the wait now scales as (FM_POLL + 10s margin), computed with awk (not bash arithmetic) so it survives the fractional FM_POLL values (0.1, 0.02) several existing tests already use - bash arithmetic expansion on a non-integer would otherwise error and silently skip the wait, a real bug an automated PR review caught on an earlier attempt at this same fix and which is now fixed and covered by its own regression test. Explicitly out of scope / deferred to the captain as a design decision, not implemented here: the report's 'also worth doing' suggestion to reconsider hosting the away-mode daemon outside the supervisor pane entirely - a bigger design tradeoff the fixes above already resolve the concrete bug for. Added new regression tests for all fixes in tests/fm-daemon.test.sh and tests/fm-afk-launch.test.sh, and updated one existing test that pinned the old (buggy) short-circuit behavior. Two pre-existing test failures were found during verification (tests/fm-wake-queue.test.sh, tests/fm-watch-triage.test.sh) and confirmed unrelated: neither test file references anything touched by this change, both failures reproduce identically with these changes fully reverted, and they are already filed as their own separate task. This branch (fm-afk-daemon-delivery-fix-v2) is a fresh push of the same validated 3 commits under a new name, after firstmate traced an earlier attempt's wrong-repo PR to a stale no-mistakes internal gate binding on the old branch name.
What Changed
pane_is_busyinbin/fm-supervise-daemon.shno longer trusts a nativebusyverdict alone; it now requires the rendered harness-scoped busy footer (fm_busy_lines_match) to corroborate before treating the pane as unavailable, falling back to the native verdict only when the pane can't be captured at all. This fixes the root cause: a live tracked background shell (the daemon's own in-pane launch) pins Herdr's nativeagent_statustoworkingindefinitely, even once the pane is idle.inject_msgandescalate_flushgain aforceparameter that drops only the busy guard (the composer-empty guard still applies unconditionally); the max-defer escape path inhousekeepingnow calls this forced mode instead of retrying the identical blocked call, bounding worst-case silent non-delivery toFM_MAX_DEFER_SECS.inject_wedge_alarm's active-alert banner now includes the buffered item count and first line instead of just an age and marker path.fm_afk_launch_stopinbin/fm-afk-launch.shreplaces its fixed 10s post-SIGTERM wait with a budget computed viaawkas(FM_POLL + 10s margin), since the daemon's cleanup trap can legitimately take up toFM_POLLseconds to reap its watcher child; usingawkinstead of bash arithmetic avoids an error on the fractionalFM_POLLvalues used by tests..agents/skills/afk/SKILL.mdto describe the corroborated busy-guard and forced max-defer escape behavior, and adds regression tests intests/fm-daemon.test.shandtests/fm-afk-launch.test.shcovering all three fixes (including one existing test updated to drop its assertion on the old native-only short-circuit).Risk Assessment
✅ Low: All three fixes are narrowly scoped, root-cause-oriented, and reuse already-proven mechanisms (fm_busy_lines_match for corroboration, herdr's existing queued-while-busy submit confirmation for the forced escape); I traced the pane_is_busy corroboration logic, the forced max-defer escape's interaction with the composer guard, and the awk-based SIGTERM wait budget against concrete inputs (native-busy/idle-rendered, capture failure, fractional FM_POLL) and found no reachable wrong-result path, and the new/updated tests exercise real function behavior rather than source-text matching.
Testing
Ran the two targeted regression suites (fm-daemon.test.sh, fm-afk-launch.test.sh) on the target commit and both pass in full (109/109 and 54/54); cross-checked against a temporary base-commit worktree to confirm the two most load-bearing new tests (forced max-defer bypassing the busy guard, and the SIGTERM wait scaling with FM_POLL) genuinely fail on the pre-fix code and pass on the post-fix code, which is strong behavioral evidence for all three fixes described in the intent (pane_is_busy corroboration, forced max-defer escape, and the awk-based fractional-FM_POLL-safe SIGTERM wait budget). This is a headless bash-daemon fix with no UI surface, so no screenshot/video evidence applies; the CLI/test-transcript evidence above is the closest product-level artifact available. Did not re-run the two pre-existing unrelated test failures (fm-wake-queue, fm-watch-triage) mentioned in the intent since they are explicitly out of scope for this change and already filed separately; a background check I started for them was stopped early since it was unnecessary scope creep beyond the smallest relevant tests. Worktree is clean with no leftover transient artifacts.
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.
bash tests/fm-daemon.test.shon target commit 64f8375: 109/109 pass, exit 0 (covers Fix 1 pane_is_busy corroboration and Fix 2 forced max-defer escape)bash tests/fm-afk-launch.test.shon target commit 64f8375: 54/54 pass, exit 0 (covers Fix 3 SIGTERM wait budget scaling and fractional-FM_POLL survival)Created a temporary detached worktree at base commit 61ccb10, copied over the new test files, and re-ran both suites:test_max_defer_forced_escape_bypasses_busy_guardfails on base (buffer not cleared behind a permanently-busy pane), andunit_stop_wait_exceeds_configured_pollfails on base (fixed 40-iteration budget did not scale with FM_POLL=30, got 40) - confirming these are genuine regression tests, not tautologiesRemoved the temporary base-commit worktree after verification✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.