fix(repo): pin a --repo worktree to the fetched revision, refuse an unknown base - #83
Merged
Merged
Conversation
…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
This was referenced Aug 20, 2026
AetherAI3
added a commit
that referenced
this pull request
Aug 22, 2026
…notes claim On 2026-08-20 this repository described five different releases at once. package.json said 0.2.0. package-lock.json still said 0.1.0 — the #81 bump never touched it. RELEASE_NOTES.md's top entry described a 0.2.0 whose feature list was written at 477f0fc, before #72 added `aether skills`, `aether capabilities` and `aether support-bundle`. The npm registry served 0.1.0 and nothing else. And a packed aether-agents-0.2.0.tgz sat in the repo root from #83 until #90 deleted it, containing a build with no skills runtime in it at all. Nothing was lying on purpose. The four statements drifted apart because nothing compared them. 0.2.0 -> 0.3.0, not 0.2.1 ------------------------- #72 is a backwards-compatible feature addition — a skills runtime, three new CLI commands, six built-in skills shipped inside the package — that the v0.2.0 notes never mentioned. Semver makes that a minor bump. Reusing 0.2.0 was rejected on identity grounds. 0.2.0 already names a specific dated artifact: the August 19 notes entry, the README availability paragraph, install.sh's pin example, and that committed tarball. Publishing different contents under the same version would make two materially different artifacts answer to one name, which is the defect this release exists to close. 0.2.0 is retired unused; it will never be published. Reconciled ---------- - package.json, package-lock.json (both version fields) and src/version.ts all say 0.3.0. - RELEASE_NOTES.md leads with v0.3.0 covering 477f0fc..ed094dc: one feature, nine user-visible fixes, three test-only commits, one unwired module (#86 ship rail, which no command invokes), one docs commit. The v0.2.0 entry is marked superseded rather than rewritten — it is the record of what was written that day, not an install instruction. - README and the release log name 0.3.0, and both still say plainly that `npm i -g aether-agents` gives you 0.1.0, because it does. New gate: test/release_coherence.test.ts ---------------------------------------- Gate A asserts every place that names the release names the same one — including both package-lock version fields, which is the drift nothing was watching. Gate B is the load-bearing one: for every feature the release notes claim, the code behind it must be present in the file list `npm pack` would actually ship, and the command exposing it must be in the CLI registry. The source checkout's dist/ is NOT the package — the allowlist is dist/src plus four docs. Mutation-checked. Adding "!dist/src/commands/skills.js" to the files allowlist silently drops `aether skills` from the tarball: verify:production -> {"ok":true, ... "packedFiles":523} MISSED IT release_coherence -> FAIL: dist/src/commands/skills.js (agent skills runtime — `aether skills`) Restored: 7/7 pass, 524 packed files. New: npm run release:candidate ------------------------------ release.yml only runs after a founder has already tagged and published, so everything it checks is checked too late to change the decision. This runs the same sequence, in the same order, before the tag exists — against a detached git worktree of a specific commit, never the dirty checkout. A dirty tree is refused unless --allow-dirty, which marks the report commitBound:false rather than pretending. Every CLI proof runs the binary npm installed from the packed tarball into a clean prefix: --version, --help, `skills list`, `capabilities`, and the handoff demo (the demo harness is not shipped, so it is placed beside the installed package and resolves the CLI and its imports from the tarball's own dist/src). Skipped steps are recorded as "not-run" with a reason and can never read as a green tick — the full npm test suite is release.yml's gate and says so in the report. No string is handed to a shell: npm is reached through its own JS entrypoint under node, so every argument stays an argument. Also ---- - scripts/verify-production.ts exports createPackReport, so a gate reasoning about what a user receives asks npm instead of reading the build directory. - test/release_canaries.test.ts's coverage map said canaries 5 and 7 were unwritable. #86 and #87 wrote them two commits later; the excuse outlived the condition. The map is now enforced by a test that reads the delegated files. - docs/releases/OPERATOR-PACKET-v0.3.0.md: the founder-owned publish sequence, with what is proven and what is explicitly not. Gates at this commit: npm run typecheck exit 0 release + production test files 21 pass / 0 fail npm run verify:production --tag v0.3.0 ok Full npm test NOT run locally (a tool_executor test does not return on this Windows box); CI is the authority. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Problem
--repocould 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.remoteTipexplicitly" as a follow-up. That follow-up is load-bearing.git fetchadvances remote refs andFETCH_HEAD. It does not move the mirror's checked-out HEAD —refreshMirrorsays so in its own contract: "never checks out, resets, merges, pulls or cleans."And
worktreeAddArgsbuilt:code.tsreadco.freshness.remoteTiponly to print it.The reachable shape:
The summary this project shipped — "
--repofetches instead of branching off a stale copy" — was true of the fetch and false of the worktree.Contract
1. The start point is explicit.
worktreeAddArgsandcreateWorktreetake an optionalstartRevision, appended last so git reads it as the start point. Omitted, behaviour is unchanged — a plain--worktreerun 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:
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 toC, fetches, then:FETCH_HEAD == CHEADis stillA— this is the defect itself, pinned as a fixturegit rev-parse HEADinside the worktree to equalC, and to not equalAMutation-checked, and this is the evidence the bug was real: reverting
worktreeAddArgsto drop the start point fails the canary withthe worktree must start at the fetched revision— the worktree lands onA. Restored: 19/19.npm run typechecknpm testWhy 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--worktreerun, and no change to the fetch/validation contract from #73.Credit
Found in review of merged
mainat37fff3c, 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.