paperclipai/paperclip · error · Error
Timed out waiting for worktree port reservation lock at ${lo
Error message
Timed out waiting for worktree port reservation lock at ${lockPath} What it means
Thrown by acquireRegistryLock when the registry lock directory already exists (EEXIST), the existing lock is NOT removable as stale, and the caller's deadline (WORKTREE_PORT_REGISTRY_LOCK_TIMEOUT_MS from withWorktreePortRegistryLockSync) has passed. It means another live process legitimately holds the worktree port registry lock for longer than the configured wait budget.
Source
Thrown at packages/shared/src/worktree-port-registry.ts:331
processIdentity,
probePort: 1,
token,
};
writeRegistryLockOwner(lockPath, owner);
lease = startRegistryLockHeartbeat(lockPath, token);
writeRegistryLockOwner(lockPath, { ...owner, probePort: lease.probePort });
return lease;
} catch (error) {
if (lease) stopRegistryLockHeartbeat(lease);
fs.rmSync(lockPath, { recursive: true, force: true });
throw error;
}
} catch (error) {
const code = error instanceof Error && "code" in error ? error.code : null;
if (code !== "EEXIST") throw error;
if (removeStaleRegistryLock(lockPath)) return null;
if (Date.now() >= deadline) {
throw new Error(`Timed out waiting for worktree port reservation lock at ${lockPath}`);
}
return null;
}
}
function releaseRegistryLock(lockPath: string, lease: RegistryLockLease): void {
stopRegistryLockHeartbeat(lease);
const owner = readRegistryLockOwner(lockPath);
if (owner?.token !== lease.token) {
return;
}
fs.rmSync(lockPath, { recursive: true, force: true });
}
export function withWorktreePortRegistryLockSync<T>(homeDir: string, run: () => T): T {
const lockPath = resolveRegistryLockPath(homeDir);
const deadline = Date.now() + WORKTREE_PORT_REGISTRY_LOCK_TIMEOUT_MS;
let lease: RegistryLockLease | null = null;View on GitHub (pinned to a7e689b3c3)
Solutions
- Identify the holder: read the owner JSON inside the lock directory (pid + token) and check whether that process is alive and what it is doing; let it finish or stop it.
- If the holder is dead or unrelated, remove the stale lock directory (it refreshes mtime via heartbeat, so only remove when the owner is truly gone).
- Reduce concurrency: serialize worktree/port-registry operations (queue, lockstep in one process) instead of racing many at once.
- If contention is expected and legitimate, raise the lock timeout budget (WORKTREE_PORT_REGISTRY_LOCK_TIMEOUT_MS) or shrink the critical section.
Defensive patterns
Strategy: validation
Validate before calling
import fs from "node:fs";
import path from "node:path";
function registryLockHeldByLiveProcess(lockPath: string): boolean {
try {
const owner = JSON.parse(fs.readFileSync(path.join(lockPath, "owner.json"), "utf8"));
try { process.kill(owner.pid, 0); return true; } catch { return false; }
} catch {
return false;
}
} Try / catch
try {
withWorktreePortRegistryLockSync(home, run);
} catch (error) {
if (error instanceof Error && error.message.includes("Timed out waiting for worktree port reservation lock")) {
// inspect the lock dir owner: alive -> wait/stop it; dead -> remove stale lock, then retry
throw error;
}
throw error;
} Prevention
- Serialize worktree/port operations through one queue or process instead of racing.
- Do not SIGSTOP/debug-attach processes that may hold the registry lock for long.
- Keep registry critical sections small so locks are held briefly.
- On timeout, inspect the lock directory's owner JSON (pid, token) before deciding to remove it.
When it happens
Trigger: Two or more processes call withWorktreePortRegistryLockSync concurrently (worktree create/remove, port reservation) and the winner holds the lock past the loser's deadline; or a hung-but-alive owner (paused process, debugger, saturated event loop) keeps its heartbeat running so the lock never looks stale.
Common situations: CI fan-out running many paperclip worktree commands at once; a stopped (SIGSTOP'd / debugger-attached) process holding the lock while its heartbeat owner files still verify; long registry operations (huge registry scans) exceeding the default timeout on slow disks.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- Failed to start worktree port reservation lock heartbeat at
- Failed to start worktree port reservation lock probe at ${lo
- Cannot determine worktree port reservation lock owner identi
- Cannot seed target embedded PostgreSQL at ${dataDir} while i
- Worktree seed source diagnostics changed while waiting for t
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21).
Data as JSON: /api/errors/c430c9741e4a3000.
Report an issue: GitHub.