Repository navigation
fix(worktrees): refuse a submodule checkout before stopping its terminals - #35458
Conversation
Walkthrough
Priority: Merge Risk:
|
[Medium impact] The PR appears safe to merge; no actionable failures were found. SummaryMoves Git's submodule refusal into the check that runs before terminals stop.
No actionable issues were found. Tests were inspected, not run.
|
…nals Git refuses `git worktree remove` without --force for any checkout with submodules, even when nothing is dirty. Orca's dirty check passed such a clean checkout, so the delete was accepted, its terminals were stopped, and Git's refusal arrived later in the background, where the CLI and automation never saw it. The dirty check now applies Git's own test (submodule repositories under the worktree's admin directory, or a gitlink checked out with its .git), which the native Windows path already used, and refuses a clean checkout that fails it with a message that keeps Git's wording, so every app version still offers Force Delete with the submodule warning. The same test now decides when a dirty refusal names submodules. Force is not applied automatically: it would also delete submodule commits, branches and stashes that were never pushed. Refs #35135
09adb8b to
d150cfd
Compare
There was a problem hiding this comment.
Actionable comments posted: 1

Review info
-
Run configuration-
- Source mode: Auto
- Repository: Unavailable
- Branch: Unavailable
- File: No configuration file from this repository applied
- Commit: Unavailable
- Layers: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
86243f48-fb54-42a1-b4f1-af492b08b34c
Files selected for processing: 4-
src/main/git/remove-worktree-clean-preflight.test.tssrc/main/git/worktree-removal-preflight.tssrc/main/git/worktree-removal-real-git.test.tssrc/shared/worktree/removal.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 1 remain after this review.
-
| if (adminDirectory && !(await isWorktreePathMissing(join(adminDirectory, 'modules')))) { | ||
| return true | ||
| } | ||
| const { stdout: index } = await gitExecFileAsync(['ls-files', '--stage', '-z'], execution) |
There was a problem hiding this comment.
Keep large-index probe failures from reaching teardown.
If a populated gitlink has no <adminDirectory>/modules directory and ls-files --stage -z emits more than 10 MiB, gitExecFileAsync rejects the output. The caller converts that failure to false. A clean worktree can then lose its terminals before git worktree remove refuses the populated submodule. Use a bounded probe that can find a gitlink without capturing the full index, or refuse deletion when this probe exceeds its output limit. (raw.githubusercontent.com)
ELI5
Git won't delete a workspace that has submodules unless you force it, even when nothing in it has changed. Orca used to find that out too late: it had already stopped the workspace's terminals and told the caller the delete was underway, and Git's refusal then happened in the background, where the CLI never saw it. Orca now runs Git's own check first and refuses up front, before anything is stopped.
What Changed
git worktree removefailed in the background with "working trees containing submodules cannot be moved or removed". The CLI (orca worktree rm) and automation never saw that error, and every retry stopped the terminals again.assertWorktreeCleanForRemoval) applies Git's own submodule test: submodule repositories under the worktree's admin directory, or an index gitlink checked out with its.git. The native Windows path already used this test. A clean checkout that fails it is refused before teardown. The same test now decides when a dirty refusal is labelled as containing submodules, replacing a probe based on.gitmodulesthat missed submodules which were deinitialized but still have a repository under the admin directory.resolveGitDirandresolveWorktreeHostPath, so WSL guest paths resolve the same way as elsewhere. If the probe itself fails anywhere except native Windows, Git decides, as before.Why
The refusal belongs with the other refusals that run before teardown. That is what the acceptance contract promises: everything that can refuse happens before terminals stop and before the host answers. Forcing automatically was rejected on purpose: #28878 removed exactly that, because
--forcealso deletes submodule commits, local branches and stashes that were never pushed.The CLI reply contract (
removed: truebefore deletion, failures only in the host log) is fixed separately in the companion PR for #35135.Linked Issue
Refs #35135
Thanks to MrClean73 for the precise report and the plain-git reproduction.
Visual Proof
N/A. No new UI: the existing submodule Force Delete toast now appears before teardown instead of after the background delete fails.
Testing
Reproduced with plain git (Git on macOS): a clean linked worktree with an initialized submodule passes
git status --porcelain --ignore-submodules=nonewith no output, thengit worktree removefails with rc=128. A deinitialized submodule whosemodulesdirectory remains is refused the same way.New real-Git cases in
src/main/git/worktree-removal-real-git.test.ts(checked-out submodule, and deinitialized submodule with its repository still in the admin directory) fail on origin/main and pass here. Each also asserts that Git itself refuses the checkout.Removal preflight, IPC removal and Windows removal suites pass.
pnpm check:changed --maxWorkers=2: everything passes except tests that also fail on origin/main on this machine (worktree-create-preparation-real-gitpartial-checkout abort, andelectron-serve-browser-process, which trips the real-home write guard). Neither touches this change.Not run: native Windows or a WSL distro. The Windows path keeps its existing check, and the probe path is the same on every OS.
I manually tested these changes locally
Automated tests added/updated, or explained why not below
Review
Agent skill upstream boundary
Notes
git ls-files --stageaftergit statusper non-forced delete, plus one stat call.Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred)