thedotmack/claude-mem · warning
Failed to remove PID file
Error message
Failed to remove PID file
What it means
removePidFile unlinks the worker PID file during shutdown. This warning (Error branch) fires when unlinkSync throws after existsSync passed — EACCES/EPERM when permissions or ownership changed, EBUSY on Windows while another handle holds the file, or ENOENT when the file vanished between the exists check and the unlink (a TOCTOU race). The error is swallowed and shutdown continues.
Source
Thrown at src/services/infrastructure/ProcessManager.ts:161
return JSON.parse(readFileSync(PID_FILE, 'utf-8'));
} catch (error: unknown) {
if (error instanceof Error) {
logger.warn('SYSTEM', 'Failed to parse PID file', { path: PID_FILE }, error);
} else {
logger.warn('SYSTEM', 'Failed to parse PID file', { path: PID_FILE }, new Error(String(error)));
}
return null;
}
}
export function removePidFile(): void {
if (!existsSync(PID_FILE)) return;
try {
unlinkSync(PID_FILE);
} catch (error: unknown) {
if (error instanceof Error) {
logger.warn('SYSTEM', 'Failed to remove PID file', { path: PID_FILE }, error);
} else {
logger.warn('SYSTEM', 'Failed to remove PID file', { path: PID_FILE }, new Error(String(error)));
}
}
}
/**
* Owner-or-dead guarded PID-file removal (Phase 5, worker-restart plan).
*
* Deletes the PID file only when the recorded pid is `expectedOwnerPid` (the
* worker the caller just shut down, or the caller itself) OR is no longer
* alive — the shared guard in supervisor/shutdown.ts with `deleteIfDead` on,
* so this helper may clean dead leftovers while the shutdown cascade only
* ever deletes its own file.
*/
export function removePidFileIfOwner(expectedOwnerPid: number | null): void {
removeOwnedPidFile(PID_FILE, expectedOwnerPid, true);
}View on GitHub (pinned to e2d1df569a)
Solutions
- Read the logged errno: ENOENT is benign (already removed); EACCES/EPERM needs a permission or ownership fix.
- Remove the file manually with the account that owns it (sudo rm if root-owned), so future runs can manage it.
- Route shutdowns through one supervisor so only one actor ever deletes the PID file.
- On Windows, exclude the claude-mem data dir from antivirus scanning if EBUSY/EPERM recurs.
Defensive patterns
Strategy: try-catch
Validate before calling
// avoid the exists/unlink race: just attempt the unlink and classify the errno
import { unlinkSync } from 'node:fs';
try {
unlinkSync(PID_FILE);
} catch (e) {
if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e;
} Type guard
function isBenignUnlink(e: unknown): e is NodeJS.ErrnoException {
return (e as NodeJS.ErrnoException)?.code === 'ENOENT';
} Try / catch
try {
removePidFile();
} catch (e) {
const code = (e as NodeJS.ErrnoException).code;
if (code === 'ENOENT') return; // already gone
if (code === 'EACCES' || code === 'EPERM') log.warn('fix ownership of PID file');
else throw e;
} Prevention
- Run the worker under one account so it always owns its PID file.
- Never run the worker under sudo against a user-owned data dir.
- Exclude the data dir from antivirus scanning on Windows.
When it happens
Trigger: unlinkSync(PID_FILE) throws: ownership changed (a sudo run made root own the file), the file sits on a read-only mount, antivirus or an open handle locks it on Windows, or a concurrent cleanup deleted it in the race window.
Common situations: Two shutdown paths racing (supervisor and worker both cleaning up); running once under sudo so root owns the file; Windows Defender scanning the file at unlink time; data dir on a read-only filesystem.
Related errors
- installPluginDependencies: no package.json at ${targetDir}
- parse_error
- SyncApply: could not create or adopt a session for memory_se
- Transcript watch config not found: ${resolvedPath}
- Auto-reprime failed for corpus "${corpus.name}"
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/4407930ed4c0ef84.
Report an issue: GitHub.