thedotmack/claude-mem · warning
Worker is healthy but not ready; skipping hook API call
Error message
Worker is healthy but not ready; skipping hook API call
What it means
A worker is already alive on the port with a matching version, but waitForWorkerReadiness timed out before the worker flipped its ready flag (readiness is stronger than port-open/health-alive). The hook skips its API call and returns false, so that hook event degrades (e.g., no memory injected).
Source
Thrown at src/shared/worker-utils.ts:482
// (warnIfVersionStillMismatched). Stays null when no worker was alive
// (plain cold-start lazy-spawn — no recycle happened, nothing to amplify)
// or when the resolved version is unreadable ('unknown').
let expectedPluginVersion: string | null = null;
if (await isWorkerPortAlive()) {
// A worker is already alive. If it is a DIFFERENT version than the one
// this resolution would spawn (e.g. the user upgraded but the previous
// worker is still squatting the port), recycle it so the resolved
// version takes over — otherwise the stale worker keeps serving
// indefinitely.
const { matches, pluginVersion, workerVersion } = await checkVersionMatch(getWorkerPort(), resolvedScript?.version ?? null);
if (pluginVersion !== 'unknown') {
expectedPluginVersion = pluginVersion;
}
if (matches) {
const ready = await waitForWorkerReadiness();
if (!ready) {
logger.warn('SYSTEM', 'Worker is healthy but not ready; skipping hook API call');
return false;
}
if (expectedPluginVersion !== null) {
await warnIfVersionStillMismatched(expectedPluginVersion);
}
return true;
}
logger.info('SYSTEM', 'Worker version mismatch — killing stale worker', {
pluginVersion,
workerVersion,
});
// The stale worker must never run its own replacement. The previous
// design (POST /api/admin/restart, then the dying worker spawns its
// successor) executed the OLD install's handoff code: a ≤13.11.0 worker
// resolves the successor script from its own install dir, respawns its
// own version, and re-binds the port before this hook's lazy-spawn — so
// the mismatch recurs on every hook forever (#3378: 2,424 recycles inView on GitHub (pinned to e2d1df569a)
Solutions
- Retry by invoking another hook event / sending the next prompt — the worker usually finishes booting on its own.
- Raise the health/readiness budget via CLAUDE_MEM_HEALTH_TIMEOUT_MS (bounded; see readTimeoutEnv).
- Pre-warm the daemon at login so the first session does not race boot.
- Check worker logs for the init step that is slow (Chroma first-run, migrations) and address it.
Defensive patterns
Strategy: retry
Validate before calling
const ready = await waitForWorkerReadiness();
if (!ready) {
// worker still initializing — skip API call this event, retry next event
return false;
} Type guard
const isWorkerReady = async (): Promise<boolean> => {
try { return (await fetchWorkerHealth()).ready === true; }
catch { return false; }
}; Prevention
- Pre-warm the worker daemon at login so the first session never races readiness.
- Size CLAUDE_MEM_HEALTH_TIMEOUT_MS for your slowest cold boot (default budgets assume ~7s Chroma start).
- Treat healthy-but-not-ready as transient: retry on the next hook event before debugging.
When it happens
Trigger: Cold boot where the port binds quickly but internal init (Chroma, DB migrations, session store) exceeds the readiness budget; waitForWorkerReadiness exhausts its attempts right before the ready flag sets.
Common situations: First session after a reboot (cold macOS+Chroma start needs ~7s); slow disks; CPU contention from a busy machine slowing worker init.
Related errors
- Worker lazy-spawned but did not become ready before hook rea
- Worker port did not open after lazy-spawn within the cold-bo
- Live PID detected but worker did not become ready before tim
- Worker spawned but readiness endpoint not responding within
- Spawn-lock holder's worker port did not open within the cold
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/d0184e7f256c82e9.
Report an issue: GitHub.