Skip to content

fix: guard firstmate against primary worktree tangles#83

Merged
kunchenguid merged 5 commits into
mainfrom
fm/tangle-guard-g5
Jun 25, 2026
Merged

fix: guard firstmate against primary worktree tangles#83
kunchenguid merged 5 commits into
mainfrom
fm/tangle-guard-g5

Conversation

@kunchenguid

@kunchenguid kunchenguid commented Jun 25, 2026

Copy link
Copy Markdown
Owner

Intent

Add two guards to firstmate that prevent and immediately detect a 'worktree tangle': the failure mode where a crewmate spawned to work on the firstmate repo itself branches/commits in the PRIMARY firstmate checkout (the repo root firstmate operates from) instead of its assigned disposable treehouse worktree, stranding the primary on a feature branch. This targets only the firstmate-on-itself case (projects have no such exposure).

Architecture insight that shapes the design: firstmate is a treehouse-pooled git repo of itself - the primary checkout AND every crewmate worktree / secondmate home are all linked git worktrees of one repo, and the primary is itself a writable linked worktree normally on the default branch (main). Therefore BRANCH NAME is the only valid discriminator: a named non-default branch checked out in the primary is the tangle; the default branch and detached HEAD (the legitimate resting state of worktrees and secondmate homes) are healthy. A 'linked-worktree vs main-worktree' test would be WRONG here because it would exempt the primary itself, so it was deliberately not used. The default branch name is resolved (origin/HEAD then main/master), not hardcoded.

GUARD 1 (prevention, spawn-time): (a) bin/fm-brief.sh - the ship-brief Setup opens with a worktree-isolation assertion ahead of the 'git checkout -b' step; the crewmate confirms via git rev-parse --show-toplevel (and a git-dir != git-common-dir linked-worktree test) it is in its own treehouse worktree, and if it resolves to the primary it STOPS and reports 'blocked: launched in primary checkout, not an isolated worktree'. (b) bin/fm-spawn.sh - after treehouse get resolves the worktree (WT), it asserts WT is a genuine isolated worktree distinct from the primary checkout (PROJ_ABS) with git -C WT rev-parse --show-toplevel canonicalizing to WT; otherwise aborts the spawn.

GUARD 2 (detection, immediate): new shared bin/fm-tangle-lib.sh provides fm_default_branch and fm_primary_tangle_branch. bin/fm-guard.sh emits a bordered alarm (like the watcher-down banner) when the primary (FM_ROOT) is on a feature branch, placed before the in-flight early-exit so it fires on the next fleet action; bin/fm-bootstrap.sh prints the same as a 'TANGLE:' problem line at session start.

Tests: new tests/fm-tangle-guard.test.sh covers lib classification, the guard banner, the bootstrap line, brief assertion ordering, and the spawn abort, hermetic over temp git repos/fakebins. tests/fm-bootstrap.test.sh and tests/fm-watcher-lock.test.sh now pin FM_ROOT so the new alarm stays inert there (the ambient checkout runs on a feature branch in CI).

Docs: AGENTS.md sections 3, 7, 8, 11 document both guards. Deliberately did NOT touch README.md, docs/, or CONTRIBUTING.md (a separate restructure is in flight there; changes are disjoint). Scope is firstmate-repo tooling only.

Note: a prior run was aborted; the only delta is a shell-lint fix - relocating '|| true' outside the $(cd && pwd -P) substitutions in fm-spawn.sh and converting an 'A && B || fail' and a bare fm_git_identity call in the new test to SC2015/SC2119-clean forms - so the repo CI shellcheck (stricter than the pipeline lint) passes. No behavior change; full local suite (13/13) green.

What Changed

  • Added shared tangle detection helpers plus bootstrap and guard alarms when the firstmate primary checkout is stranded on a non-default branch.
  • Hardened ship dispatch by asserting crewmates are launched in isolated treehouse worktrees, with brief instructions that stop before branching if the primary checkout is detected.
  • Added hermetic tangle guard coverage and updated related bootstrap and watcher-lock tests and docs.

