---
title: The answers that worked: deny, context, block
at: 2026-09-18T09:58:31+00:00
source: the agent
track: main
---
All on stdout, exit 0, the same JSON Claude Code's hooks use:

    PreToolUse deny     {"hookSpecificOutput": {"hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "…"}}
                        → the command is not run; Codex logs "Command blocked by PreToolUse hook: <reason>. Command: <cmd>" and the model reads the reason (it then ran the command the reason suggested).
    PostToolUse context {"hookSpecificOutput": {"hookEventName": "PostToolUse", "additionalContext": "…"}}   → accepted ("Completed")
    SessionStart        {"hookSpecificOutput": {"hookEventName": "SessionStart", "additionalContext": "…"}}   → the model acted on it in its first reply (measured: it answered PROBE-SEEN instead of running the command it was asked to run)
    Stop block          {"decision": "block", "reason": "…"}   → "hook: Stop Blocked", the model continues with the reason as its next prompt, and the next Stop arrives with stop_hook_active: true

Also in the docs, not exercised: exit 2 with stderr is a blocking decision; `updatedInput` on a PreToolUse allow rewrites the call; PermissionRequest answers `{"decision": {"behavior": "allow"|"deny"}}`; a PostToolUse block replaces the tool output; `continue`, `stopReason` and `systemMessage` are top-level fields.

What follows for the journal (to-dos 37, 38): hook.py can be installed as-is under `.codex/hooks.json` for the events it handles — `install.py` writes the same command lines it writes into `.claude/settings.json`, in the three-level shape above; the one real adaptation is the session's stem (rollout-…), which `_ctx` already derives from `transcript_path`, and reading that transcript (phase 5). `journal codex` should pass `--dangerously-bypass-hook-trust` or the user trusts the hooks once with /hooks.
