{"record":{"id":"34d7e1481da28d65","repo":"can1357/oh-my-pi","slug":"gc-already-running-lockpath","errorCode":null,"errorMessage":"GC already running: ${lockPath}","messagePattern":"GC already running: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/cli/gc-cli.ts","lineNumber":1481,"sourceCode":"\t} catch (error) {\n\t\tif (codeOf(error) === \"ENOENT\") return;\n\t}\n}\n\nasync function openGcBreakerLock(lockPath: string): Promise<{ path: string; handle: fs.FileHandle }> {\n\tconst breakerPath = `${lockPath}${GC_LOCK_BREAKER_SUFFIX}`;\n\tfor (let attempt = 0; attempt < 2; attempt += 1) {\n\t\tconst handle = await openNewGcLock(breakerPath);\n\t\tif (handle) {\n\t\t\ttry {\n\t\t\t\tawait handle.writeFile(`${process.pid}\\n${new Date().toISOString()}\\n`);\n\t\t\t\treturn { path: breakerPath, handle };\n\t\t\t} catch (error) {\n\t\t\t\tawait releaseGcLockFile(breakerPath, handle);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t}\n\t\tif (!(await removeStaleGcLock(breakerPath))) throw new Error(`GC already running: ${lockPath}`);\n\t}\n\tthrow new Error(`GC already running: ${lockPath}`);\n}\n\nasync function openGcLock(lockPath: string): Promise<fs.FileHandle> {\n\tconst direct = await openNewGcLock(lockPath);\n\tif (direct) return direct;\n\n\tconst breaker = await openGcBreakerLock(lockPath);\n\ttry {\n\t\tconst raced = await openNewGcLock(lockPath);\n\t\tif (raced) return raced;\n\t\tif (!(await removeStaleGcLock(lockPath))) throw new Error(`GC already running: ${lockPath}`);\n\t\tconst takeover = await openNewGcLock(lockPath);\n\t\tif (takeover) return takeover;\n\t\tthrow new Error(`GC already running: ${lockPath}`);\n\t} finally {\n\t\tawait releaseGcLockFile(breaker.path, breaker.handle);","sourceCodeStart":1463,"sourceCodeEnd":1499,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/cli/gc-cli.ts#L1463-L1499","documentation":"The GC lock acquisition path throws this when a stale lock could not be removed (removeStaleGcLock returned false) — another GC run appears live, so this invocation refuses to start and reports the lock file path.","triggerScenarios":"Acquiring the GC lock when the breaker/lock file exists and cannot be reclaimed as stale: an active concurrent GC holds it, or a stale lock file fails the staleness heuristics (e.g. fresh mtime from a hung process, unlink permission failure).","commonSituations":"Two `omp gc` invocations racing (cron + manual); a previous GC crashed leaving a lock whose mtime looks fresh; running GC as a different user without permission to remove the lock file.","solutions":["Wait for the running GC to finish and retry","Confirm no GC process is alive (`ps | grep omp gc`), then delete the lock file at the path in the message and re-run","If the file cannot be removed due to permissions, fix ownership of the lock/state directory"],"exampleFix":"# before\nomp gc  # GC already running: /path/lock\n# after\nps aux | grep 'omp gc'   # confirm nothing is running\nrm /path/to/gc.lock\nomp gc","handlingStrategy":"retry","validationCode":"import * as fs from \"node:fs/promises\";\n// Probe before running GC\ntry {\n  const h = await fs.open(lockPath, \"wx\"); await h.close(); await fs.rm(lockPath);\n} catch (e) {\n  if ((e as NodeJS.ErrnoException).code === \"EEXIST\") throw new Error(`another GC appears to be running (${lockPath})`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await acquireGcLock(lockPath);\n} catch (err) {\n  if (err instanceof Error && err.message.startsWith(\"GC already running\")) {\n    // back off and retry once the current GC finishes or the lock is cleared as stale\n    await Bun.sleep(5000);\n    await acquireGcLock(lockPath);\n  } else throw err;\n}","preventionTips":["Serialize GC runs (single scheduler) instead of ad-hoc manual + cron invocations","Check for and clean stale lock files when a previous GC crashed","Ensure consistent user/permissions on the lock directory so stale locks can be unlinked"],"tags":["locking","gc","concurrency"],"backgroundTag":"lock-already-held","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}