fix(bin): require launch ownership and safer away-mode return checks - #2863
Open
pramendra wants to merge 7 commits into
Open
fix(bin): require launch ownership and safer away-mode return checks#2863pramendra wants to merge 7 commits into
pramendra wants to merge 7 commits into
Conversation
added 7 commits
August 23, 2026 22:13
An away-mode session read as "the daemon exits about 38 seconds after launch". It did not exit on its own: `bin/fm-afk-return.sh check` shares `begin`'s stop-and-drain body, so running it with away mode active and no catch-up gate open performed the entire return - SIGTERM to the daemon, `state/.afk` cleared - under a subcommand name that reads like a status probe. The daemon logged its ordinary signal-trapped shutdown and its host job reported exit 0, which is why the teardown looked spontaneous. Because the Stop-owned auto-arm stands down while away mode claims ownership, the result was supervision genuinely off. `check` must stay able to complete an interrupted `begin` - the gate is published before any lifecycle mutation - so it keeps the shared body and instead refuses the one case that cannot be a continuation: no gate open while away-mode lifecycle state is still live. The usage block now marks both `begin` and `check` as mutating and names `guard` as the only read-only command. The same session launched the daemon as `exec bin/fm-afk-start.sh`, which is neither documented shape. Measured against a private tmux socket, both `exec`-wrapped and documented launches stayed up well past 60 seconds, so the launch shape was not the cause - but a bare or `exec`-wrapped entry opens an away session with no launcher lifecycle record for stop or return to reconcile. The entry now refuses unless the caller declares which shape it is: `FM_AFK_STATE_PREPARED=1` for the harness-native background job, or `FM_AFK_LAUNCH_OWNED=1`, which `bin/fm-afk-launch.sh` sets in the command it runs in its own non-visible terminal. Both refusals have colocated regressions, and the measurements are recorded in docs/verification/supervision.md.
…turn refusal assertions
…refuse The captain ruled option D on the guard-asymmetry finding: a leaked away-mode daemon terminal record with no active away mode should surface loudly to the captain, not block ordinary work behind a refusal. return_guard already refused on live away mode or a pending catch-up gate, but said nothing when neither was true and state/.afk-daemon-terminal still existed - the exact leaked-terminal shape. It now reports that case on stderr and still returns 0.
Confidence Score: 5/5The PR appears safe to merge, with the supported launch and return lifecycle paths remaining internally consistent and covered by behavioral tests. The ownership markers reach both supported terminal launch paths, native startup remains gated by launcher-prepared state, and Reviews (1): Last reviewed commit: "fix(bin): widen return_guard to report a..." | Re-trigger Greptile |
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
The away-mode daemon exits about 38 seconds after launch instead of staying up. Verify the leading hypothesis (launched as 'exec bin/fm-afk-start.sh' inside a harness-tracked background job, and a watcher already running that the daemon inherited but did not own) before fixing it - an 8-second liveness check passing tells nothing; verify the daemon is alive after at least 60 seconds under whichever launch shape is tested, with a log excerpt as evidence, and if it still exits cleanly with no signal from anyone, report that as a real daemon defect rather than retrying variations. Prevent the cascade where a dead daemon with state/.afk still present leaves supervision genuinely off (the Stop-owned auto-arm stands down while away mode claims ownership) and fm-afk-launch.sh then refuses to re-enter away mode until the return catch-up gate clears. Separately, bin/fm-afk-return.sh check is not read-only: running it cleared away mode and tore down the daemon terminal under a name that reads like a status probe - decide with evidence whether that is check's intended contract (and if so its name/help must say so) or a second defect, and fix accordingly. Acceptance: the daemon must survive past 60 seconds under the documented launch shape with log evidence; whatever the root cause, the fix must make the WRONG launch shape fail loudly or be impossible (a guard, a refusal, or a corrected entry point) rather than relying on an operator remembering - a documentation-only change is not acceptable; the fm-afk-return.sh check naming-versus-behavior question must be resolved one way or the other; add colocated tests where the behavior is testable; keep shellcheck clean; keep this repo's own gate green. Captain ruling on the investigation's guard-asymmetry findings (2026-08-23): widen return_guard to detect and report a leaked daemon terminal loudly, without refusing (do not make it a hard refusal); confirm and apply the wording correction on the launch-guard rationale comment/doc pointer while keeping the guard itself as-is.
What Changed
bin/fm-afk-start.shnow refuses (exit 2) unless the caller declares a launcher-owned shape viaFM_AFK_STATE_PREPARED=1orFM_AFK_LAUNCH_OWNED=1; a bare orexec-wrapped invocation previously started an away session with no terminal/lifecycle record forfm-afk-launch.sh stoporfm-afk-return.shto reconcile.bin/fm-afk-launch.shnow setsFM_AFK_LAUNCH_OWNED=1in the command it launches in the herdr/tmux terminal it creates.bin/fm-afk-return.sh checknow refuses to start the stop-and-drain body when away-mode state is still live and no catch-up gate is open, instead of silently clearing away mode and tearing down the daemon under a name that reads as a read-only probe.return_guardnow also detects and loudly reports (without refusing) a leaked daemon-terminal record left behind after away mode has already ended, pointing atfm-afk-launch.sh stopto reconcile it.fm-afk-launch.shanddocs/architecture.md/docs/scripts.md/.agents/skills/afk/SKILL.md, added adocs/verification/supervision.mdrecord with log evidence reproducing the real defect (thecheckmisfire, not a launch-shape issue) and confirming the daemon survives past 60s under both launch shapes, and added colocated regression tests intests/fm-afk-launch.test.sh,tests/fm-afk-return.test.sh, andtests/fm-daemon.test.sh.Risk Assessment
✅ Low: The branch root-causes the daemon-exit defect (fm-afk-return.sh check tearing down a live daemon under a status-probe-sounding name), makes the wrong launch shape fail loudly via a new declared-ownership guard in fm-afk-start.sh, resolves the check/guard naming-vs-behavior question by making guard the sole read-only command while documenting check's mutation, and implements the captain's 2026-08-23 ruling to widen return_guard into a non-blocking report for a leaked daemon terminal — all changes are well-bounded, internally consistent (I traced check_may_proceed and return_guard against the .afk/.afk-daemon-terminal state combinations and found no reachable wrong-result path), covered by colocated tests that execute the real scripts and assert observable output/state/exit codes (not source-text matching), backed by dated 60s+ survival log evidence in docs/verification/supervision.md, and shellcheck-clean.
Testing
Ran the three targeted behavioral test suites covering every changed script (fm-afk-return.test.sh, fm-afk-launch.test.sh, fm-daemon.test.sh) — all passed, including the new tests that directly reproduce and verify the fixes: check-refuses-to-start-the-return, guard-reports-leaked-terminal-without-refusing, launch-ownership-declaration for both tmux and herdr backends, and afk-start-refuses-an-unowned-direct-launch. The pinned-ShellCheck project lint gate ran clean on every modified file. The pre-existing docs/verification/supervision.md evidence record (part of this change set) independently satisfies the ≥60s liveness requirement with real log/transcript excerpts, and I verified the corrected doc-pointer heading actually exists in docs/herdr-backend.md. No test failures, no environment issues, and no leftover artifacts in the working tree.
Evidence: Away-mode daemon lifetime verification (60s+ liveness, log excerpts)
Source: Away-mode daemon lifetime verification (60s+ liveness, log excerpts)
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.
bin/fm-test-run.sh tests/fm-afk-return.test.sh tests/fm-afk-launch.test.shbin/fm-test-run.sh tests/fm-daemon.test.shbin/fm-lint.sh bin/fm-afk-start.sh bin/fm-afk-launch.sh bin/fm-afk-return.sh tests/fm-afk-launch.test.sh tests/fm-afk-return.test.sh tests/fm-daemon.test.shgrep -n '^#' docs/herdr-backend.md (confirmed 'Away-mode supervisor support' heading matches the corrected doc pointer)✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.