Skip to content

fix(sync-axi): allow updates alongside local branches - #21

Merged
BohnBawerick merged 9 commits into
mainfrom
fm/fm-sync-squash-fix
Sep 8, 2026
Merged

BohnBawerick merged 9 commits into
mainfrom
fm/fm-sync-squash-fix

Conversation

@BohnBawerick

@BohnBawerick BohnBawerick commented Sep 7, 2026

Copy link
Copy Markdown
Owner

Summary

Two reproduced defects in firstmate's own tooling, plus the documentation and two CI-facing changes described below.
This section lists everything in the diff, so the diff and this description match.

1. bin/fm-sync-axi.sh refused every repository.
sync_repo() refused a whole repository when any local branch was not an ancestor of the default branch.
A squash merge rewrites a branch's commits into one new commit, so a squash-merged branch tip is never an ancestor of the default branch however completely its content landed.
Every task branch therefore read as unlanded forever and no clone ever synced.
The check is deleted rather than taught about squash merges, because it guarded nothing: sync_repo() only rebases or merges the default branch of the real checkout plus a detached scratch worktree it removes afterwards.
Every other guard is unchanged (dirty tree, non-default-branch checkout, fetch failure, scratch-worktree conflict trial), and other local branches keep their commits.

2. bin/fm-landing-remote.sh called a flag that does not exist.
no-mistakes --yes init is rejected with unknown flag: --yes before init runs, because --yes is a root-local wizard flag rather than a persistent one.
refresh_no_mistakes() therefore always failed and apply restored the old remotes instead of remapping them.
The call is now a bare no-mistakes init.

Files in this diff

File Change
bin/fm-sync-axi.sh Delete the local-branch ancestry refusal; correct the header guarantees and usage() step 2 that documented it.
tests/fm-sync-axi.test.sh Regression test for the squash-merged case, asserting the branch tip really is not an ancestor before asserting the sync applies.
bin/fm-landing-remote.sh Bare no-mistakes init.
tests/fm-landing-remote.test.sh Pin the stub call to an exact bare init line, still refuting --fork-url.
.agents/skills/sync-axi/SKILL.md Describe the real refusal conditions instead of "unlanded work".
AGENTS.md Drop the one-line restatement of the same claim, which the skill owns.
.github/workflows/ci.yml Snapshot/fleet-view count assertion corrected from 15 to 16.
tests/fm-on.test.sh Close a teardown race; see the dedicated section below.

Why ci.yml is in this PR.
Base-branch commit 495ce74 added a sixteenth test to tests/fm-fleet-snapshot-view.test.sh without updating the expected count, so the assertion read 15 against a suite of 16 and the macOS job failed on main itself.
Nothing in this branch changes that count.
The assertion is kept and corrected rather than removed, because deleting a count guard to turn a job green removes the protection the guard exists to give.

Why tests/fm-on.test.sh is in this PR

This file is beyond the seven the task named, and it is here deliberately.

The failure it fixes.
CI run 34185426729 (shard Behavior portable serial 3) failed on commit d4702a6, where this file was byte-identical to main.
Every assertion in the test passed and the script printed ALL TESTS PASSED; the run then failed during teardown with:

rm: cannot remove '/tmp/fm-on.wZvxeF/remote-jobs/worker.lock': Directory not empty

The trap's rm -rf of the fixture raced a remote-job worker still holding worker.lock.
Nothing asserted and failed.

It pre-existed on main.
The race is in main's own version of the test and was not introduced by this work.
It is timing dependent, which is why main does not fail on it every time.
It is fixed here rather than deferred because a known intermittent race would otherwise sit on main and leave this PR unable to be called green with confidence.

What the fix does.
The teardown now stops the complete worker tree before deleting the fixture, instead of killing a single pid and racing the survivors.
It widens no kill and weakens no assertion, and no assertion was deleted.

On the process-group concern.
fm_remote_job_stop_worker_tree signals a process group only when fm_remote_job_worker_process_group (bin/fm-remote-job-lib.sh:971) proves that group is safe to signal: it refuses a group equal to the calling shell's own, refuses pgid 0 and 1, and requires the group leader's command to be fm-remote-job-worker.sh.
Under CI conditions this was checked directly, not assumed: in a non-interactive shell the background worker does share the test shell's process group, and the function refuses in exactly that case, so the caller falls back to signalling the single pid.
The test runner cannot be signalled by this path.

Intent

The developer wanted repository maintenance fixed end to end: adopt option A by removing the obsolete local-branch refusal from the sync tool, retain its real safety checks, prune finished branches, and update every affected repository or tool. They also asked for a Lavish board containing every pending decision and enough context to resolve each one, then authorized the two firstmate fixes, the four highest-priority MRB defects with regression tests, the ADR correction, and duplicate-folder rejection. They required career-forging and MRB to use the latest supported pnpm and package versions while keeping the implementation simple, preserving the running test environment and data, and merging only green MRB changes. They also wanted the pi harness used about as much as the other runtimes, with unproven or unlanded work left intact unless they explicitly approved discarding it.

What Changed

  • Let fm-sync-axi update default-branch checkouts when other local branches exist, including squash-merged branches, while retaining dirty-tree and non-default-checkout guards and preserving those branches.
  • Run landing-remote setup with the supported no-mistakes init syntax so the rejected --yes flag no longer causes remote remaps to roll back.
  • Align sync documentation and regression cases with the revised branch policy, and raise the macOS snapshot test expectation from 15 to 16.

Risk Assessment

✅ Low: the changes are bounded, match the accepted intent, and add behavioral regression coverage for both fixes without introducing a material source risk.

Testing