Risk Assessment

✅ Low: Captain, the change is narrowly scoped to spawn/bootstrap/guard shell paths with focused coverage, and I found no material risks in the diff.

Testing

Captain, I exercised the new tangle guard tests, the adjacent bootstrap and watcher-lock suites, and a manual E2E transcript showing silent healthy states, the guard banner, the bootstrap TANGLE line, brief ordering, spawn refusal in the primary checkout, and allowed spawn into an isolated worktree; the worktree was clean afterward.

Evidence: Tangle guard E2E CLI transcript
Firstmate worktree-tangle guard E2E evidence
repo under test: /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01KVZY23DPJ2R136369KQ4FQGH

== Setup: hermetic primary checkout on main ==
primary=/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/primary
branch=main

== Detection stays silent on default branch ==
fm-guard output: <silent>

== Detection stays silent on detached HEAD ==
fm-guard output: <silent>

== Detection alarms when primary is stranded on a feature branch ==
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  WORKTREE TANGLE - PRIMARY CHECKOUT IS ON A FEATURE BRANCH
●  /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/primary is on 'fm/tangle-evidence', not its default branch 'main'.
●  A crewmate likely branched/committed in the primary instead of its own worktree.
●  The work is SAFE on the 'fm/tangle-evidence' ref. Restore the primary to 'main':
●      git -C /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/primary checkout main
●  then re-validate 'fm/tangle-evidence' in a proper isolated worktree.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

== Bootstrap reports the same TANGLE remediation line ==
TANGLE: primary checkout on feature branch 'fm/tangle-evidence' (expected 'main'); the work is safe on that ref - restore the primary with: git -C /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/primary checkout main, then re-validate the branch in a proper worktree

== Ship brief puts isolation assertion before branch creation ==
isolation_assertion_line=11
branch_creation_line=13
You are in a disposable git worktree of alpha, at a detached HEAD on a clean default branch.

