fix: remove unrecognized fields from Claude Code Stop hook output (#1291)

* fix: remove unrecognized fields from Claude Code Stop hook output

Claude Code validates Stop hook JSON output against its hook contract
schema which only accepts {decision?, reason?, systemMessage?}. The
formatOutput() function was returning {continue, suppressOutput} which
are not part of the Claude Code hook API, causing "JSON validation
failed" errors on every session stop.

Return an empty object {} for the default case (no hookSpecificOutput),
preserving only systemMessage when present. This is valid for all hook
event types and eliminates the schema validation error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add unhappy-path tests for formatOutput per PR review

Add edge case coverage for malformed input (undefined/null), falsy
systemMessage values, non-contract field stripping, and contract key
allowlist. Also add defensive null guard to formatOutput matching
normalizeInput pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Alex Worland <alexworland@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
AlexWorland
2026-03-12 19:59:45 -07:00
committed by GitHub
parent 38d9ac7adb
commit 10e980cd69
2 changed files with 69 additions and 29 deletions
+11 -4
View File
@@ -16,13 +16,20 @@ export const claudeCodeAdapter: PlatformAdapter = {
};
},
formatOutput(result) {
if (result.hookSpecificOutput) {
const r = result ?? ({} as HookResult);
if (r.hookSpecificOutput) {
const output: Record<string, unknown> = { hookSpecificOutput: result.hookSpecificOutput };
if (result.systemMessage) {
output.systemMessage = result.systemMessage;
if (r.systemMessage) {
output.systemMessage = r.systemMessage;
}
return output;
}
return { continue: result.continue ?? true, suppressOutput: result.suppressOutput ?? true };
// Only emit fields in the Claude Code hook contract — unrecognized fields
// cause "JSON validation failed" in Stop hooks.
const output: Record<string, unknown> = {};
if (r.systemMessage) {
output.systemMessage = r.systemMessage;
}
return output;
}
};