Skip to content

D361 — Codex gets the write-findings hook, through additionalContext on stdout

Decision. The cartographer-write-findings hook (D353) is installed for Codex too. The feedback channel is chosen by an argument of the subcommand, never by sniffing the payload: cartographer hook write-findings --channel stderr|context. stderr (default, Claude Code) prints the message on stderr and exits 2; context (Codex) prints {"hookSpecificOutput":{"hookEventName":"PostToolUse","additionalContext":"<message>"}} on stdout and exits 0, never 2. The hook JSON is per provider (writeFindingsCommandArgs): Codex's command is ./write-findings.sh --channel context, and the shims forward their arguments ("$@", %*). A bad flag is exit 0, not a usage error.

Why. Probed on Codex 0.162.0 (#637): the payload carries tool_name (mcp__<server>__<tool>) and tool_response as the MCP result object, which the parser already reads. Exit 2 on a PostToolUse hook replaces the tool result with the stderr text; the agent reads a failed call and repeats the write, losing the content_hash. additionalContext with exit 0 reaches the model and keeps the result. Exit 0 with stderr only does not reach the model.

Alternatives rejected. Sniffing turn_id in the payload to pick the channel: an undocumented field is not a contract. Using additionalContext for Claude Code too: D353 verified stderr + exit 2 there and this probe did not re-run it. A separate subcommand per client: the parsing and the message are shared. Leaving Codex out: the agent would never see findings on that client.

Consequences. The hook JSON, and so the content hash, changes for Claude installs only through the shim's "$@": existing installs are rewritten once on the next sync. Codex trusts hooks per definition, so the user approves the new hook once. A new client adds a writeFindingsCommandArgs entry and a writeFindingsHook: true declaration, after a probe of its channel.