stablyai/orca · warning · Error

Worktree deletion already in progress: ${args.worktreeId}

Error message

Worktree deletion already in progress: ${args.worktreeId}

What it means

Thrown by worktrees:remove when worktreeRemovalsInFlight already has an entry for the same inFlightKey (worktreeId+hostId) but with a different optionsKey (options encode force/branch-delete/pty-stop flags). Same-options concurrent calls share the promise; differing-options concurrent calls are rejected to prevent two conflicting destructive operations on one worktree.

Source

Thrown at src/main/ipc/worktrees.ts:2376

  ipcMain.handle(
    'worktrees:remove',
    async (_event, args: RemoveWorktreeArgs): Promise<RemoveWorktreeResult> => {
      const { repoId, worktreePath } = parseWorktreeId(args.worktreeId)
      const repo = getRepoForWorktreeRemoval(store, repoId, args.hostId)
      if (!repo) {
        throw new Error(`Repo not found: ${repoId}`)
      }
      // The resolved repo supplies host ownership when legacy callers omit args.hostId.
      const removalHostId = getRepoExecutionHostId(repo)
      const inFlightKey = getWorktreeRemovalInFlightKey(args.worktreeId, removalHostId)
      const optionsKey = getWorktreeRemovalOptionsKey(args)
      const inFlightRemoval = worktreeRemovalsInFlight.get(inFlightKey)
      if (inFlightRemoval) {
        if (inFlightRemoval.optionsKey === optionsKey) {
          return inFlightRemoval.promise
        }
        throw new Error(`Worktree deletion already in progress: ${args.worktreeId}`)
      }

      // Why: concurrent stale-toast/double-click/sidebar races can hit the same worktree; share the op so only one path touches Git and disk.
      const removal = (async (): Promise<RemoveWorktreeResult> => {
        // Why: worktree.create is traced; delete freezes were invisible without a matching worktree.remove parent span.
        return withWorktreeSpan({ stage: 'remove', path: worktreePath }, async () => {
          if (isFolderRepo(repo)) {
            if (args.worktreeId === getFolderWorkspaceRootId(repo)) {
              throw new Error(
                'Cannot delete the project root workspace. Remove the folder project instead.'
              )
            }
            // Why: folder workspaces share one root, so there's no Git remove step to close shells; sweep PTYs before dropping metadata.
            await withWorktreeRemoveStageSpan('pty_sweep', 'folder', async () => {
              // Folder projects can be SSH-backed, so fence the sweep to the owning host exactly
              // like the git paths — the local inventory must never reach a remote workspace's id.
              // The resolved repo is authoritative here: path-derived metadata is shared by
              // same-id host copies and can describe a different owner's workspace.

View on GitHub (pinned to 1136503c6a)

Solutions

  1. De-dupe remove requests in the renderer (track an in-flight flag per worktreeId) and avoid changing options between retries.
  2. If a different option set is genuinely needed, wait for the first removal to settle, then re-evaluate from the new worktree state.
  3. Reuse the same options when retrying so the in-flight promise is shared instead of rejected.
Defensive patterns

Strategy: validation

Validate before calling

// De-dupe removes per worktreeId in the renderer.
const removing = new Set<string>()
async function removeOnce(worktreeId, opts) {
  if (removing.has(worktreeId)) return
  removing.add(worktreeId)
  try {
    await invoke('worktrees:remove', { worktreeId, ...opts })
  } finally {
    removing.delete(worktreeId)
  }
}

Try / catch

try {
  await invoke('worktrees:remove', args)
} catch (e) {
  if (e.message.startsWith('Worktree deletion already in progress')) {
    // Another option set is running; await worktree refresh instead of retrying with different options.
    return
  }
  throw e
}

Prevention

When it happens

Trigger: Two near-simultaneous worktrees:remove calls for the same worktree with different options — e.g. a non-force delete in flight and a force delete arriving, or a sidebar double-click racing a stale toast's force action.

Common situations: Double-click in the sidebar, a stale recovery toast firing while the user retries with force, or an automation client issuing overlapping remove requests with changed flags.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/57bb744544b5891e. Report an issue: GitHub.