What happen
On Claude Code, every compaction logs a hook failure:
PostCompact ['/Users/yg/.caveman/bin/caveman-proxy' native-hook claude --adapter '.../native-hook-fast.js'] failed:
Hook JSON output validation failed — (root): Invalid input
The adapter answers PostCompact with a hookSpecificOutput envelope:
{"hookSpecificOutput":{"hookEventName":"PostCompact","additionalContext":"Build simplest complete system. …"}}
Claude Code's hook schema doesn't accept hookSpecificOutput for PostCompact. Its error enumerates the events that do: PreToolUse, UserPromptSubmit, PostToolUse, PostToolBatch, Stop/SubagentStop. So the Core text is discarded and an error is surfaced to the user instead.
Expected
PostCompact returns a shape Claude Code accepts (or returns nothing), so compaction completes without an error.
Worth noting the payload looks redundant: caveman's SessionStart entry is registered without a matcher, so it also fires on source: "compact" — and emits the same 1781-character additionalContext under a valid hookEventName: "SessionStart". Core already survives compaction through that path. Dropping PostCompact from the Claude lifecycle list may be the whole fix.
Before/after example
Input: /compact (Claude Code)
Got: PreCompact … completed successfully
PostCompact … failed: Hook JSON output validation failed — (root): Invalid input
Want: both hooks complete; Core delivered once via SessionStart(source=compact)
Reproduces outside a real compaction:
echo '{"hook_event_name":"PostCompact","session_id":"x","cwd":"'"$PWD"'","transcript_path":"/dev/null"}' \
| ~/.caveman/bin/caveman-proxy native-hook claude \
--adapter /opt/homebrew/lib/node_modules/@caveman-ai/cli/dist/native-hook-fast.js
# -> {"hookSpecificOutput":{"hookEventName":"PostCompact","additionalContext":"…"}}
Same command with SessionStart + "source":"compact" returns a valid hookEventName: "SessionStart" and identical additionalContext.
Where it comes from
dist/index.js hardcodes PostCompact into the lifecycle list for non-gemini/non-codex agents, i.e. Claude:
: ["SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "PostToolUseFailure",
"PreCompact", "PostCompact", "SubagentStart", "SubagentStop", "Stop", "SessionEnd"];
Knock-on effect: the workaround fights doctor --fix
Removing the PostCompact block from ~/.claude/settings.json fixes the error, but then caveman doctor claude reports state: degraded, drifted: true, and flips every component to false (routing, lifecycle_hooks, core, mcp_recovery, tool_rewrite) with coding_policy: off. Running the suggested caveman doctor claude --fix re-adds PostCompact and the error returns — verified that this is the only thing --fix changes in the config.
So users get a choice between a clean compaction and a healthy-looking doctor. It'd be good if the drift check tolerated a host that has pruned an event its runtime rejects.
(Also, state: degraded reads alarming but seems purely a config-match label — caveman status output is byte-identical in both states, including MCP recovery missing — streaming turns and Claude Pro/Max sessions pass through uncompressed.)
Platform
Version / install method
@caveman-ai/cli 1.2.3, global npm install (/opt/homebrew/lib/node_modules/@caveman-ai/cli)
- Also reproduced on 1.2.5 — I ran that release's
dist/native-hook-fast.js against the payload above and got the same hookEventName: "PostCompact" envelope, so upgrading doesn't help.
- Claude Code 2.1.239 (caveman reports
tested_version: 2.1.226, version_status: newer_unknown)
- macOS, Node v26.7.0,
pack_version 2.2.0
What happen
On Claude Code, every compaction logs a hook failure:
The adapter answers
PostCompactwith ahookSpecificOutputenvelope:{"hookSpecificOutput":{"hookEventName":"PostCompact","additionalContext":"Build simplest complete system. …"}}Claude Code's hook schema doesn't accept
hookSpecificOutputforPostCompact. Its error enumerates the events that do:PreToolUse,UserPromptSubmit,PostToolUse,PostToolBatch,Stop/SubagentStop. So the Core text is discarded and an error is surfaced to the user instead.Expected
PostCompactreturns a shape Claude Code accepts (or returns nothing), so compaction completes without an error.Worth noting the payload looks redundant: caveman's
SessionStartentry is registered without a matcher, so it also fires onsource: "compact"— and emits the same 1781-characteradditionalContextunder a validhookEventName: "SessionStart". Core already survives compaction through that path. DroppingPostCompactfrom the Claude lifecycle list may be the whole fix.Before/after example
Reproduces outside a real compaction:
Same command with
SessionStart+"source":"compact"returns a validhookEventName: "SessionStart"and identicaladditionalContext.Where it comes from
dist/index.jshardcodesPostCompactinto the lifecycle list for non-gemini/non-codex agents, i.e. Claude:Knock-on effect: the workaround fights
doctor --fixRemoving the
PostCompactblock from~/.claude/settings.jsonfixes the error, but thencaveman doctor claudereportsstate: degraded,drifted: true, and flips every component tofalse(routing,lifecycle_hooks,core,mcp_recovery,tool_rewrite) withcoding_policy: off. Running the suggestedcaveman doctor claude --fixre-addsPostCompactand the error returns — verified that this is the only thing--fixchanges in the config.So users get a choice between a clean compaction and a healthy-looking
doctor. It'd be good if the drift check tolerated a host that has pruned an event its runtime rejects.(Also,
state: degradedreads alarming but seems purely a config-match label —caveman statusoutput is byte-identical in both states, includingMCP recovery missing — streaming turns and Claude Pro/Max sessions pass through uncompressed.)Platform
Version / install method
@caveman-ai/cli1.2.3, global npm install (/opt/homebrew/lib/node_modules/@caveman-ai/cli)dist/native-hook-fast.jsagainst the payload above and got the samehookEventName: "PostCompact"envelope, so upgrading doesn't help.tested_version: 2.1.226,version_status: newer_unknown)pack_version2.2.0