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
- Echo the hook payload to a file and validate it against the expected event schema to see which field error.reason flags
- Check .claude/settings.json hook wiring — ensure the correct event (Stop/SessionEnd/etc.) invokes this command
- Update claude-mem and Claude Code together so payload schemas match
- 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
- Register hooks only on supported events in settings.json
- Test the hook with a recorded valid payload before deploying
- Keep claude-mem and Claude Code versions aligned
- Always pipe stdin when invoking the hook manually
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
- Hook input unavailable, skipping hook
- adapter rejected input: invalid_cwd
- adapter rejected input: invalid_cwd
- adapter rejected input: invalid_cwd
- adapter rejected input: invalid_cwd
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)