{"record":{"id":"c430c9741e4a3000","repo":"paperclipai/paperclip","slug":"timed-out-waiting-for-worktree-port-reservation-lo","errorCode":null,"errorMessage":"Timed out waiting for worktree port reservation lock at ${lockPath}","messagePattern":"Timed out waiting for worktree port reservation lock at (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/shared/src/worktree-port-registry.ts","lineNumber":331,"sourceCode":"        processIdentity,\n        probePort: 1,\n        token,\n      };\n      writeRegistryLockOwner(lockPath, owner);\n      lease = startRegistryLockHeartbeat(lockPath, token);\n      writeRegistryLockOwner(lockPath, { ...owner, probePort: lease.probePort });\n      return lease;\n    } catch (error) {\n      if (lease) stopRegistryLockHeartbeat(lease);\n      fs.rmSync(lockPath, { recursive: true, force: true });\n      throw error;\n    }\n  } catch (error) {\n    const code = error instanceof Error && \"code\" in error ? error.code : null;\n    if (code !== \"EEXIST\") throw error;\n    if (removeStaleRegistryLock(lockPath)) return null;\n    if (Date.now() >= deadline) {\n      throw new Error(`Timed out waiting for worktree port reservation lock at ${lockPath}`);\n    }\n    return null;\n  }\n}\n\nfunction releaseRegistryLock(lockPath: string, lease: RegistryLockLease): void {\n  stopRegistryLockHeartbeat(lease);\n  const owner = readRegistryLockOwner(lockPath);\n  if (owner?.token !== lease.token) {\n    return;\n  }\n  fs.rmSync(lockPath, { recursive: true, force: true });\n}\n\nexport function withWorktreePortRegistryLockSync<T>(homeDir: string, run: () => T): T {\n  const lockPath = resolveRegistryLockPath(homeDir);\n  const deadline = Date.now() + WORKTREE_PORT_REGISTRY_LOCK_TIMEOUT_MS;\n  let lease: RegistryLockLease | null = null;","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/packages/shared/src/worktree-port-registry.ts#L313-L349","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import fs from \"node:fs\";\nimport path from \"node:path\";\nfunction registryLockHeldByLiveProcess(lockPath: string): boolean {\n  try {\n    const owner = JSON.parse(fs.readFileSync(path.join(lockPath, \"owner.json\"), \"utf8\"));\n    try { process.kill(owner.pid, 0); return true; } catch { return false; }\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  withWorktreePortRegistryLockSync(home, run);\n} catch (error) {\n  if (error instanceof Error && error.message.includes(\"Timed out waiting for worktree port reservation lock\")) {\n    // inspect the lock dir owner: alive -> wait/stop it; dead -> remove stale lock, then retry\n    throw error;\n  }\n  throw error;\n}","preventionTips":["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."],"tags":["worktree","port-registry","file-lock","timeout","concurrency"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-21T17:58:32.592Z","contentChangedAt":"2026-08-21T17:58:32.592Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}