stablyai/orca · warning
Worktree deletion already in progress
Error message
Worktree deletion already in progress
What it means
Thrown by acquireWatcherRemovalGate when a removal is already in progress for the same root path (or a parent/child of it). The gate uses isPathInsideOrEqual bidirectionally so that deleting '/a' and '/a/b' cannot run concurrently — the second acquire sees an existing state with removalCount > 0 overlapping its root and refuses. This prevents two destructive runs from racing on shared filesystem roots.
Source
Thrown at src/main/ipc/watcher-removal-gate.ts:113
}
export function acquireWatcherRemovalGate(
rootPath: string,
connectionId?: string
): WatcherRemovalGate {
const normalizedRoot = normalizeRuntimePathForComparison(rootPath)
const hostStates = matchingHostStates(connectionId)
if (
hostStates.some(
(state) =>
state.removalCount > 0 &&
(isPathInsideOrEqual(state.rootPath, normalizedRoot) ||
isPathInsideOrEqual(normalizedRoot, state.rootPath))
)
) {
// Why: desktop and runtime removal entry points have separate request
// dedupe; the shared root fence must still prevent two destructive runs.
throw new Error('Worktree deletion already in progress')
}
const key = watcherRemovalGateKey(normalizedRoot, connectionId)
const state = states.get(key) ?? createState(key, normalizedRoot, connectionId)
state.removalCount++
// Why: deleting a parent root must wait for native installs already admitted
// under that root, not only installs keyed to the exact same spelling.
const fenced = matchingHostStates(connectionId)
.filter(
(candidate) =>
candidate.installs.size > 0 && isPathInsideOrEqual(normalizedRoot, candidate.rootPath)
)
.map((candidate) => ({ state: candidate, tokens: new Set(candidate.installs) }))
const drains = fenced.map(
({ state: candidate }) =>
new Promise<void>((resolve) => candidate.installDrainWaiters.add(resolve))
)
const ready = drains.length === 0 ? Promise.resolve() : Promise.all(drains).then(() => undefined)
let released = falseView on GitHub (pinned to 1136503c6a)
Solutions
- Wait for the in-flight deletion to complete (the first gate's ready/release) before issuing the second.
- Serialize deletions in the caller via a per-root queue keyed on normalized path.
- In the UI, disable the delete action while a deletion for an overlapping root is pending.
Example fix
// before
const gate1 = acquireWatcherRemovalGate(rootPath)
const gate2 = acquireWatcherRemovalGate(rootPath) // throws
// after
const gate1 = acquireWatcherRemovalGate(rootPath)
await gate1.ready
try { /* do removal */ } finally { gate1.release() }
const gate2 = acquireWatcherRemovalGate(rootPath) Defensive patterns
Strategy: try-catch
Validate before calling
function isRemovalInProgress(rootPath: string, connectionId?: string): boolean {
// expose a peek helper from the gate module that mirrors acquireWatcherRemovalGate's overlap check
return peekRemovalInProgress(normalizeRuntimePathForComparison(rootPath), connectionId)
} Try / catch
try {
const gate = acquireWatcherRemovalGate(rootPath, connectionId)
await gate.ready
try { /* perform removal */ } finally { gate.release() }
} catch (e) {
if (/Worktree deletion already in progress/.test((e as Error).message)) {
showToast('A deletion for this root is already running; please wait.')
} else throw e
} Prevention
- Serialize deletions per normalized root in the caller using a queue.
- Disable the delete affordance in the UI while an overlapping removal is pending.
- Always release the gate in a finally block to avoid starving later deletions.
When it happens
Trigger: acquireWatcherRemovalGate is called twice for the same rootPath (or nested/overlapping roots) before the first gate is released. Desktop and runtime removal entry points have separate dedupe but converge on this shared fence.
Common situations: User rapidly clicks delete on two worktrees sharing a parent root, or on the same worktree from two surfaces (sidebar + command). A parent-root deletion is in flight when a child deletion is requested. Automation triggers a cleanup while the user is manually deleting.
Related errors
- Unable to create untitled markdown note
- Codex hooks.json changed while Orca prepared its trust repai
- agent_session_claim_unavailable
- Local branch "${branchName}" is checked out in another workt
- Access denied: invalid worktree path
AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12).
Data as JSON: /api/errors/2595a444c9b1eb0c.
Report an issue: GitHub.