diff --git a/docs/plans/2026-10-14_clone-fetch-off-branch.md b/docs/plans/2026-10-14_clone-fetch-off-branch.md new file mode 100644 index 000000000..2be178820 --- /dev/null +++ b/docs/plans/2026-10-14_clone-fetch-off-branch.md @@ -0,0 +1,101 @@ +# A role clone off the root's branch still gets fetched (2026-10-14) + +Filed by Sage on Mos's report of 2026-10-14. Gitea #1611. + +## Fetch a role clone that is on another branch or dirty + +### Problem + +At each launch the host updates a role's project clone only when the clone +is clean and on the root's branch (`packages/seat/src/checkout.mjs`, +`update()`). Every other clone is kept, and the host runs no more git in it. +For `other-branch` and `dirty` that is more than safety needs. By then +`unsafe()` has passed, the index has no gitlink and the root's branch is +known, so the host's fetch is as safe there as in a clean clone. Skipping it +leaves the clone's `refs/remotes/origin/*` as old as the last launch that +found the clone clean and on the root's branch. + +A coder sits on a work branch for most of a task, so this happens all the +time. On 2026-10-14 the coder was blocked on 19/266 because its clone's +`origin/refactor` was 0c3826910, several landings behind. That clone was +also kept for `config-changed`, from a `[user]` section written at 19:53Z +on 10-13, before row 104 (#1598) gave sessions their identity through the +environment. The coder removes that section itself as a one-off. It isn't +part of this row, and `config-changed` stays as it is. + +### Owner and reviewer + +- Owner: darkwing, before row 116. +- Reviewer: dewey (decision 130: the host runs git in a clone the session + can write). + +### Files owned + +- `packages/seat/src/checkout.mjs` +- `packages/seat/tests/checkout.test.mjs` +- `packages/seat/README.md`, "The project clones" +- `packages/cli/src/launcher.mjs`, the record line after `ensureCheckouts` +- `packages/cli/tests/launcher.test.mjs` +- `packages/cli/README.md`, the checkout paragraph in "Sessions" +- `packages/harness/src/bundle.mjs`, `checkoutLines()` +- `packages/harness/tests/bundle.test.mjs` + +Nothing in `unsafe()`, `allowed()`, `gitEnv()`, the `SAFE` arguments or the +sandbox changes. If the fix seems to need any of them, stop and tell Sage. + +### What ships + +1. In `update()`, a clone that would be kept for `other-branch` or `dirty` + gets the same fetch the clean path runs, with the same arguments and + environment, and no merge. Only those two reasons fetch. Every check + before them (`unsafe()`, the gitlink check, the root read) still keeps + the clone with no fetch, as today. A detached HEAD is `other-branch` and + fetches too. +2. The result is a new outcome, `fetched`, with: + - `reason`: `other-branch` or `dirty`; + - `branch`: the clone's own branch, or `null` on a detached HEAD; + - `commit`: the clone's HEAD (`null` when git can't read it); + - `origin`: `{ branch, commit }`, the root's branch and the commit of + `refs/remotes/origin/` after the fetch (`null` when unreadable). + A fetch that fails is `kept` with `fetch-failed`, as today. If you find a + better shape than a new outcome, record the choice and the reason in the + packet. +3. The launcher writes a `checkout-fetched` line to the launch log, with the + project and the reason, next to `checkout-kept`. +4. The bundle's prompt names a fetched clone's branch and commit, says it + wasn't fast-forwarded and why, and names `origin/` at its commit. + The shared sentence changes to say the host fetches every clone it can + safely run git in at a launch, and fast-forwards only a clean clone on + the root's branch. The rest of the sentence (never during a session, + don't set git config, identity from the environment) stays. +5. The READMEs say the same. The seat README's table gets the `fetched` + row, and the reason table says which reasons fetch. + +### Not in this row + +- Accepting `user.name` or `user.email` in a clone's config. Refusing them + is deliberate (row 104). +- Merging, rebasing or resetting anything in a fetched clone. +- Fetching a clone kept for any other reason. + +### Acceptance + +- For a dirty clone (modified, untracked, staged) and one on another branch + or detached: after a commit to the root, the launch returns `fetched`, + `refs/remotes/origin/` is the root's new commit, and HEAD, + `refs/heads/*`, the index and every file outside `.git` are byte-for-byte + what they were. The existing "kept as they are" test becomes this test, + with its fingerprint narrowed to those parts. +- A clone kept for each earlier reason (`not-a-clone`, `hooks-present`, + `submodule`, `config-changed`, `root-unreadable`, `root-detached`) still + runs no git, the fetch included. The rigged-clone test still passes + unchanged. +- A failed fetch in a dirty clone is `kept`, `fetch-failed`. +- The launcher test sees `checkout-fetched` with the reason, and the bundle + test sees the new line. +- Mutants, one per check, listed in the packet: fetch for a third reason; + merge after the fetch; drop the fetch; report the wrong `origin` commit; + fetch before the gitlink check. Each one fails a test. +- Full gate green. Dewey approves on #1611 naming the candidate manifest. + Sage reruns the gate at the landing target. Going live needs a re-pin on + Mos's go (decision 109).