thedotmack/claude-mem · info

Hook input unavailable, skipping hook

Error message

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

What it means

After AdapterRejectedInput, hookCommand also checks isNonBlockingHookInputError: errors meaning hook input was simply unavailable (no/empty stdin, unreadable stdin stream) rather than invalid. These are logged with this message, a no-op result is emitted, and the hook exits with SUCCESS — the hook never blocks the host session on missing input.

Solutions

  1. When testing, always pipe a sample payload: cat sample.json | claude-mem hook ...
  2. Verify the hook entry in settings.json is registered on an event that actually provides input JSON
  3. Check that no shell wrapper (script/sudo) detaches stdin before the hook runs
  4. Confirm Claude Code version compatibility so it actually writes the payload to the hook's stdin

Example fix

// before
claude-mem hook --event Stop   # no stdin -> this warning
// after
echo '{"session_id":"abc","transcript_path":"/tmp/t.jsonl"}' | claude-mem hook --event Stop
Defensive patterns

Strategy: fallback

Validate before calling

let raw = '';
try { raw = readFileSync(0, 'utf8'); } catch { raw = ''; }
if (!raw.trim()) {
  emitModelContext(adapter, buildNoOpResult(event));
  exitGraceful(options);
  process.exit(HOOK_EXIT_CODES.SUCCESS);
}

Type guard

function hasStdinPayload(): boolean { try { return readFileSync(0, 'utf8').trim().length > 0; } catch { return false; } }

Try / catch

try { /* pipeline */ } catch (error) {
  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;
  }
}

Prevention

When it happens

Trigger: The hook process receives no stdin payload: invoked outside a hook context, stdin closed early by the host, TTY-attached manual run with no piped input, or the platform writes nothing because the event carried no data.

Common situations: Running `claude-mem hook` directly in a terminal during debugging; hook registered on an event that sends no body; shell redirection mistakes in settings.json; platform bug closing stdin prematurely.

Understand the failure class

Background: JSON parse error: "Unexpected token" / "not valid JSON" / "failed to parse" — what JSON parsers are really complaining about — this error's family across 45 libraries.

Related errors


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

Appendix: source

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

  // To bypass the buffer for a specific write, use emitDiagnostic /
  // emitBlockingError from src/shared/hook-io.ts. Direct process.stderr.write
  // calls are buffered.
  const stderrBuffer = installHookStderrBuffer();

  const adapter = getPlatformAdapter(platform);
  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);

View on GitHub (pinned to d8bc9755e7)