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

  1. 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.
  2. 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).
  3. Reduce concurrency: serialize worktree/port-registry operations (queue, lockstep in one process) instead of racing many at once.
  4. 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

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

Related errors


AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-21). Data as JSON: /api/errors/c430c9741e4a3000. Report an issue: GitHub.