Repository navigation
fix(worktree): offer force removal for submodule worktrees - #60172
DevDengChao wants to merge 2 commits into
Conversation
Git refuses `git worktree remove` for a worktree with initialized submodules unless `--force` is passed, and its error has no force hint. The bulk delete path sent `force: false`, and the server only classified dirty-worktree errors as `forceRequired`, so the app showed a dead-end error. Classify the submodule refusal as `forceRequired` and pin git's message locale to C so the classification works outside English locales. After a bulk delete, list the worktrees git refused and ask once before force-deleting only those; dirty, active, and linked worktrees are still kept. Update the TUI, Code Mode, and docs for the force case. Co-authored-by: opencode-agent[bot] <219766164+opencode-agent[bot]@users.noreply.github.com>
There was a problem hiding this comment.
The change looks sound. I reproduced #60165 on the base through the server API, using the same DELETE /api/worktree call the bulk delete makes: the submodule refusal came back with forceRequired: false. With this PR it comes back with forceRequired: true, and force: true still removes the worktree. The new core test passes, and typecheck:tsc passes in core, app and tui.
I couldn't run the new Playwright test here because the browsers aren't installed, so I only read the Settings bulk-delete flow rather than driving it. It keeps the first pass non-forced, collects only the worktrees git refused after they passed the active/linked/dirty check, and asks once before retrying them with force. Since the PR description only has an illustration, could you add a real screenshot or short recording of the Force delete worktrees? dialog in the app?
Public API: no new or removed endpoints, fields or flags. DELETE /api/worktree now also sets WorktreeError.data.forceRequired: true for the submodule refusal. This is backwards compatible: clients that already retry on forceRequired will now offer force in this case too. The worktree_remove tool's failure text also changes.
Related: #40613
There was a problem hiding this comment.
The new commit only merges dev. This PR's own changes are the same as in my last review. On the merged head, the #60165 reproduction still returns forceRequired: true for the submodule refusal and force: true still removes the worktree. The core git tests pass, and so does typecheck:tsc in core and app. No new concerns. The public API impact is the same as before: DELETE /api/worktree now reports forceRequired: true for this refusal, which is backwards compatible.
Issue for this PR
Closes #60165
Type of change
What does this PR do?
Git refuses
git worktree removefor a worktree whose submodules are initialized unless--forceis passed. The error has no "use --force" hint, and OpenCode only marked dirty-worktree errors asforceRequired, so the desktop bulk delete stopped at the raw error.packages/core/src/git.ts: classify git's "containing submodules" refusal asforceRequired, and pin the worktree subprocess toLC_ALL=Cso the English match also works on non-English locales.packages/app/src/settings/workspaces/workspaces.tsx: keep the first bulk pass atforce: falseso dirty, active, and linked worktrees are still kept. Collect the worktrees git refused, then show one force confirmation and retry only those withforce: trueafter the user confirms.forceRequiredis set; update their copy to describe the submodule case, and mention it in theworktree_removedocs.The force confirmation is explicit because force deletion also discards uncommitted changes and unpushed commits inside those submodules.
How did you verify your code works?
packages/core:bun test test/git.test.ts— 22 pass. The new test builds a real repo with a committed submodule, initializes it in a linked worktree, and assertsforceRequired === trueforforce: falseand a successfulforce: trueremoval. With the regex reverted, the same test fails withReceived: false.packages/app:bun run test:e2e:built -- e2e/regression/settings.spec.ts— 51 pass, including the new case that makes the mock server return aWorktreeErrorwithforceRequired: true, asserts the force dialog, and observes the retry withforce: true.bun run typecheck:tscinpackages/core,packages/app, andpackages/tui.bunx oxlinton the changed files — 0 warnings/errors. The fullbun run lintreports only pre-existing warnings and 0 errors.The local Bun is 1.3.14 while the repo pins 1.4.4, so
bun check(and the pre-push hook that runs it) is unavailable here;typecheck:tscis the source-of-truth typecheck in this repo.Visual
Screenshots / recordings
The animation above shows the new force-delete confirmation; no additional recording is needed.
Checklist
Added by the opencode bot (AI-generated).
Related: #40613
Public API: DELETE /api/worktree now reports forceRequired: true when git refuses to remove a worktree with initialized submodules (backwards compatible)