can1357/oh-my-pi · error · Error
GC already running: ${lockPath}
Error message
GC already running: ${lockPath} What it means
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.
Source
Thrown at packages/coding-agent/src/cli/gc-cli.ts:1481
} catch (error) {
if (codeOf(error) === "ENOENT") return;
}
}
async function openGcBreakerLock(lockPath: string): Promise<{ path: string; handle: fs.FileHandle }> {
const breakerPath = `${lockPath}${GC_LOCK_BREAKER_SUFFIX}`;
for (let attempt = 0; attempt < 2; attempt += 1) {
const handle = await openNewGcLock(breakerPath);
if (handle) {
try {
await handle.writeFile(`${process.pid}\n${new Date().toISOString()}\n`);
return { path: breakerPath, handle };
} catch (error) {
await releaseGcLockFile(breakerPath, handle);
throw error;
}
}
if (!(await removeStaleGcLock(breakerPath))) throw new Error(`GC already running: ${lockPath}`);
}
throw new Error(`GC already running: ${lockPath}`);
}
async function openGcLock(lockPath: string): Promise<fs.FileHandle> {
const direct = await openNewGcLock(lockPath);
if (direct) return direct;
const breaker = await openGcBreakerLock(lockPath);
try {
const raced = await openNewGcLock(lockPath);
if (raced) return raced;
if (!(await removeStaleGcLock(lockPath))) throw new Error(`GC already running: ${lockPath}`);
const takeover = await openNewGcLock(lockPath);
if (takeover) return takeover;
throw new Error(`GC already running: ${lockPath}`);
} finally {
await releaseGcLockFile(breaker.path, breaker.handle);View on GitHub (pinned to 9690622007)
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
Example fix
# before omp gc # GC already running: /path/lock # after ps aux | grep 'omp gc' # confirm nothing is running rm /path/to/gc.lock omp gc
Defensive patterns
Strategy: retry
Validate before calling
import * as fs from "node:fs/promises";
// Probe before running GC
try {
const h = await fs.open(lockPath, "wx"); await h.close(); await fs.rm(lockPath);
} catch (e) {
if ((e as NodeJS.ErrnoException).code === "EEXIST") throw new Error(`another GC appears to be running (${lockPath})`);
} Try / catch
try {
await acquireGcLock(lockPath);
} catch (err) {
if (err instanceof Error && err.message.startsWith("GC already running")) {
// back off and retry once the current GC finishes or the lock is cleared as stale
await Bun.sleep(5000);
await acquireGcLock(lockPath);
} else throw err;
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- WAL checkpoint failed for ${dbPath}: busy=${result.busy}, wa
- session header changed during stats cleanup: ${session.path}
- native spelling thread stopped
- Agent is already processing. Use steer() or followUp() to qu
- Failed to open auth database at '${dbPath}' after ${maxAttem
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/34d7e1481da28d65.
Report an issue: GitHub.