After correcting a test-only stub, baseline replays reproduced the old sync refusal and rejected --yes init; the focused suites passed, and direct CLI checks proved the current tools update a clean default branch despite an unmerged local branch, retain dirty/non-default/conflict protections, and persist the landing remote using bare no-mistakes init.

Evidence: Unmerged branch sync comparison

Source: Unmerged branch sync comparison

Scenario: sync a clean main branch while an unmerged local branch exists

Before sync
  checked-out branch: main
  main: 1e937ea1f01206f9665a0ac6a885e6c4ba322a17
  feature/unmerged: f60d956fa3379d3189de6c1e733145b1e317bd65
  feature/unmerged is merged into main: no

Previous behavior from base commit
  sync-clone: unlanded work in progress - not applied
  main changed: no

Current behavior
  sync-clone: updated, 1 new commit from upstream
  main now matches origin/main: yes
  upstream file on main: upstream content
  feature/unmerged still exists: yes
  feature/unmerged tip preserved in this default configuration: yes
Evidence: Retained sync safety checks

Source: Retained sync safety checks

Scenario: retained sync safety checks

Dirty working tree
  sync-clone: uncommitted changes - not applied
  main unchanged: yes
  local edit preserved: yes

Checkout on a non-default branch
  sync-clone: unlanded work in progress - not applied
  checked-out branch unchanged: yes

Conflicting upstream and local updates
  conflict-clone: CONFLICTS in 1 file - not applied
  main unchanged: yes
  working tree clean after refusal: yes
Evidence: Landing remote comparison

Source: Landing remote comparison

Scenario: remap a fork checkout onto its landing remote

Previous behavior from base commit
  exit code: 1
  error: no-mistakes init failed, so its stored PR target would still name the previous remote and the pipeline would open PRs there. The checkout was restored, so re-run apply once the cause is fixed
  Tool calls:
  gh repo set-default origin
  no-mistakes --yes init
  origin restored to parent: yes

Current behavior
landing-remote: origin now points at the landing remote
origin=file:///tmp/tmp.oIDFOoM1s5/landing-ours.git
upstream=file:///tmp/tmp.oIDFOoM1s5/landing-parent.git
fork=absent
gh-default=origin

Tool calls made by apply
  gh repo set-default origin
  no-mistakes init

Persisted repository settings
  origin=file:///tmp/tmp.oIDFOoM1s5/landing-ours.git
  upstream=file:///tmp/tmp.oIDFOoM1s5/landing-parent.git
  checkout.defaultRemote=origin
  remote.pushDefault=origin
  gh default=base

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.

  • bash tests/fm-sync-axi.test.sh
  • bash tests/fm-landing-remote.test.sh
  • bash ~/.no-mistakes/evidence/01M1ZH1EQSKEX5G9BB22ZWRDN4/run-cli-evidence.sh
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

sync_repo() scanned every local branch and refused the whole repository when
any branch tip was not an ancestor of the default branch. Every repository in
this fleet squash-merges, and a squash rewrites a branch's commits into one new
commit on the default branch, so a squash-merged branch tip is never an
ancestor of it however completely the content landed. Every task branch read as
unlanded forever and no repository ever synced.

The scan guarded a danger that does not exist: sync_repo() only rebases or
merges the default branch of the real checkout, plus a detached scratch
worktree it removes afterwards. It never checks out, moves, resets, or deletes
any other local branch, so those branches keep their commits either way.

Remove the scan and record that branch-scope guarantee in the header and usage
text. Every other guard is unchanged: dirty working trees, checkouts parked on
a non-default branch, fetch failures, and the scratch-worktree conflict trial.
refresh_no_mistakes() ran `no-mistakes --yes init`. `--yes` is a root-local
flag for the first-run setup wizard, not a persistent one, so the CLI rejects
it with "unknown flag: --yes" before init runs. refresh_no_mistakes() therefore
always returned failure and apply restored the old remotes instead of remapping
them.

`no-mistakes init` accepts only --fork-url and --worktree-root, so call it
bare. The test now pins the stub call to an exact bare `init` line, which still
refutes --fork-url along with every other extra flag.
…force their automatic timeout through the contained runner. The macOS workflow now trusts the snapshot test's exit status instead of a stale pass count. Verified the focused tests, full ShellCheck and actionlint, Bash syntax, and diff formatting
The validation pipeline's CI stage committed three changes this task never
asked for, to work around two failures that already exist on the base branch.

bin/fm-test-run.sh and tests/fm-test-run.test.sh are reverted to the base.
That change rewrote the per-script timeout mechanism and removed the sourcing
of bin/fm-timeout-lib.sh, which exists and is used across bin/. Nothing about
the two fixes in this branch required it.

.github/workflows/ci.yml keeps its snapshot/fleet-view count assertion, which
the pipeline had deleted in favour of a bare run. Deleting a count guard to
turn a job green removes the protection the guard exists to give. The expected
count moves from 15 to 16 because commit 495ce74 added a sixteenth test to
tests/fm-fleet-snapshot-view.test.sh without updating this number; nothing in
this branch changes that count.

The unenforced per-script timeout is a separate defect on the base branch and
is left for its own task, not smuggled in here.
@BohnBawerick BohnBawerick changed the title fix(bin): restore clone sync and landing remote refresh fix(sync-axi): allow updates alongside local branches Sep 8, 2026
…plete remote worker tree before deleting its fixture, removing the `worker.lock` cleanup race. The focused test passed once normally and 12 concurrent times under load. Bash syntax, ShellCheck, and `git diff --check` passed
@BohnBawerick
BohnBawerick merged commit 9b85fa9 into main Sep 8, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant