thedotmack/claude-mem · warning · AdapterRejectedInput

Adapter rejected input

Error message

Adapter rejected input (${error.reason}), skipping hook

What it means

hookCommand builds a platform adapter and runs the hook pipeline. If the adapter throws AdapterRejectedInput — meaning the hook's stdin payload did not conform to the expected schema for that event/platform — the warning is logged with error.reason, a no-op result is emitted so the model context stays clean, and the process exits gracefully with SUCCESS. The hook is intentionally non-blocking on bad input.

Solutions

  1. Echo the hook payload to a file and validate it against the expected event schema to see which field error.reason flags
  2. Check .claude/settings.json hook wiring — ensure the correct event (Stop/SessionEnd/etc.) invokes this command
  3. Update claude-mem and Claude Code together so payload schemas match
  4. Test the hook manually with a known-good sample payload via cat payload.json | claude-mem hook

Example fix

// before
cat payload.json | claude-mem hook --event UnknownEvent
// after
cat payload.json | claude-mem hook --event Stop
Defensive patterns

Strategy: validation

Validate before calling

const raw = readFileSync(0, 'utf8');
const payload = JSON.parse(raw); // validate before invoking hookCommand
if (!payload || typeof payload.session_id !== 'string' || !payload.hook_event_name) {
  throw new AdapterRejectedInput('missing required hook fields');
}

Type guard

function isHookPayload(v: unknown): v is { session_id: string; hook_event_name: string } {
  return typeof v === 'object' && v !== null && 'session_id' in v && 'hook_event_name' in v;
}

Try / catch

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;
  }
}

Prevention

When it happens

Trigger: Claude Code (or another platform) pipes a payload the adapter cannot accept: empty stdin, JSON missing required fields for the event type, wrong event shape for the configured handler, or a payload from an unsupported platform version.

Common situations: Hook misconfigured in settings.json (wrong event wired to this command); running the hook command manually with no stdin; Claude Code version change altering the hook payload schema; another plugin emitting malformed input.

Understand the failure class

Background: "Invalid JSON response" and "Failed to parse response" errors: when an API answers 200 but the body isn't the JSON your library expected — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

  // We BUFFER stderr during handler execution so that unsolicited writes from
  // third-party libraries don't leak into model context. The buffer is FLUSHED
  // only when we choose to surface (logger errors at the catch-all branch,
  // fail-loud counter from worker-utils, blocking-error path). Successful exits
  // drop the buffer — preserving the original "quiet on success" behavior.
  //
  // 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.

View on GitHub (pinned to d8bc9755e7)