Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

PreToolUse:Bash dispatcher echoes input event to stdout → "Hook JSON output validation failed — (root): Invalid input" #2239

Open

Description

Plugin version: 2.0.0

Symptom

On nearly every Bash tool call, Claude Code reports:

PreToolUse:Bash hook error
⎿ Hook JSON output validation failed — (root): Invalid input

Root cause

In scripts/hooks/bash-hook-dispatcher.js, runHooks() returns the unmodified raw stdin (the PreToolUse input event) on stdout whenever no sub-hook produced additionalContext:

return {
 output: additionalContext
 ? buildPreToolUseAdditionalContext(additionalContext)
 : currentRaw, // ← echoes the input event {session_id, hook_event_name, tool_name, tool_input, ...}
 ...
};

Claude Code parses a hook's stdout as JSON and validates it against the hook output schema. The echoed input object (keys session_id, transcript_path, cwd, hook_event_name, tool_name, tool_input) doesn't match → (root): Invalid input.

It fires for any command where no sub-hook adds context (i.e. most commands). Commands that do add additionalContext produce a valid envelope and don't error, which is why it looks intermittent.

The early-return on a non-zero exit (return { output: currentRaw, ... }) has the same flaw. auto-tmux-dev has a related issue: it rewrites dev-server commands by emitting a full input-event JSON on stdout, which also cannot be valid hook output.

Reproduce

ROOT=~/.claude/plugins/cache/ecc/ecc/2.0.0
echo '{"session_id":"t","transcript_path":"/tmp/x","cwd":"'"$PWD"'","hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"ls -la"}}' \
 | CLAUDE_PLUGIN_ROOT="$ROOT" node "$ROOT/scripts/hooks/pre-bash-dispatcher.js"
# → prints the input event JSON back to stdout (should print nothing)

Fix

Only emit currentRaw when a sub-hook deliberately set stdout; otherwise emit ''. Track with a rawModified flag:

function runHooks(rawInput, hooks) {
 let currentRaw = rawInput;
 let rawModified = false;
 // ...
 const result = normalizeHookResult(currentRaw, hook.run(currentRaw));
 if (result.raw !== currentRaw) rawModified = true;
 currentRaw = result.raw;
 // ...
 // non-zero early return:
 return { output: rawModified ? currentRaw : '', stderr, additionalContext, exitCode: result.exitCode };
 // ...
 // final return:
 return {
 output: additionalContext
 ? buildPreToolUseAdditionalContext(additionalContext)
 : rawModified ? currentRaw : '',
 stderr, additionalContext, exitCode: 0,
 };
}

This preserves GateGuard's {stdout} pass-through and block-no-verify's exit-2 behavior, while emitting nothing in the common pass-through case. Verified locally: ls/git status/cat/echo → empty stdout + exit 0 (no error); --no-verify still blocks via exit 2 + stderr; additionalContext reminders still build a valid hookSpecificOutput envelope.

A PR with this change follows.

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

        AltStyle によって変換されたページ (->オリジナル) /