thedotmack/claude-mem · warning
Port still bound entering restart fallback — verifying curre
Error message
Port still bound entering restart fallback — verifying current port owner instead of spawning
What it means
In the restart fallback path, if the port was never observed free, spawning a competitor is pointless — it could not bind anyway. The CLI instead verifies whichever process currently owns the port: either the stuck old worker (verification fails and reports its health payload) or an already-running successor it failed to observe in time (verification passes).
Source
Thrown at src/services/worker-service.ts:1177
const restartScript = resolveWorkerScriptPath() ?? __filename;
let spawnedScript = 'none (port still bound — nothing spawned)';
let spawnLockHeld = false;
if (restartFreed) {
// Owner-or-dead guarded (Phase 5): delete only the old worker's PID
// file (oldPid) or a dead pid's leftover. If a successor we failed to
// observe already wrote its own file, it must survive this cleanup.
removePidFileIfOwner(oldPid);
// Spawn gate (src/shared/worker-spawn-gate.ts): if another launcher
// (a hook or the MCP server) is already mid-spawn, skip our own spawn
// and just verify its worker below.
spawnLockHeld = acquireSpawnLock();
} else {
// The port never freed: either the old worker refuses to die (the
// verification below fails and reports its health payload) or a
// successor we failed to observe in time already owns the port (the
// verification below passes). Spawning a competitor here is never
// useful — it could not bind the port anyway.
logger.warn('SYSTEM', 'Port still bound entering restart fallback — verifying current port owner instead of spawning', { port, portWaitSkipped: handoffSawLiveWorker });
}
try {
if (spawnLockHeld) {
const restartPid = spawnDaemon(restartScript, port);
if (restartPid === undefined) {
console.error('Failed to spawn worker daemon during restart.');
// Manual release: process.exit() does not unwind to finally.
releaseSpawnLock();
process.exit(1);
}
spawnedScript = restartScript;
logger.info('SYSTEM', 'Worker restart spawned (CLI fallback)', { pid: restartPid, script: restartScript });
// Hold the lock until the spawned worker owns the port (the spawn
// isn't "done" until then — same rule as the other gated
// launchers); the longer verification below runs unlocked.
await waitForHealth(port, getPlatformTimeout(15000));
} else if (restartFreed) {
spawnedScript = 'none (another launcher holds the spawn lock)';View on GitHub (pinned to e2d1df569a)
Solutions
- Check `claude-mem status` — if the current owner is a healthy new-version worker, the restart effectively succeeded
- If the old worker is the stuck owner, run `claude-mem stop` (or kill its pid) and then `claude-mem start`
- Passive warning: if everything is healthy afterwards, no action needed
Defensive patterns
Strategy: validation
Validate before calling
async function describePortOwner(port: number): Promise<{ pid: number; healthy: boolean; version?: string }> {
const pid = await getCurrentWorkerPid(port, 2000);
let healthy = false, version: string | undefined;
try {
const res = await fetch(`http://127.0.0.1:${port}/health`);
healthy = res.ok;
version = (await res.json()).version;
} catch {}
return { pid: pid ?? -1, healthy, version };
} Prevention
- Before restarting, check the current port owner's health and version — a live successor needs no action
- If the old worker is the stuck owner, stop/kill it explicitly instead of racing restarts
- One restart initiator at a time prevents this fork in the first place
When it happens
Trigger: The old worker is slow to release the port while a successor from the restart handoff has already bound it; or the old worker is genuinely stuck and still owns the listener.
Common situations: Restart racing the dying worker's cleanup; a successor started by a hook's lazy-spawn owning the port before the CLI noticed; overloaded machines stretching the port-free window.
Related errors
- Port did not free up after shutdown
- `server ${commandLabel}` is a server runtime command, but CL
- Self-replacing worker handoff did not verify in time — falli
- Invalid CLAUDE_MEM_REDIS_PORT=${value}; expected a TCP port
- CLAUDE_MEM_SERVER_DATABASE_URL is required for `server ${com
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/34baa6a25414b177.
Report an issue: GitHub.