stablyai/orca · warning

Local branch "${branchName}" changed after the workspace was

Error message

Local branch "${branchName}" changed after the workspace was deleted. Review it before deleting it.

What it means

Thrown in the catch block after `update-ref -d refs/heads/<name> <expectedHead>` failed. update-ref -d with an expected value only deletes if the ref still matches that value; failure means the branch moved between when the workspace was deleted (and expectedHead captured) and now. Rather than delete commits the user may want, the function surfaces the divergence for manual review.

Source

Thrown at src/main/git/worktree.ts:1399

    cwd
  ) => gitExecFileAsync(args, { cwd })
): Promise<void> {
  if (!branchName || branchName.includes('\0')) {
    throw new Error('Invalid branch name')
  }
  if (!expectedHead) {
    throw new Error(
      `Cannot force-delete local branch "${branchName}" without the commit Git preserved.`
    )
  }
  if (await isLocalBranchCheckedOut(repoPath, branchName, runGit)) {
    throw new Error(`Local branch "${branchName}" is checked out in another worktree.`)
  }
  // Why: stale toast actions must not delete a branch that moved; `update-ref -d` deletes only if the ref still == expectedHead.
  try {
    await runGit(['update-ref', '-d', `refs/heads/${branchName}`, expectedHead], repoPath)
  } catch {
    throw new Error(
      `Local branch "${branchName}" changed after the workspace was deleted. Review it before deleting it.`
    )
  }
  if (await isLocalBranchCheckedOut(repoPath, branchName, runGit)) {
    try {
      await runGit(['update-ref', `refs/heads/${branchName}`, expectedHead, ''], repoPath)
    } catch (restoreError) {
      console.warn(
        `[git] Failed to restore local branch "${branchName}" after concurrent checkout`,
        restoreError
      )
    }
    throw new Error(`Local branch "${branchName}" is checked out in another worktree.`)
  }
  try {
    await runGit(['config', '--remove-section', `branch.${branchName}`], repoPath)
  } catch {
    // Best-effort parity with `git branch -D`; stale config is harmless.

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Inspect the branch with `git log refs/heads/<name>` to confirm whether the new commits are wanted.
  2. If the new commits are disposable, re-resolve the head and call forceDeleteLocalBranch again (or use `git branch -D` directly with explicit intent).
  3. If the commits matter, merge or cherry-pick them elsewhere before deleting.
  4. Drop the stale toast action rather than retrying blindly.
Defensive patterns

Strategy: try-catch

Type guard

function isBranchMovedError(err: unknown): boolean {
  return err instanceof Error && /changed after the workspace was deleted/.test(err.message)
}

Try / catch

try {
  await forceDeleteLocalBranch(repoPath, branch, head)
} catch (err) {
  if (isBranchMovedError(err)) {
    promptReviewBranch(branch) // user decides whether new commits are disposable
  } else throw err
}

Prevention

When it happens

Trigger: A concurrent process pushed new commits to the branch and a fetch updated the local ref; the user committed to the branch in another worktree in the gap between worktree removal and branch delete; a background scheduled pull advanced the ref.

Common situations: Stale 'delete branch' toast action replayed seconds or minutes after the original removal, during which the branch received new work; multi-worktree setups where another worktree advanced the same branch.

Related errors


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