**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`; both must resolve to the disposable treehouse worktree you were launched in, typically a path under a `.treehouse/` pool, not the primary checkout firstmate operates from.
The path check is authoritative: `git rev-parse --git-dir` and `git rev-parse --git-common-dir` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append `blocked: launched in primary checkout, not an isolated worktree` to the status file and stop.

1. First action: create your branch: `git checkout -b fm/tangle-brief-e2e`
2. Run `no-mistakes doctor`; if it reports the repo is not initialized here, run `no-mistakes init`.

== Spawn prevention refuses a pane that resolves inside the primary checkout ==
exit_code=1
error: treehouse get did not yield an isolated worktree (resolved '/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/spawn-project/sub'; worktree root '/private/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/spawn-project'; primary '/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/spawn-project'); refusing to launch to avoid tangling the primary checkout. Inspect window firstmate:fm-abort-primary-e2e
meta_file=absent

== Spawn prevention allows a genuine isolated linked worktree ==
warn: no registry at /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/spawn-home/data/projects.md; defaulting spawn-project to no-mistakes off
spawned ok-isolated-e2e harness=codex kind=ship mode=no-mistakes yolo=off window=firstmate:fm-ok-isolated-e2e worktree=/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/spawn-worktree
created_meta:
window=firstmate:fm-ok-isolated-e2e
worktree=/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/spawn-worktree
project=/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/manual-e2e/spawn-project
harness=codex
kind=ship
mode=no-mistakes
yolo=off

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

🔧 **Rebase** - 3 issues found → auto-fixed ✅
  • ⚠️ bin/fm-brief.sh - merge conflict rebasing onto origin/fm/tangle-guard-g5
  • ⚠️ bin/fm-spawn.sh - merge conflict rebasing onto origin/fm/tangle-guard-g5
  • ⚠️ tests/fm-tangle-guard.test.sh - merge conflict rebasing onto origin/fm/tangle-guard-g5

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • TMPDIR=/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/tmp bash tests/fm-tangle-guard.test.sh
  • TMPDIR=/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/tmp bash tests/fm-bootstrap.test.sh
  • TMPDIR=/var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/tmp bash tests/fm-watcher-lock.test.sh
  • /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH/run-tangle-guard-e2e.sh /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01KVZY23DPJ2R136369KQ4FQGH /var/folders/5x/4nqprlbx0518k3ybcb1sz6gr0000gn/T/no-mistakes-evidence/01KVZY23DPJ2R136369KQ4FQGH
  • git status --short
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Firstmate is a treehouse-pooled git repo of itself: the primary checkout
and every crewmate worktree / secondmate home are linked worktrees of one
repo. A crewmate sent to work firstmate-on-itself can branch and commit in
the PRIMARY checkout instead of its own isolated worktree, stranding the
primary on a feature branch (e.g. fm/readme-restructure-d3). Add two guards:

Prevention (spawn-time):
- fm-brief.sh: ship-brief Setup now opens with a worktree-isolation
  assertion ahead of the branch step. The crewmate confirms it is in its
  own treehouse worktree, and stops with
  `blocked: launched in primary checkout, not an isolated worktree`
  if it resolves to the primary.
- fm-spawn.sh: after treehouse get, refuse to launch unless the resolved
  worktree is a genuine isolated worktree root distinct from the primary
  checkout (PROJ_ABS); abort with a clear error otherwise.

Detection (immediate):
- bin/fm-tangle-lib.sh: shared classifier. A NAMED non-default branch in
  the primary is the tangle; the default branch and detached HEAD (the
  legitimate state of worktrees and secondmate homes) stay silent.
- fm-guard.sh: bordered alarm, in the watcher-down banner's spirit, on the
  very next fleet action when the primary is on a feature branch.
- fm-bootstrap.sh: same assertion at session start as a TANGLE: line.

Tests: tests/fm-tangle-guard.test.sh covers the lib classification, the
guard banner, the bootstrap line, brief assertion ordering, and the spawn
abort. fm-bootstrap and fm-watcher-lock guard tests now pin FM_ROOT so the
ambient (feature-branch) checkout never leaks a TANGLE line into them.

Docs: AGENTS.md sections 3, 7, 8, 11 document both guards.
@kunchenguid kunchenguid merged commit 10850ad into main Jun 25, 2026
4 checks passed
@kunchenguid kunchenguid deleted the fm/tangle-guard-g5 branch June 25, 2026 18:12
leo1oel added a commit to leo1oel/nemo that referenced this pull request Jun 26, 2026
…unchenguid#83) (#6)

Port two functional upstream fixes, adapted to the herdr/Claude-only fork.

kunchenguid#88 fm-send settle: after a successful text submit, pause FM_SEND_SETTLE
seconds (default 1, 0 disables) before returning. A cleared composer only
proves the text was submitted; the harness needs a beat to spin up the turn
before its busy footer appears, so an immediate peek would otherwise see the
stale idle pane (the fm-send false-positive symptom). Scoped to the text path
only; the shared submit core the away-mode daemon uses never pays it.

kunchenguid#83 worktree-tangle guard: firstmate is a git repo of itself (secondmate homes
and self-on-self crewmate worktrees are linked herdr worktrees of FM_ROOT), so
a crewmate can branch/commit in the PRIMARY checkout instead of its isolated
worktree, stranding the primary on a feature branch.
- bin/fm-tangle-lib.sh: pure-git classifier (named non-default branch in the
  primary is the tangle; default branch and detached HEAD stay silent).
- fm-guard.sh: bordered ●-marked alarm on the next fleet action, checked first
  and independent of in-flight tasks.
- fm-spawn.sh: refuse to launch unless the opened worktree is a genuine isolated
  worktree root distinct from the primary checkout.
- fm-brief.sh: ship briefs open with a worktree-isolation assertion before the
  branch step.
- AGENTS.md sections 7/8/11 document both guards.
- tests/fm-tangle-guard.test.sh (new) covers lib classification, the guard
  banner, brief assertion ordering, and the spawn abort; fm-wake-queue guard
  tests pin FM_ROOT so the ambient feature-branch checkout cannot leak a banner.
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