feat(bin): let a lone untracked CAPTAINS-LOG.md pass the fleet-sync clean check - #1205
Open
cdonovan-abtex wants to merge 6 commits into
Open
Conversation
… blocked sync STUCK
main's fbece9c switched fm-teardown to exact tmux selectors and made the old-vs-new conformance fixture extract bin/backends/tmux.sh from BASE_REF. On a PR merge ref, merge-base(HEAD, main) is main itself, so the "old" fixture picked up the exact selectors and the permissive-selector assertion failed. main's 2459f77 fixes that by resolving a content-historical permissive adapter; merging main brings the fix onto this branch.
Author
|
@kunchenguid The refreshed CI and Require no-mistakes workflows are waiting for maintainer approval ( |
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
Ship the Captain-adopted anchored CAPTAINS-LOG.md fleet-sync eligibility rule in Firstmate shared tracked code. Treat exactly one lone untracked root '?? CAPTAINS-LOG.md' entry as clean only for fleet auto-update, leaving the file untracked, discoverable, and protected by Git from overwrite. Tracked CAPTAINS-LOG modifications, that entry plus any second dirty path, nested logs, and similarly named paths must remain dirty and refuse automatic updating. Do not add any global, repository, or info/exclude ignore and do not weaken general dirty-tree, pruning, worktree, or non-fleet-sync Git safety. Keep the behavior contract in the fleet-sync script header/help rather than AGENTS.md, add colocated shell regression tests, validate relevant runtime/backend axes, and open one PR without merging.
What Changed
bin/fm-fleet-sync.shnow treats a working tree whose entiregit status --porcelainoutput is exactly?? CAPTAINS-LOG.mdas eligible for auto-update. The exception is tracked in its ownlone_captains_logflag rather than by looseningdirty, so it relaxes only the on-default fast-forward gate — detached-HEAD re-attachment, pruning, and worktree guards still require a fully clean tree, and no ignore rule is added anywhere. A tracked-log modification, the log plus any second dirty path, a nested log, and a similarly named path all stay dirty and refuse to update.CAPTAINS-LOG.md, the clone is escalated to a loud, quantifiedSTUCK: branch <default> with untracked CAPTAINS-LOG.md blocking fast-forwardinstead of a quietskipped:. Detecting that refusal reads Git's human-facing error text, so themerge --ff-onlyand the threefetchcalls behind the packed-refs-lock recovery are pinned toLC_ALL=C, matching the existingfm-wake-lib.shidiom; the blocked path is matched literally so a similarly named file cannot satisfy it.fm-fleet-sync.shscript header (with the STUCK wording inbootstrap-diagnostics/SKILL.mdanddocs/architecture.mdupdated to match), andtests/fm-fleet-sync.test.shgains 8 colocated regression tests covering the allow case, Git's retained overwrite protection, non-English locale, detached HEAD, and each of the four refuse cases. The suite passes 30/30, including under stock macOS bash 3.2.Risk Assessment
✅ Low: The final round removes all global shell-mode mutation from the test file in favor of a local
|| true, restoring the suite's declaredset -u-only baseline without altering any assertion, and the production change is a well-bounded, locale-hardened, fully test-pinned eligibility rule that satisfies every stated acceptance criterion.Testing
I exercised the change both as automated regression tests and as a real end-user CLI run. The colocated suite passes 30/30 under bash 5.3 and again under stock macOS bash 3.2, and
fm-test-run.sh --changed --base fa0d85dpasses all 8 session-bootstrap suites. For product-level evidence I built a real 7-clone fleet covering every case the intent names and ran the actualbin/fm-fleet-sync.sh: only the clone whose sole dirt is one untracked root CAPTAINS-LOG.md fast-forwarded (log left untracked and unmodified), every other case stayed STUCK and untouched, and the origin-tracks-the-log case surfaced as a quantified STUCK naming the blocking path rather than a benign skip. Running the base-commit script against an identical fleet copy showed all seven as STUCK, isolating the delta. I also verified no ignore of any scope was introduced, that the rule is idempotent on re-run, that prune and worktree safety still behave, and that the session-startfm-bootstrap.shsurface nags for every blocking case while silently updating the blessed one. This is a shell CLI change with no rendered UI, HTML, or renderer surface, so the reviewer-visible evidence is CLI transcripts and post-run Git state — which is precisely what the Captain experiences. No failures, no flakes, worktree left clean.Evidence: Full evidence transcript (before/after fleet run, per-clone outcomes, Git-protection proof, idempotency, session-start surface, prune safety)
Evidence: Before/after CLI contrast on an identical 7-clone fleet
Evidence: Post-run state: log stays visible, untracked, and unmodified; no ignore rule anywhere
== a-lone-captains-log (HEAD: fast-forwarded bf9cefc..217e8ec) $ git status --porcelain ?? CAPTAINS-LOG.md $ cat CAPTAINS-LOG.md # Captain's local content # Captain log - day 1: charted the reef $ git check-ignore -v CAPTAINS-LOG.md notes/CAPTAINS-LOG.md CAPTAINS-LOG.md.bak <none - no ignore rule matches; the log stays visible to Git> == g-origin-tracks-log (HEAD: unchanged) $ git status --porcelain ?? CAPTAINS-LOG.md $ cat CAPTAINS-LOG.md # Captain's local content # irreplaceable local log $ git check-ignore -v CAPTAINS-LOG.md notes/CAPTAINS-LOG.md CAPTAINS-LOG.md.bak <none - no ignore rule matches; the log stays visible to Git>Evidence: What the Captain sees at session start (fm-bootstrap FLEET_SYNC relay)
$ fm-bootstrap.sh | grep FLEET_SYNC FLEET_SYNC: b-tracked-log-edited: STUCK: on branch main with uncommitted changes, 1 commits behind origin/main - needs attention FLEET_SYNC: c-log-plus-other-dirt: STUCK: on branch main with uncommitted changes, 1 commits behind origin/main - needs attention FLEET_SYNC: d-nested-log: STUCK: on branch main with uncommitted changes, 1 commits behind origin/main - needs attention FLEET_SYNC: e-similar-name: STUCK: on branch main with uncommitted changes, 1 commits behind origin/main - needs attention FLEET_SYNC: f-detached-plus-log: STUCK: on detached HEAD with uncommitted changes, 1 commits behind origin/main - needs attention FLEET_SYNC: g-origin-tracks-log: STUCK: on branch main with untracked CAPTAINS-LOG.md blocking fast-forward, 1 commits behind origin/main - needs attention $ # a-lone-captains-log is silently fast-forwarded (no nag), log untouched: ?? CAPTAINS-LOG.md e623ef2 C1 -> HEAD == origin/main (fleet auto-update succeeded)Evidence: Prune and worktree safety unchanged under the new exception
$ git branch --list # before + held-by-worktree * main merged-pr $ fm-fleet-sync.sh prunecase prunecase: pruned merged-pr prunecase: synced f5d7b9e..6566576 $ git branch --list # after + held-by-worktree * main $ git status --porcelain # the Captain's log is still there, untracked ?? CAPTAINS-LOG.mdEvidence: Reproducible demo-fleet builder used to generate the evidence
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 3 issues found → auto-fixed (3) ✅
bin/fm-fleet-sync.sh:341- The clean-tree exception lets a clone with an untracked root CAPTAINS-LOG.md reachgit merge --ff-onlyeven when origin has published a tracked CAPTAINS-LOG.md. Git correctly refuses, but the clone is then reported asskipped: fast-forward failed: ...instead of the previousSTUCK: ... N commits behind ... - needs attention..agents/skills/bootstrap-diagnostics/SKILL.md:32definesskipped:as "a benign one-off skip (offline, no origin, local-only); bootstrap continued, investigate only if it blocks work", yet this state is permanent and carries no commits-behind count — exactly the "quiet drift" the script header says STUCK exists to prevent. Worse,first_linetruncates the merge error to "error: The following untracked working tree files would be overwritten by merge:", dropping the following line that names the file, so the operator cannot tell which path blocked. Consider re-checking for the CAPTAINS-LOG overwrite conflict after a failed fast-forward and routing it throughreport_stuck(e.g.report_stuck "untracked CAPTAINS-LOG.md blocking fast-forward") so it stays loud and quantified. tests/fm-fleet-sync.test.sh:314 currently pins the quiet form, so this needs the author's call.bin/fm-fleet-sync.sh:355-dirtyfeeds three call sites, not just the fast-forward gate. Relaxing it at line 341 also relaxes the detached-HEAD auto-recovery precondition at line 355, so fleet sync will now rungit checkout <default>on a detached clone that holds an untracked root CAPTAINS-LOG.md. That mutates the working tree under a condition that previously required a fully clean tree. It is safe in practice (checkout refuses to overwrite an untracked file when the target branch tracks it, and the failure falls through toreport_stuck), but the new header text at lines 12-15 advertises only that "fleet sync may attempt its normal fast-forward" — it does not mention the re-attach path. Either narrow the exception to the fast-forward gate, or extend the header wording to cover recovery and add a regression test for detached-HEAD + lone untracked CAPTAINS-LOG.md, since none of the six new tests exercises that combination.bin/fm-fleet-sync.sh:339- This repo already has a marker-excluding dirty check with the same shape:dirty_statusin bin/fm-ff-lib.sh:225-231 usesawk -v marker="?? $SUB_HOME_MARKER" '$0 != marker { print; exit }', which is semantically equivalent, order-independent, streams rather than materializing the whole porcelain output into a shell variable, and preserves the early-exit the removed| head -1provided. fm-fleet-sync.sh does not source fm-ff-lib.sh (only fm-lock-lib.sh), so reuse is not free and the current inline form is correct as written — noting it only so the two marker-exclusion idioms are a conscious divergence rather than drift.🔧 Fix: Scope lone-log exception to fast-forward; report blocked sync STUCK
3 issues (1 warning, 2 infos) still open:
bin/fm-fleet-sync.sh:149-is_captains_log_overwrite_refusalmatches the English phraseuntracked working tree files would be overwritten, but thegit merge --ff-onlyat line 421 runs with the operator's ambient locale. Git ships gettext translations, so under a non-English LANG/LC_ALL the merge refusal is localized, the grep misses, and the clone falls through toecho "$label: skipped: $reason"at line 431 — restoring exactly the benign-skip reporting this commit was written to eliminate, and silently so. This repo already treats locale-dependent command output as a bug and pins it: bin/fm-wake-lib.sh:51 carries the comment "Pin LC_ALL=C so lstart's date format is locale-invariant", with the same idiom at bin/fm-ensure-agents-md.sh:67 and bin/fm-tmux-lib.sh:169. Fix: run the merge asLC_ALL=C git -C "$PROJ" merge --ff-only "$BASE" 2>&1. The pre-existingis_packed_refs_lock_error(line 141) has the identical exposure against the fetches at lines 172/183/205 and is worth pinning in the same pass.bin/fm-fleet-sync.sh:150-grep -q "^[[:space:]]*$CAPTAINS_LOG\$"interpolates the filename into a basic regular expression, where the.inCAPTAINS-LOG.mdis a wildcard — the pattern also matches a blocked path likeCAPTAINS-LOGXmd. It is unreachable today because the caller at line 423 requireslone_captains_log = yes, which proves the sole untracked entry is exactlyCAPTAINS-LOG.md, so this is a latent trap rather than a live bug. Escaping the literal (grep -q '^[[:space:]]*CAPTAINS-LOG\.md$') or trimming the leading whitespace and usinggrep -Fxq -- "$CAPTAINS_LOG"keeps the check correct if the constant or the guard is ever changed..agents/skills/bootstrap-diagnostics/SKILL.md:34- The operator-facing STUCK entry enumerates the causes as "the clone is dirty, on a non-default branch, detached with unique commits, or diverged" and does not cover the newbranch <default> with untracked CAPTAINS-LOG.md blocking fast-forwardstate, whose remediation is different (the Captain moves or commits the local log, or upstream stops tracking it — not the usual drift resolution). TheSTUCK: on <state>, N commits behind <base> - needs attentionline format is unchanged, so the doc's parsing guidance and the "dispatch a crewmate" advice still hold. Flagging rather than fixing because your intent requires "Keep the behavior contract in the fleet-sync script header/help rather than AGENTS.md" — this file is neither, so whether the new cause belongs here is your call.🔧 Fix: Pin LC_ALL=C for git error matching; match log literally
1 warning still open:
tests/fm-fleet-sync.test.sh:373- The new test wraps its guarded run inset +e/set -e, but this file enables onlyset -u(line 29) — errexit was never on, so the trailingset -edoes not restore prior state, it switches errexit on for the rest of the run. The five pre-existing users of this idiom (lines 684, 714, 742, 768, 794) are the last five entries in the invocation list, so their leak was harmless;test_captains_log_stuck_survives_a_non_english_localeis invoked 7th of 30 (line 812), so all 23 subsequent tests now execute under errexit when they were written and validated without it. I scanned those tests and found no command that returns non-zero on the happy path, so the suite should still pass — the cost is latent: any baregrep/git/[ ]later added to a following test will abort the whole run at that point instead of emittingnot ok, and the tests after it silently never execute. Change line 373 toset +eto match the file's actual baseline (and consider the same for the five pre-existing sites so the idiom is consistent).🔧 Fix: Stop test harness from enabling errexit mid-suite
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
bash tests/fm-fleet-sync.test.sh— 30/30 ok, including the 8 new CAPTAINS-LOG regression tests/bin/bash tests/fm-fleet-sync.test.sh— full suite re-run under stock macOS bash 3.2 (the runtime axis behind issue #1179), exit 0bin/fm-test-run.sh --changed --base fa0d85d— all 8 selectedsession-bootstrapsuites pass (fleet-sync, bootstrap, session-start, sessionstart-nudge, gate-refuse, gotmp, tangle-guard, update)Manual end-to-end: built a 7-clone fleet (lone root log / tracked log edited / log+second dirt / nested log / similar name / detached+log / origin-tracks-log) and ran the realbin/fm-fleet-sync.shagainst it — CLI transcript in02-after-fleet-sync-transcript.txtManual baseline contrast: rangit show fa0d85d:bin/fm-fleet-sync.shagainst an identical copy of the same fleet — all 7 clones reported STUCK, isolating the behavior delta to this changeManual Git-protection proof: post-rungit status --porcelain,cat CAPTAINS-LOG.md,git check-ignore -v CAPTAINS-LOG.md notes/CAPTAINS-LOG.md CAPTAINS-LOG.md.bak,git config --get core.excludesFile, and an active-rule count of every.git/info/excludein the fleetManual idempotency check: secondfm-fleet-sync.shrun on the synced fleet —already current, log still?? CAPTAINS-LOG.mdManual session-start surface:FM_HOME=<demo fleet> bin/fm-bootstrap.sh | grep FLEET_SYNC— STUCK lines relayed for every blocking case, silence plus a successful fast-forward for the lone-log cloneManual prune/worktree safety: clone with a lone CAPTAINS-LOG plus a gone-upstreammerged-prbranch and a worktree-heldheld-by-worktreebranch —fm-fleet-sync.sh prunecasepruned only the gone branch and preserved the worktree✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.