Skip to content

fix(worktree): offer force removal for submodule worktrees - #60172

Open
DevDengChao wants to merge 2 commits into
anomalyco:devfrom
DevDengChao:worktree-submodule-force
Open

DevDengChao wants to merge 2 commits into
anomalyco:devfrom
DevDengChao:worktree-submodule-force

Conversation

@DevDengChao

@DevDengChao DevDengChao commented Oct 29, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #60165

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

Git refuses git worktree remove for a worktree whose submodules are initialized unless --force is passed. The error has no "use --force" hint, and OpenCode only marked dirty-worktree errors as forceRequired, so the desktop bulk delete stopped at the raw error.

  • packages/core/src/git.ts: classify git's "containing submodules" refusal as forceRequired, and pin the worktree subprocess to LC_ALL=C so the English match also works on non-English locales.
  • packages/app/src/settings/workspaces/workspaces.tsx: keep the first bulk pass at force: false so dirty, active, and linked worktrees are still kept. Collect the worktrees git refused, then show one force confirmation and retry only those with force: true after the user confirms.
  • TUI and Code Mode already retry with force when forceRequired is set; update their copy to describe the submodule case, and mention it in the worktree_remove docs.

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 asserts forceRequired === true for force: false and a successful force: true removal. With the regex reverted, the same test fails with Received: 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 a WorktreeError with forceRequired: true, asserts the force dialog, and observes the retry with force: true.
  • bun run typecheck:tsc in packages/core, packages/app, and packages/tui.
  • bunx oxlint on the changed files — 0 warnings/errors. The full bun run lint reports 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:tsc is the source-of-truth typecheck in this repo.

Visual

A submodule worktree row, then a force-delete confirmation with a check mark

Screenshots / recordings

The animation above shows the new force-delete confirmation; no additional recording is needed.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

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)

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>

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: Cannot delete worktrees with initialized git submodules from "Delete worktrees without sessions"

1 participant