feat(bin): replay held local improvements across upstream updates - #1622
Open
JaredHuynhning wants to merge 4 commits into
Open
feat(bin): replay held local improvements across upstream updates#1622JaredHuynhning wants to merge 4 commits into
JaredHuynhning wants to merge 4 commits into
Conversation
5 tasks
JaredHuynhning
force-pushed
the
fm/fm-local-patch-stack
branch
from
August 4, 2026 11:46
4bcfbb9 to
b022914
Compare
JaredHuynhning
force-pushed
the
fm/fm-local-patch-stack
branch
from
August 7, 2026 03:24
b022914 to
1646067
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
Let this Firstmate instance take upstream improvements every night while also running local improvements that upstream has not merged. Keep upstream history as a clean, unmodified, fast-forward-only base: do not rewrite or merge into the branch tracking origin. Record held improvements explicitly and inspectably, then transactionally reapply them after every upstream advance. Automatically retire a held improvement when upstream contains content-equivalent work even if it was rebased or squashed, using content rather than commit identity. On a genuine collision, fail closed before publishing anything: keep the primary and linked secondmates on the last known-good effective revision, never silently drop either side, and reuse state/.nightly-update-needs-attention with a message naming the held improvement, the exact upstream change, and affected paths; provide a proven explicit recovery path. Carry the same effective revision into real leased detached linked secondmate worktrees so they cannot silently diverge from the primary, and document any unsupported remote or standalone route honestly. Use bin/fm-push-guard.sh from upstream PR 1602 as the first real held improvement. Prove an upstream advance and held file are both live, prove squash/rebase-equivalent automatic retirement, prove a RED genuine conflict plus recovery, and prove the held file in a real secondmate home. Preserve existing update, fleet-sync, bootstrap, secondmate-sync, test-runner, documentation, and lint behavior.
What Changed
bin/fm-held-improvements.shforinit/add/retire/list,bin/fm-held-lib.shfor the mechanics) that records each local improvement as an ordered, titled binary patch underconfig/held-improvements/, keyed by a stable id matched exactly against the<order>-<id>stem.bin/fm-update.shso that, when the stack is initialized, the default branch stays a pristine fast-forward of upstream while a scratch worktree rebuilds the full candidate. The primary switches its detached live ref only after every entry applies, entries whose content or patch id already landed upstream retire automatically (rebased and squashed equivalents included, whitespace differences excluded), and a genuine conflict exits nonzero, keeps the prior effective revision live, and writesstate/.nightly-update-needs-attentionnaming the held entry, each colliding upstream commit, and the affected paths (deduplicated per commit). Known-effective refs are capped byHELD_EFFECTIVE_RETAINso they cannot grow without bound, andinitrolls back its directory if the ref or checkout step fails.bin/fm-ff-lib.shto resolve the primary's effective commit (default branch, or the held live ref) and to let a clean detached secondmate home switch across replayed ancestry when both ends are known-effective, keeping dirty, branch-checked-out, and unknown-divergence homes untouched.bin/fm-bootstrap.shnow surfaces the attention file as aNIGHTLY_UPDATE_ATTENTION:line on every path including detect-only,bin/fm-test-run.shroutesbin/fm-ff-lib.shandbin/fm-held-*changes to the secondmate and session-bootstrap suites, and the update, spawn, configuration, architecture, and skill docs are updated to describe held mode, including the unsupported standalone-clone and remote routes.Risk Assessment
✅ Low: Every accepted round-1 fix landed as a bounded, targeted change with a regression test that is genuinely red against the old code, the held stack remains opt-in local configuration with the default non-held paths untouched, and the two remaining findings are info-level and need either operator misconfiguration of an env knob or roughly ten consecutive failed publishes to bite.
Testing
I ran the new held-improvement suite plus the four adjacent suites that own the preserved update, secondmate-sync, fleet-sync, and bootstrap behaviour, and all pass with no failures. Because passing shell tests alone would not show the product working, I also built a real sandbox (bare origin, primary checkout, and a genuine leased detached linked secondmate worktree) and drove the actual CLIs with the real bin/fm-push-guard.sh pulled from PR 1602 head 8f4c809. The captured transcript shows all four required proofs: an upstream advance and the held file live together on both primary and secondmate at one identical effective revision with the origin-tracking branch left a clean fast-forward (zero merge commits, no held commit on it); a squashed upstream commit with a different commit id retiring the entry by content with a recorded reason; a genuine collision exiting 1, publishing nothing, holding both homes on the last known-good revision, writing state/.nightly-update-needs-attention naming the held id, upstream commit and subject and path, then bootstrap reporting it as NIGHTLY_UPDATE_ATTENTION; and the explicit retire-then-rerun recovery clearing the alarm and reconverging both homes. This change has no UI surface, so the reviewer-visible artifact is the CLI transcript rather than a screenshot. One sub-clause I did not exercise end to end is the held-mode remote-secondmate skip, which needs a configured remote host; it is implemented as an explicit skip message and documented in the updatefirstmate skill and docs/configuration.md.
Evidence: End-to-end held-improvement CLI transcript (upstream advance, squash retirement, RED conflict, recovery, secondmate convergence)
$ bin/fm-held-improvements.sh list active 010 pr-1602 PR 1602 stale-branch push guard $ bin/fm-update.sh # nightly run firstmate: updated eece106..9f5ec53 (upstream main 8d56597..c056223; reapplied: pr-1602; retired: none) secondmate alpha: updated eece106..9f5ec53 (instructions changed: AGENTS.md) $ head -2 sm-alpha/bin/fm-push-guard.sh # the held PR 1602 file is live in the real secondmate home #!/usr/bin/env bash # fm-push-guard.sh - refuse a task-branch push that could discard remote work. $ the branch tracking origin is a clean fast-forward, never merged or rewritten refs/heads/main c056223937b8db42a9e8ff603e959069899c8c53 origin/main c056223937b8db42a9e8ff603e959069899c8c53 strict fast-forward of the recorded base: yes merge commits on the pristine base: 0 held commit present on refs/heads/main: 0 === ACT 3: upstream squashes the same work in; the held entry must retire itself === held source commit ddb84003dcd138634aea8982aef5c99b133a9117 upstream squash 4868d129163762976291f539bca93b74619b2c2e firstmate: updated ebbbe89..4868d12 (upstream main 9dc1066..4868d12; reapplied: none; retired: pr-1602) retired 010 pr-1602 PR 1602 stale-branch push guard upstream content equivalent detected at 4868d129163762976291f539bca93b74619b2c2e === ACT 4: a genuine collision must go RED and publish nothing === --> exit status: 1 $ cat home/state/.nightly-update-needs-attention held improvement pr-1602 (PR 1602 stale-branch push guard) collided with upstream change(s): a2c35070fdcde07c90b498827a3f6e4ce1f288c9 upstream: replace the push guard with an incompatible policy; paths: bin/fm-push-guard.sh; live revision remains 9f5ec53307baa4a2787174f1f5405b8b1b06d710 last known good 9f5ec53307baa4a2787174f1f5405b8b1b06d710 primary HEAD 9f5ec53307baa4a2787174f1f5405b8b1b06d710 sm-alpha HEAD 9f5ec53307baa4a2787174f1f5405b8b1b06d710 NIGHTLY_UPDATE_ATTENTION: held improvement pr-1602 (PR 1602 stale-branch push guard) collided with upstream change(s): a2c35070fdcde07c90b498827a3f6e4ce1f288c9 ... === ACT 5: the documented recovery path === retired: pr-1602 took the upstream push policy after review firstmate: updated 9f5ec53..a2c3507 (upstream main c056223..a2c3507; reapplied: none; retired: none) secondmate alpha: updated 9f5ec53..a2c3507 (instructions changed: bin) --> exit status: 0 (absent - alarm cleared) primary HEAD a2c35070fdcde07c90b498827a3f6e4ce1f288c9 sm-alpha HEAD a2c35070fdcde07c90b498827a3f6e4ce1f288c9Evidence: Reproducible end-to-end walkthrough script
Evidence: The real bin/fm-push-guard.sh from PR 1602 head 8f4c809 used as held entry 010-pr-1602
Evidence: Targeted suite results
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-held-improvements.sh:141-cmd_retireselects the entry with the unanchored glob"$config"/active/*-"$id".patch, so an id that is a hyphen-suffix of another entry's id matches the wrong file. With only010-pr-1602.patchactive,fm-held-improvements.sh retire 1602 '<reason>'matches exactly one entry (matches=1passes) and silently retirespr-1602, moving the wrong held improvement out of the active set on the very path the intent designates as the explicit conflict-recovery route. The same unanchored glob at lines 113 and 116 makesaddfalsely reportid ... is already in usefor a genuinely new id. Anchor both to the order prefix, e.g."$config"/active/[0-9][0-9][0-9]-"$id".patch.bin/fm-held-lib.sh:157- Theseendedup inheld_conflicting_upstream_changestestscase "\n$seen\n"(literal backslash-n inside double quotes) against an accumulator joined with a real newline (${seen:+$'\n'}), so the pattern can only ever match whenseenholds exactly one entry. Reproduced: feedinga,b,athrough the identical loop emitsemit:a emit:b emit:a. When one upstream commit modifies two or more of a held patch's paths, that commit and subject are repeated in thestate/.nightly-update-needs-attentionmessage for every path it touched. Use a real newline in the case subject (case $'\n'"$seen"$'\n' in) or dedup with aseen_<hash>marker.bin/fm-test-run.sh:925-families_for_changed_pathmapsbin/fm-ff-lib.shonly topure-contract-unit, but this change moves held-mode decision logic into that file (primary_head_commit'sheld_stack_activebranch at fm-ff-lib.sh:63 and theeffective_rewritecheckout path at fm-ff-lib.sh:349-373).bin/fm-held-*andbin/fm-update.shwere correctly added to thesession-bootstraparm at line 897, but the ff library was not. A later change that touches onlybin/fm-ff-lib.shwill therefore not selectsession-bootstrap, sotests/fm-held-improvements.test.sh,tests/fm-update.test.sh, andtests/fm-bootstrap.test.shwill not run against an edit to the exact code that decides whether a secondmate switches across replayed ancestry. Addsession-bootstrap(andsecondmate) to thebin/fm-ff-lib.sharm.bin/fm-held-lib.sh:58-held_register_effectivewritesrefs/firstmate/held/effective/<sha>and nothing ever prunes it. Every successfulheld_update_primaryadds two refs (the new upstream base and the new candidate), so a nightly updater accumulates roughly 730 refs per year in the primary repo, each one pinning its commit and full tree againstgit gc. The known-effective set only needs to be deep enough for a linked secondmate that missed a few updates to catch up; consider capping it (retain the last N, or drop entries whose commit is an ancestor of the current pristine base) so the repo does not grow without bound.bin/fm-held-lib.sh:94-state/.nightly-update-needs-attentionhas no reader anywhere in this repo: grep across bin/, .agents/, and docs/ finds only this writer,held_clear_attention, the new test, and the two doc sentences.docs/architecture.md:303describes it as "the existingstate/.nightly-update-needs-attentionalarm", and the intent says to reuse it, which suggests an external nightly job consumes it. Nothing in the session-start or bearings surface surfaces it, so within this repo a replay conflict is visible only throughfm-update.sh's nonzero exit and stderr. Please confirm the external consumer exists, or the conflict may go unnoticed by a captain who does not read the nightly job's output.bin/fm-held-improvements.sh:94-cmd_initdoesmv "$tmp" "$config"beforegit update-ref "$HELD_LIVE_REF"and the detaching checkout, and the script runs underset -eu. If either later step fails,config/held-improvements/exists while the live ref does not. From that stateheld_stack_presentis true so everyfm-update.shrun fails closed with an attention alarm, whileinitrefuses with "already exists" andadd/retire/listrefuse with "held-improvement stack is not initialized". There is no supported command to get out, so the operator must hand-remove the directory, which is at odds with the intent's requirement for a proven explicit recovery path. Either publish the ref before the directory is moved into place, or give the tool adeinit/repair path.tests/fm-held-improvements.test.sh:21- The intent names "bin/fm-push-guard.sh from upstream PR 1602 as the first real held improvement". The committed evidence uses a two-line stand-in (printf "push guard from PR 1602\\n") synthesised inside the test, and the real stack lives under gitignoredconfig/held-improvements/, so nothing in the diff shows PR 1602's actual patch was captured. The test proves the machinery end to end, which is the right thing for a test to do, but it does not prove the real held improvement is live. Please confirm the actual PR 1602 patch was captured into the local stack, since that part is operator-local state a source review cannot verify.🔧 Fix: fix held stack id matching, dedup, ref growth, init atomicity
2 infos still open:
bin/fm-held-lib.sh:356-held_update_primaryregisters$new_baseand$candidateas known-effective at lines 356-357, beforegit checkout --detachat 358 and before the live ref publishes at 364. A run that fails after that point therefore still consumes two slots of the bounded window without publishing anything. Combined withHELD_EFFECTIVE_RETAINhaving no floor at line 64, the previous live commit's anchor can be evicted: the ordering is [.., old_live, new_base, candidate], so it survives only while the retain value is at least 3, and roughly ten consecutive failed publishes at the default of 20 push it out too. Once that ref is gone,held_commit_is_known_effectivereturns false for every linked secondmate still sitting onold_live, the ancestry check also fails across the replay, andff_targetreportsskipped: divergedfor each home with no supported command to re-anchor them. The failure is reported rather than silent, and bootstrap now surfaces the nightly alarm, so an operator would normally act first. Move the twoheld_register_effectivecalls below the successfulupdate-ref "$HELD_LIVE_REF"so only published revisions consume the window, and clampHELD_EFFECTIVE_RETAINto a minimum of 3 since docs/configuration.md:230 advertises the knob without stating a lower bound.bin/fm-bootstrap.sh:1025- Thewhile IFS= read -r attention_line ... done < "$nightly_attention"loop discards a final line that has no trailing newline, becausereadreturns nonzero on EOF even after assigning the partial line.held_attentionwrites throughheld_write_file, which always terminates withprintf '%s\n', so this is defensive rather than currently reachable. It matters because the alarm message puts the multi-line upstream commit list in the middle and the paths and live-revision tail on the last line, so a truncated write would silently cost the operator exactly the part that names the affected paths. Change the loop condition towhile IFS= read -r attention_line || [ -n "$attention_line" ]; do.✅ **Test** - passed
✅ No issues found.
bash tests/fm-held-improvements.test.sh(10 cases: convergence, content-equivalent retirement, whitespace non-equivalence, relative FM_HOME, checked-out pristine base refusal, RED conflict plus recovery, exact id matching, collision dedup, init rollback, bounded effective refs)bash tests/fm-update.test.sh(preserves the non-held fast-forward update behaviour)bash tests/fm-secondmate-sync.test.sh(preserves spawn/bootstrap secondmate sync behaviour after the fm-ff-lib.sh change)bash tests/fm-fleet-sync.test.sh(preserves fleet-sync behaviour)bash tests/fm-bootstrap.test.sh(includes the new detect-only NIGHTLY_UPDATE_ATTENTION report)Manual end-to-end:REPO=$(pwd) EV=... bash held-improvements-e2e.shdriving the realbin/fm-held-improvements.sh init/add/list/retire,bin/fm-update.sh, andbin/fm-bootstrap.shagainst a sandbox origin, a primary checkout, and a realgit worktree add --detachsecondmate homeFetched the actualbin/fm-push-guard.shfrom PR 1602 head8f4c809viagh apiand used it verbatim as held entry010-pr-1602bin/fm-held-improvements.sh:152- bin/fm-lint.sh fails on this branch with SC1007 at bin/fm-held-improvements.sh:152 (local id=$1 reason=$2 config patch found= stem matches=0). This is executable code introduced by the change, outside the documentation phase's remit, so it was left for the lint phase.🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix: make empty found init explicit in fm-held-improvements.sh
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.