fix(bin): prevent mid-session session-start reruns - #94
Merged
Conversation
…first run The watcher-continuity PreToolUse gate allowed bin/fm-session-start.sh unconditionally, so a mid-session re-run while tasks were in flight with no live watcher could re-acquire the home lock and run bootstrap's mutating sweeps - the deferred follow-up documented in docs/watcher-continuity.md. Generalize the shared ancestry walk into fm_session_lock_relation() (free / ancestry / foreign) in bin/fm-primary-scope-lib.sh, pass the relation into bin/fm-continuity-command-policy.mjs, and deny a session-start attempt whenever a live session holds the home lock: an ancestry holder means session start already ran here, a foreign holder means another session owns the home. A lock-free home - including a dead holder after a crash - keeps session start a recovery command, and the wake-drain, watcher-arm, literal teardown, and exact sentinel-enable allowances are unchanged. The deny guidance also stops naming the once-per-session entry point for a foreign-held lock.
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
Close a real gap in the continuity gate of the firstmate public repo. bin/fm-continuity-pretool-check.sh is a PreToolUse guard that, while tasks are in flight and no live watcher holds the home lock, refuses most bin/fm-*.sh fleet commands but allows a short safe list, including the once-per-session bin/fm-session-start.sh. The known gap (documented as a deferred follow-up in docs/watcher-continuity.md 'Known limitation'): mid-session reruns of fm-session-start.sh were not distinguished from the legitimate first run, so the once-per-session allowance could be exploited or misfire mid-session. The decided technique, fixed in advance: reuse the lock-in-ancestry approach already implemented for bin/fm-sessionstart-nudge.sh so a session-start invocation is recognized as the legitimate first run only when no live session holds the home session lock, and a rerun after the session already completed session-start (live lock holder in this process's ancestry) - or an attempt while another live session holds the lock (ancestry mismatch / foreign holder) - is treated like any other gated fleet command and refused with the gate's canonical outage summary. Implementation decisions: the shared ancestry walk in bin/fm-primary-scope-lib.sh was generalized into a three-way fm_session_lock_relation() (free/ancestry/foreign) with fm_session_lock_in_ancestry() delegating to it so the nudge behavior is unchanged and there is one owner; the shell gate passes the relation to bin/fm-continuity-command-policy.mjs via a new optional --session-lock flag (absent or unrecognized value is treated as free, preserving old-caller behavior, and the classifier stays a pure function of its inputs); a live-held lock denies fm-session-start.sh with a distinct midsession-session-start reason code, rendered with the canonical outage summary and holder-specific wording; the generic deny guidance now names the once-per-session session-start entry point only when the lock relation is free. Deliberately unchanged: the gate's existing allowances (wake drain, watcher arm recovery, ordinary literal fail-closed teardown, exact sentinel enable) are untouched and are now explicitly asserted to remain allowed while a lock is held; a dead/stale lock holder (crash recovery) still allows the genuine first session-start. Acceptance criteria: first session-start in a lock-holding session allowed exactly as before; a second/mid-session session-start attempt while tasks are in flight with no live watcher refused with the canonical outage summary; no regression for other allowed commands; shellcheck-clean (bin/fm-lint.sh passes); colocated executable tests in tests/fm-continuity-pretool-check.test.sh covering first-run allowed, rerun refused (direct and nested), ancestry-mismatch refused, dead-holder first run allowed, and each existing allowance still permitted. Docs: the watcher-continuity.md 'Known limitation' section was rewritten as 'Session-start first-run scoping' since the gap is closed, and arm-pretool-check.md's claim that the ancestry check scopes guidance text only was updated to describe the classification contract. This changes firstmate's shared tracked material (bin/), so firstmate-coding-guidelines were followed: one-sentence-per-line markdown, plain dashes, no agent co-author, one-owner rule for the lock-relation decision.
What Changed
fm-session-start.shallowance to genuine first runs, denying same-session reruns and attempts under a foreign live session lock with holder-specific outage guidance.free,ancestry, orforeign) while preserving stale-lock recovery and existing recovery-command allowances.Risk Assessment
✅ Low: The follow-up centralizes harness-aware holder liveness, preserves the lock CLI contract, fixes stale PID reuse, and satisfies the stated continuity-gate acceptance criteria without introducing a material risk.
Testing
The full continuity-gate behavior suite passed, including direct and nested reruns, foreign and stale locks, and preserved recovery allowances; an isolated end-to-end transcript additionally demonstrates a genuine first run being silently allowed and a lock-held mid-session rerun returning the expected user-visible denial. No linters or static-analysis tools were run, per the testing rules.
Evidence: Continuity gate end-user transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 1 issue found → auto-fixed ✅
bin/fm-primary-scope-lib.sh:57- Intent requires that “a dead/stale lock holder (crash recovery) still allows the genuine first session-start,” but this newkill -0check treats any reused live PID as a foreign session. That contradictsfm-lock.sh, which considers a holder stale when the PID is not a harness and would permit acquisition. A stale lock whose PID was reused by an unrelated process will therefore block recovery indefinitely; the new foreign-holder test even usessleep, encoding this mismatch. Align relation liveness with the authoritativeholder_alivesemantics and test both a real foreign harness and a reused non-harness PID.🔧 Fix: Unify harness-aware session lock liveness
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
Inspectedgit diff 3e81ed5f5fa3cce53ce3876198ff1458900802cc..8e72073affd98d0fc6c674a7b88d89b0b8a0c933and the colocated behavior tests.bash tests/fm-continuity-pretool-check.test.shManually invokedbin/fm-continuity-pretool-check.sh --command 'bin/fm-session-start.sh'in an isolated lock-free fixture and confirmed exit 0 with no output.Repeated the guard invocation beneath a harness-like Node lock holder and confirmed exit 2 with a PreToolUsedeny, canonical outage summary, and ancestry-specific mid-session wording.Verifiedgit status --shortremained clean after testing.✅ **Document** - passed
✅ No issues found.
🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix: Silence deliberate ShellCheck single-quote warning
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.