Skip to content

fix(repo): pin a --repo worktree to the fetched revision, refuse an unknown base - #83

Merged
AetherAI3 merged 1 commit into
mainfrom
fix/repo-exact-remote-base
Aug 19, 2026
Merged

fix(repo): pin a --repo worktree to the fetched revision, refuse an unknown base#83
AetherAI3 merged 1 commit into
mainfrom
fix/repo-exact-remote-base

Conversation

@AetherAI3

Copy link
Copy Markdown
Owner

Problem

--repo could report a freshly fetched base and then start work days behind it.

PR #73 added remote validation and a fetch, and recorded "pinning the worktree to freshness.remoteTip explicitly" as a follow-up. That follow-up is load-bearing.

git fetch advances remote refs and FETCH_HEAD. It does not move the mirror's checked-out HEADrefreshMirror says so in its own contract: "never checks out, resets, merges, pulls or cleans."

And worktreeAddArgs built:

["-C", repoRoot, "worktree", "add", "-b", branch, dir]   // no start point

code.ts read co.freshness.remoteTip only to print it.

The reachable shape:

mirror HEAD   A
origin/main   A-B-C
FETCH_HEAD    C
printed       "(fetched) @ C"
worktree cut  A          ← days behind what was just reported

The summary this project shipped — "--repo fetches instead of branching off a stale copy" — was true of the fetch and false of the worktree.

Contract

1. The start point is explicit. worktreeAddArgs and createWorktree take an optional startRevision, appended last so git reads it as the start point. Omitted, behaviour is unchanged — a plain --worktree run still branches from the user's own checkout, which is the intended base there.

2. An unnameable base is refused, not warned about. Previously an unfetchable mirror printed a warning and carried on. Proceeding is the dangerous half: it starts work on an unknown base immediately after printing a reassuring line. Now:

✗ refusing to start: the base for owner/repo is not known to match the remote.
  Could not resolve host: github.com
  a worktree cut now would branch off whatever the mirror already had.
  reconnect and retry, or work in a local checkout you control.

Exit 1. No worktree, no run.

Tests

Three argv tests, plus a real-git canary that asserts the resulting checkout rather than the arguments — because arguments were never the thing in doubt.

It builds a remote at A, clones it, advances the remote to C, fetches, then:

  • asserts FETCH_HEAD == C
  • asserts the mirror's HEAD is still Athis is the defect itself, pinned as a fixture
  • cuts the worktree
  • requires git rev-parse HEAD inside the worktree to equal C, and to not equal A

Mutation-checked, and this is the evidence the bug was real: reverting worktreeAddArgs to drop the start point fails the canary with the worktree must start at the fetched revision — the worktree lands on A. Restored: 19/19.

command result
npm run typecheck exit 0
npm test 1090 pass / 0 fail

Why refuse rather than warn

Fail-open was defensible while the base was merely unverified. It is not defensible once we know the worktree silently starts from a stale commit — the failure is invisible, the output actively reassuring, and the resulting branch is what a user then opens a PR from.

If an override is wanted later it should be an explicit flag with its own name, not the absence of a check.

Scope

src/core/worktree.ts, src/commands/code.ts, test/worktree.test.ts. No change to a plain --worktree run, and no change to the fetch/validation contract from #73.

Credit

Found in review of merged main at 37fff3c, not by this project's own tests — which is worth recording, because #73's tests asserted the argv and the freshness report, and both were correct. Nothing asserted the commit the worktree actually landed on. That gap is what the new canary closes.

…nknown base

PR #73 added remote validation and a fetch, and recorded "pinning the worktree
to freshness.remoteTip explicitly" as a follow-up. That follow-up turns out to
be load-bearing, not cosmetic.

`git fetch` advances remote refs and FETCH_HEAD. It does NOT move the mirror's
checked-out HEAD — refreshMirror says so in its own contract ("never checks
out, resets, merges, pulls or cleans"). And worktreeAddArgs built:

    ["-C", repoRoot, "worktree", "add", "-b", branch, dir]

with no start point, so `worktree add` branched off whatever the mirror already
had. code.ts read co.freshness.remoteTip only to print it.

The reachable shape was:

    mirror HEAD   A
    origin/main   A-B-C
    FETCH_HEAD    C
    printed       "(fetched) @ C"
    worktree cut  A

so a run could report a fresh base and then start days behind it. The summary
this project shipped — "--repo fetches instead of branching off a stale copy" —
was true of the fetch and false of the worktree.

Two changes.

1. worktreeAddArgs and createWorktree take an optional startRevision, appended
   last so git reads it as the start point. Omitted, behaviour is unchanged, so
   a plain --worktree run still branches from the user's own checkout.

2. code.ts pins to co.freshness.remoteTip, and REFUSES when the base cannot be
   named. Previously an unfetchable mirror printed a warning and proceeded.
   Proceeding is the dangerous half: it starts work on an unknown base while
   having just printed a reassuring line. A run that cannot establish its base
   now exits 1 and says why.

Tests: three argv tests plus a real-git canary that asserts the resulting
checkout rather than the arguments. It builds a remote at A, clones it, moves
the remote to C, fetches, asserts the mirror's HEAD is still A (the defect
itself), cuts the worktree, and requires `git rev-parse HEAD` inside it to
equal C.

Mutation-checked, and this is the evidence the bug was real: reverting
worktreeAddArgs to drop the start point fails the canary with "the worktree
must start at the fetched revision" — the worktree lands on A. Restored, 19/19.

Gates at this commit:
  npm run typecheck   exit 0
  npm test            1090 pass / 0 fail
@AetherAI3
AetherAI3 merged commit 63aa197 into main Aug 19, 2026
5 checks passed
@AetherAI3
AetherAI3 deleted the fix/repo-exact-remote-base branch August 19, 2026 13:47
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