Twinned from https://github.com/cfsh/ninefold/issues/518 by tools/beads/import_github.py's reconcile pass.
rebuild_f5.py drives the session's own checkout — it moves HEAD to f5/integration, merges every open PR there, builds, imports, and hands HEAD back. Almost every sharp edge that keeps getting filed against this script is a consequence of that one fact, and each has been fixed separately at the symptom.
Aedan asked for this shape on #503 after approving the sweep-and-warn fix:
> I think worktree option is better long-term. Since dev-a/b/c live in long-lived clones of the repo, they could make a worktree just for rebuilding f5 and it wouldn't need a full clone each time right, so it would be expensive on setting up a new dev-x, but cheap on incremental IIUC?
Filing it as its own issue because it currently lives only in a comment on a closed issue, which is the reliable way to lose it.
ninefold-cYes, and cheaper than expected. git worktree add shares the object store, so nothing is re-cloned:
| | size | duplicated by a worktree? |
|---|---|---|
| .git | 235 MB | no — shared |
| tracked tree | 1238 files, ~1 MB | yes (trivial) |
| .godot/ import cache | 235 MB | yes — generated once, then incremental |
| .godot/mono build output | 9 MB | yes |
So: ~1 MB of real checkout, plus a second Godot import cache that is generated on first run and incremental thereafter. Exactly the "expensive to set up a new dev-x, cheap incrementally" shape guessed on #503 — and the expensive part is a one-time import, not a clone.
| | today | with a worktree |
|---|---|---|
| #503 — generated .uid/.import sidecars leak into the session's branch | fixed by a before/after sweep (#505) | impossible — different tree |
| #512 item 1 — rerere's git add -A commits, then destroys, the session's untracked scratch files | open | impossible — the session's tree is never touched |
| #512 item 3 — a failed baseline git status inverts the sweep into "delete everything untracked" | open | the sweep stops existing |
| HEAD parked on the throwaway branch (below) | partially guarded | impossible — HEAD never moves |
The sweep, the before/after baseline, bail_to_start, hand_back, and the start_ref(git) == F5 guard inside sweep_artifacts all exist *only* to manage the blast radius of borrowing the session's checkout. A worktree deletes that whole category rather than hardening it.
hand_back is a no-op when the ref you started on is f5/integration itself:
```python def hand_back(git: Git, ref: str) -> int: if ref and ref != F5: # <-- silently skipped when ref IS f5 ... ```
That state is not exotic, it is the *documented workflow*: an unrecorded conflict deliberately parks you on f5/integration so you can resolve it in place. You resolve, commit, re-run — and now start_ref is f5/integration, so the successful run "hands HEAD back" to the throwaway branch and says nothing. The ⚠ STILL ON f5/integration warning only fires when a hand-back to a *different* ref fails, which is the one path this isn't.
It bit me twice today on #511: two commits of feature work landed on f5/integration, and the only symptom was git push failing with *"upstream branch does not match"* — because f5/integration tracks origin/main. Build and tests both passed on the wrong branch. Recovered by cherry-picking, but the next rebuild would have destroyed them silently.
The script's own docstring says this class already cost a full review round (#348/#375). That was a *failed* hand-back; this is a *skipped* one — a different entry into the same hole.
Cheap standalone mitigation if the worktree is not taken soon: warn (don't checkout) when ref == F5, reusing the existing message. Currently the quietest outcome is the most dangerous one.
rebuild_f5 cannot converge on a delete/modify conflict; that is a rerere problem, unrelated to which tree it runs in.paused label vs poll.py's wrap-up predicate, and the "No open PRs" line when everything is paused.f5.sh is unaffected and should stay as-is: it deliberately moves *Aedan's* checkout onto f5/integration to test-drive, which is the point of it. Only the rebuild side moves.rebuild_f5.py (rebuild_f5.sh is a shim since #492)..gdignore is not enough for a nested checkout (cf. #372's nested-checkout scan problem).git worktree prune / re-create path for when the worktree is missing, stale, or left mid-merge — that replaces the current "resolve in place then re-run" flow, and the conflict-resolution UX is the part worth designing rather than porting: the operator now has to cd somewhere to resolve.verify() runs build + tests + headless import; all three need to run in the worktree, so every path assumption in rebuild_f5.py wants an explicit root rather than cwd.Filed unclaimed.
None.
No comments.