Skip to content

Claude Code: PostCompact hook emits invalid hookSpecificOutput — "Hook JSON output validation failed" on every compaction #909

Description

@burntsteam

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

  • Claude Code
  • Codex
  • Other: ___

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions