Run a flow in a git worktree - #166
Merged
Merged
Conversation
The scans looked for worktrees under the repository's main checkout, no matter which checkout the shell was started in. From inside a worktree that meant its siblings were scanned but the main checkout was not — an asymmetry nobody chose. A test named "the shell's own directory is never listed twice" pinned it as a side effect of asserting deduplication. `.orca/` is per-checkout, and a worktree's runs leave their progress log and session manifests in the worktree. So the rule is now the same shape as the storage: a checkout scans itself and the worktrees hanging off its own `.orca/worktrees/`. The checkout that made them sees all of them; a worktree, whose own `.orca/worktrees/` is empty, sees only itself. This drops the main-checkout derivation from the scan, which leaves `mainCheckout`'s two-argument overload with no caller.
When the run's branch has gained commits, orca refuses rather than moving it, and offered three ways out: merge it, delete it, or remove the worktree. The third does not work. The refusal is about the branch, so removing the worktree just makes the next run recreate it detached and hit the same refusal. The message now names `git branch -D <branch>`. The test asserted the refusal mentions the worktree path, so it encoded the same mistake; it now asserts the remedy that works.
The spec said a worktree is created detached and left that way until the feature branch is bound. The code puts it on an orca-worktree-<hash> branch right after creating it, because a detached worktree records the literal HEAD as the run's starting branch, which recovery refuses as an unsafe ref — so an interrupted run could never be resumed. The README and ADR 0018 already said this; only the spec still said the opposite. It also now notes that the branch is never deleted either, so full cleanup is two commands.
The shell asked two yes/no questions: create a branch, then — only if the answer was yes — run in a worktree. That is one decision split in two, with the second question silently disappearing depending on the first. It also left the pair orca refuses (--worktree with --skip-branch) expressible, prevented only by the order the questions were asked in and an early return. Nothing in the types stopped a later edit from reordering them. Now one prompt with three rows: a new branch in this checkout (the default), the branch checked out now, or a new worktree. RunTarget maps each to its flags, and no case sets both, so the refused pair has no representation here. NewBranch leads the rows because ConsoleUiShell cannot honor preselect: the first row is what the cursor starts on, so its position is what keeps Enter doing what it did before.
The §3 amendment still said the scan spans the worktrees orca made for the repository. It is now scoped to the checkout the shell runs in, matching where the data lives.
adamw
added a commit
that referenced
this pull request
Sep 11, 2026
Closes the four doc and test gaps the review of #166 left open. Docs and tests only — no behaviour change. **`Worktrees`' object doc** talked about "the two reads", which the object has outgrown. It now states the rule for all of the reads, and names `mainCheckoutOrReason` as the one that gives a reason instead of a blank "not known". **`WorktreeRun.resolve`'s doc** listed three of the messages a caller can get back. It lists all of them now, and says what each one means. **Two of the three "no main checkout" messages had no test**: the one for a repository whose main worktree is not a checkout (`--separate-git-dir` or a submodule), and the one for a git that does not answer the query. The second is reached with a repository path holding a newline, which makes git's answer unreadable in the same way an old git does. **`headBranch` and `onABranch` answer "not on a branch" when a worktree cannot be read at all**, which nothing tested. The new test deletes the worktree directory first, so git really cannot be run there; it fails if that fail-closed answer is taken out. os-lib prints the failed exec's stack trace from a thread of its own, so the test log shows it while passing — noted in the test. The `--separate-git-dir` repo two tests were building inline is now `GitRepo.seededSeparateGitDir`. `sbt scalafmtAll` and `sbt test` are green: 2239 tests, 0 failures.
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.
Closes #150.
Adds a
--worktreeflag. With it, orca creates a git worktree at startup and runs the whole flow there, so several runs can work on one repository at once without sharing a checkout.The design is in
docs/plans/worktree-runs/(specification and implementation plan), added by this branch.How it works
The worktree goes in
<main checkout>/.orca/worktrees/<promptHash>. The hash is the one that already names the run's progress log, so the same task always gets the same worktree — which is how a resumed run finds its way back. Re-run with the same task text and--worktreeagain.The location is found from git (
--git-common-dir), not from the current directory, so it is the same whether you start in the main checkout or in a worktree..orca/worktrees/gets a self-ignoring.gitignore, like.orca/cache/already has. Without itgit add -Awould stage the worktree as an embedded repo.The run happens on an
orca-worktree-<hash>branch. The worktree is created detached, but cannot be left that way: a detached worktree records the literalHEADas the run's starting branch, which recovery refuses as unsafe, so an interrupted run could never be resumed.Orca removes neither the worktree nor that branch — a failed run's worktree holds the log that resumes it, and a successful one holds the work. Full cleanup is
git worktree remove .orca/worktrees/<hash>andgit branch -D orca-worktree-<hash>. A plaingit clean -xdfskips the worktree.The shell scans per checkout: the checkout that made the worktrees sees itself and all of them, a worktree sees only itself. Run the shell from the checkout to survey every run.
What's refused
--worktree --skip-branchand--worktree --keep-changesare errors. The first asks git to check one branch out twice; the second promises to keep uncommitted files that a worktree does not carry over.In the interactive shell the question cannot produce either pair: "Where should this run's work go?" is one choice of three — a new branch in this checkout (the default), the branch checked out now, or a new worktree.
Things to know
target/, nonode_modules..gitignorewill see the second checkout.Left open by review
WorktreeScancaps the scan at 20 worktrees, ranked by last run. Past the cap a worktree is invisible to the resume offer and the session list. Documented in its scaladoc.TreeMode/RunTargetADT) were declined as too wide for this change. The rule is enforced where the flags are parsed, and the interactive prompt only asks about a worktree when the branch answer was yes.Worktrees' object doc still says "the two reads",WorktreeRun.resolve's doc does not list everyLeft, and two error cases lack tests.