thedotmack/claude-mem · warning

Worker unavailable, skipping hook

Error message

Worker unavailable, skipping hook: ${error instanceof Error ? error.message : error}

What it means

This warning is logged by the claude-mem hook command when the background worker that processes hook events is unreachable. Rather than failing the hook (which would surface errors in Claude Code and could accumulate Windows Terminal tabs), the hook logs a warning, emits a no-op result, and exits 0. A separate fail-loud counter (recordWorkerUnreachable) tracks consecutive failures and surfaces the error after a threshold is reached.

Solutions

  1. Restart the claude-mem worker (the plugin auto-starts it; re-run the hook or reload Claude Code).
  2. Check the worker health endpoint / port configuration for a stale or wrong port.
  3. Reinstall or rebuild the plugin with `npm run build-and-sync` to reset worker state.
  4. If it persists, inspect worker logs at ~/.claude-mem/ for the underlying startup failure.
  5. Treat exit 0 as by-design: the fail-loud counter will emit hook_failed telemetry after N consecutive failures.
Defensive patterns

Strategy: fallback

Validate before calling

// before firing the hook, check worker reachability
const health = await fetch(`http://${host}:${port}/health`, { signal: AbortSignal.timeout(2000) }).then(r => r.ok).catch(() => false);
if (!health) console.warn('[hook] worker not reachable; hook will be skipped');

Type guard

function isWorkerUnavailableError(e: unknown): boolean {
  return e instanceof Error && /ECONNREFUSED|ECONNRESET|ETIMEDOUT|ENOTFOUND|fetch failed/i.test(e.message);
}

Try / catch

try {
  await sendToWorker(event);
} catch (error) {
  if (isWorkerUnavailableError(error)) {
    logger.warn('HOOK', `Worker unavailable, skipping: ${error instanceof Error ? error.message : error}`);
    exitGraceful(options); // exit 0 by design
    return;
  }
  throw error;
}

Prevention

When it happens

Trigger: A hook event fires (session start, prompt submit, etc.) and the request to the worker process fails with an error classified by isWorkerUnavailableError — e.g. the worker process is not running, its port is closed, or the connection is refused.

Common situations: The claude-mem worker crashed or was killed; the plugin was installed but the worker never started; a stale worker port config; system reboot without the worker restarting; antivirus blocking local IPC.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/d6df33e73539c7a5. Report an issue: GitHub.

Appendix: source

Thrown at src/cli/hook-command.ts:147

  const handler = getEventHandler(event);

  try {
    return await executeHookPipeline(adapter, handler, platform, options);
  } catch (error) {
    if (error instanceof AdapterRejectedInput) {
      logger.warn('HOOK', `Adapter rejected input (${error.reason}), skipping hook`);
      emitModelContext(adapter, buildNoOpResult(event));
      exitGraceful(options);
      return HOOK_EXIT_CODES.SUCCESS;
    }
    if (isNonBlockingHookInputError(error)) {
      logger.warn('HOOK', `Hook input unavailable, skipping hook: ${error instanceof Error ? error.message : error}`);
      emitModelContext(adapter, buildNoOpResult(event));
      exitGraceful(options);
      return HOOK_EXIT_CODES.SUCCESS;
    }
    if (isWorkerUnavailableError(error)) {
      logger.warn('HOOK', `Worker unavailable, skipping hook: ${error instanceof Error ? error.message : error}`);
      // EXIT_SIGNAL per CLAUDE.md: transient worker errors exit 0 to avoid
      // Windows Terminal tab accumulation. The fail-loud counter (worker-utils
      // recordWorkerUnreachable) handles the surface-after-N-failures path and
      // emits the threshold-gated hook_failed telemetry internally. Awaited:
      // when the count JUST reaches the threshold it sends the event and then
      // exits 2; exitGraceful below would kill a pending POST mid-flight.
      await recordWorkerUnreachable();
      exitGraceful(options);
      return HOOK_EXIT_CODES.SUCCESS;
    }

    logger.error('HOOK', `Hook error: ${error instanceof Error ? error.message : error}`, {}, error instanceof Error ? error : undefined);
    // hook_failed telemetry MUST be awaited BEFORE emitBlockingError — it
    // calls process.exit(2), which would kill a fire-and-forget POST
    // mid-flight. captureCliEvent never throws and is hard-capped at 2s.
    // Closed-enum props only: the error message itself is never sent.
    {
      const hookType = getActiveHookType();

View on GitHub (pinned to d8bc9755e7)