Stop hook invalid JSON error is too opaque for unsupported fields

Open 💬 3 comments Opened Apr 21, 2026 by SYRS-AI

Summary

When a Stop hook returns JSON with an unsupported field such as hookSpecificOutput, Codex reports only a generic parse failure like:

hook returned invalid stop hook JSON output

That made a real integration failure much harder to diagnose than it needed to be. The hook was returning a shape that is accepted on other events (SessionStart, UserPromptSubmit, PostToolUse) but not on Stop.

Why this matters

In a multi-event hook integration, it is natural to reuse the same output builder across events. If one event has stricter schema rules than the others, a field-level diagnostic is much more actionable than a generic JSON-invalid error.

In my case, the hook JSON itself was valid JSON and the hook exited 0; the problem was only that Stop rejected an extra field that other hook events commonly use.

Current documented behavior

The public hooks docs say Stop expects JSON on stdout and that it supports the common output fields. The docs do not show hookSpecificOutput for Stop:

Current source behavior

At current main (6f6997758a6936b522c046191ae3cae4293e23e9 when I checked), the hooks schema for Stop is strict and does not include hookSpecificOutput:

  • codex-rs/hooks/src/schema.rs#L340-L353
  • StopCommandOutputWire has #[serde(deny_unknown_fields)]
  • fields are only the flattened universal output, decision, and reason
  • codex-rs/hooks/src/engine/output_parser.rs#L215-L232
  • parse_stop() deserializes directly into StopCommandOutputWire
  • codex-rs/hooks/src/events/stop.rs
  • when parsing fails, Codex surfaces hook returned invalid stop hook JSON output

Minimal example

This is valid JSON, but because it includes hookSpecificOutput, it is rejected by Stop:

{
  "decision": "block",
  "reason": "Run one more pass.",
  "hookSpecificOutput": {
    "hookEventName": "Stop",
    "additionalContext": "Queue DB is source of truth."
  }
}

Requested improvement

At minimum, please make the error specific enough to identify the actual contract violation, for example by reporting the unexpected field name/path instead of only saying the whole payload is invalid.

Any of these would help:

  1. Surface the serde unknown-field error directly, e.g. unsupported field hookSpecificOutput for Stop.
  2. Add event-aware validation diagnostics before returning the generic parse error.
  3. Optionally, if intended, support hookSpecificOutput.additionalContext on Stop too for parity with other events.

I am not claiming the current parser is wrong per the documented schema. The issue is that the runtime diagnostic is too opaque for a common integration mistake.

Environment

  • Codex CLI version: 0.122.0
  • Platform: macOS
  • Hooks feature enabled with features.codex_hooks=true

Additional context

I found this while debugging a Codex-only Stop hook failure in a cross-tool bridge. The local integration bug was easy to fix once I traced the schema, but that took longer than it should have because the runtime error did not reveal that the JSON was structurally valid and only had an unsupported field.

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