feat: preserve Firstmate continuity across Claude context compaction - #20
Merged
Conversation
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
Make Firstmate graceful around Claude Code context autocompaction. Use the verified PreCompact hook to deterministically capture a fresh local durable resume anchor before both automatic and manual compaction, then use a compact-sourced SessionStart recovery path to inject that anchor plus normal session-start reconciliation so a long autonomous primary resumes without lost durable fleet state. Keep the hook Firstmate-scoped and additive to Stop and PreToolUse, reuse stow only for periodic judgment-based conversation knowledge, empirically verify Claude Code event payloads and ordering plus manual and automatic behavior in isolated homes, add shellcheck-clean colocated tests and documentation, and hold the PR for captain merge. Do not implement compaction triggering or unrelated away-mode, dialog-detection, or permission work.
What Changed
jq.stowusage.Risk Assessment
✅ Low: Captain, the change is well-bounded, the prior fail-open paths are addressed, and the source now conforms to the stated autocompaction recovery intent.
Testing
The supplied full baseline had already passed; focused automation and isolated manual/automatic capture-to-recovery replays also passed on Claude Code 2.1.217, produced reviewer-visible hook transcripts, confirmed Firstmate-only scoping and captain-held PR preservation, and left the source worktree clean. No screenshot was applicable because the changed user surface is CLI hook output rather than a graphical UI.
Evidence: End-to-end compaction recovery transcript
Evidence: Manual PreCompact resume anchor
Evidence: Fresh automatic PreCompact resume anchor
Evidence: Compact-sourced SessionStart recovery output
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed (2) ✅
bin/fm-autocompact.sh:66- The required criterion says to “deterministically capture a fresh local durable resume anchor before both automatic and manual compaction” and inject recovery on compact-sourced SessionStart, butcommand -v jq ... || exit 0silently disables both phases. Becausejqis not an unconditional bootstrap dependency, a valid primary can compact without an anchor or recovery. Confirm this must fail closed during capture and still surface recovery context after compaction.bin/fm-autocompact.sh:182- The required fresh-anchor guarantee is contradicted because the rendering group returns only its final command’s status. An earlierprintforsedfailure can therefore be masked by the successful line-182 test, after which an incomplete temporary file is published and compaction proceeds. Ensure every rendering failure prevents the atomic move.🔧 Fix: Captain, harden jq-free autocompact recovery
1 warning still open:
bin/fm-autocompact.sh:199- Once an in-scoperecovercommand is running, unreadable or empty stdin and malformed or missing event fields still exit successfully beforerecover_context. Because the registered hook is already compact-scoped, these transport/parser failures suppress both the readable anchor and normal session-start digest after the compaction boundary. Emit recovery with a warning on parse failure; reserve silent no-op behavior for a successfully parsed non-compact event.🔧 Fix: Captain, preserve recovery on malformed payloads
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; uv run --directory tools/agent-fleet --locked pytest || rc=1; uv run --directory tools/agent-fleet --locked python -m compileall -q src || rc=1; exit "$rc"Pre-supplied successful baseline:command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; uv run --directory tools/agent-fleet --locked pytest || rc=1; uv run --directory tools/agent-fleet --locked python -m compileall -q src || rc=1; exit "$rc"Focused automation:bash tests/fm-autocompact.test.shManual replay: piped a manual PreCompact payload throughFM_ROOT_OVERRIDE=<isolated-primary> FM_HOME=<isolated-home> ./bin/fm-autocompact.sh captureAutomatic replay: piped an automatic PreCompact payload through the same isolated capture path and verified the prior manual marker disappearedRecovery replay: piped a compact SessionStart payload throughFM_GATE_REFUSE_BYPASS=1 FM_ROOT_OVERRIDE=<isolated-primary> FM_HOME=<isolated-home> ./bin/fm-autocompact.sh recoverScoping replay in a marked secondmate home, an unmarked linked child worktree, and a non-Firstmate directoryRuntime check:claude --versionreturned Claude Code 2.1.217Cleanup verification:git status --shortreturned clean✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.