stablyai/orca · error
Cannot force-delete local branch "${branchName}" without the
Error message
Cannot force-delete local branch "${branchName}" without the commit Git preserved. What it means
Thrown by forceDeleteLocalBranch when expectedHead is falsy. The function deletes via `update-ref -d refs/heads/<name> <expectedHead>`, an optimistic-concurrency delete that only succeeds if the ref still points at expectedHead. Without expectedHead the operation cannot be made safe — a stale toast action could delete a branch that has since received new commits. This guard refuses to proceed rather than fall back to an unconditional `branch -D`.
Source
Thrown at src/main/git/worktree.ts:1388
gitExecFileAsync(args, gitExecOptions(cwd, options))
)
return true
}
export async function forceDeleteLocalBranch(
repoPath: string,
branchName: string,
expectedHead: string,
runGit: (args: string[], cwd: string) => Promise<{ stdout: string; stderr: string }> = (
args,
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) {View on GitHub (pinned to 1136503c6a)
Solutions
- Ensure every caller resolves and passes the branch's current HEAD SHA before invoking forceDeleteLocalBranch.
- If the SHA is genuinely unavailable (worktree already gone), do not call this function — surface a manual-review prompt to the user instead.
- Re-derive expectedHead with `git rev-parse refs/heads/<name>` against the live repo before the call, accepting that a race is then possible but at least the delete is bounded.
Example fix
// before
await forceDeleteLocalBranch(repoPath, branchName, undefined)
// after
const head = await resolveBranchHead(repoPath, branchName)
if (!head) {
toast.error('Cannot determine branch HEAD; review before deleting.')
return
}
await forceDeleteLocalBranch(repoPath, branchName, head) Defensive patterns
Strategy: validation
Validate before calling
function hasResolvedHead(head: unknown): head is string {
return typeof head === 'string' && /^[0-9a-f]{40}$/i.test(head)
} Type guard
function hasResolvedHead(head: unknown): head is string {
return typeof head === 'string' && /^[0-9a-f]{40}$/i.test(head)
} Prevention
- Capture branch HEAD at the same instant you queue the delete action, not lazily on replay.
- If HEAD cannot be resolved, prompt the user for manual review instead of calling through.
- Never pass undefined or empty string as expectedHead — the guard refuses it by design.
When it happens
Trigger: A 'delete branch' toast action whose serialized payload lost the head SHA; calling forceDeleteLocalBranch from new code that omitted the expectedHead argument; the workspace was deleted before the preserved commit SHA was captured.
Common situations: Stale toast action replayed after an app restart that didn't re-hydrate the head SHA; a refactor that introduced a call path forgetting to thread branchHead through.
Related errors
- Invalid branch name
- Local branch "${branchName}" changed after the workspace was
- ${name} must be a positive integer, received ${value}
- no benchmark file exists at both HEAD and HEAD~1 in this che
- No worktrees on environment ${envName}
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/233e9ce1ee9cd185.
Report an issue: GitHub.