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
- Inspect the branch with `git log refs/heads/<name>` to confirm whether the new commits are wanted.
- If the new commits are disposable, re-resolve the head and call forceDeleteLocalBranch again (or use `git branch -D` directly with explicit intent).
- If the commits matter, merge or cherry-pick them elsewhere before deleting.
- 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
- Run branch deletes immediately after worktree removal, before concurrent pulls/commits can move the ref.
- Do not auto-retry on this error — re-resolve the head and ask the user.
- Treat the toast action as stale if significant time has elapsed since removal.
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
- Cannot force-delete local branch "${branchName}" without the
- Local branch "${branchName}" is checked out in another workt
- Unable to resolve branch base
- Invalid branch name
- ${name} must be a positive integer, received ${value}
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/b711cf73d6488327.
Report an issue: GitHub.