How Hooks Are Implemented in the Codex Plugin: A Technical Deep Dive

The Codex plugin implements hooks in Claude Code through a declarative JSON configuration that maps hook events (SessionStart, SessionEnd, and Stop) to executable Node.js scripts, enabling automated lifecycle management and a review gate that can block sessions based on Codex output.

The openai/codex-plugin-cc repository extends Claude Code with a sophisticated hook system that intercepts key session events. These hooks allow the plugin to run custom logic at session startup, teardown, and completion, with the Stop hook serving as a critical quality gate that can prevent session closure until Codex reviews pass.

Hook Architecture Overview

The Codex plugin implements three distinct hook points in Claude Code's lifecycle. Each hook executes a specific Node.js script when triggered:

  • SessionStart: Runs when a Claude session opens to initialize state and register session IDs
  • SessionEnd: Executes during session closure to perform cleanup and cancel lingering jobs
  • Stop: Fires when Claude finishes a turn and attempts to end the session, capable of blocking completion if review issues are found

The mapping between these hook names and their execution scripts lives in plugins/codex/hooks/hooks.json, which Claude Code reads at plugin load time to register the commands automatically.

The Declarative Hook Configuration

The hook system relies on a JSON manifest that declares which commands to run for each lifecycle event. This configuration uses the ${CLAUDE_PLUGIN_ROOT} environment variable to resolve script paths dynamically.

{
  "hooks": {
    "SessionStart": [{ "hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-lifecycle-hook.mjs\" SessionStart", "timeout": 5 }] }],
    "SessionEnd":   [{ "hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/session-lifecycle-hook.mjs\" SessionEnd",   "timeout": 5 }] }],
    "Stop":         [{ "hooks": [{ "type": "command", "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/stop-review-gate-hook.mjs\"", "timeout": 900 }] }]
  }
}

SessionStart and SessionEnd Hooks

Both lifecycle hooks invoke the same session-lifecycle-hook.mjs script with different arguments. The SessionStart hook initializes per-session state, such as registering the session ID in the tracked jobs system. The SessionEnd hook handles cleanup operations, removing temporary files and canceling any active Codex jobs. These hooks execute with a 5-second timeout, ensuring they complete quickly without blocking the user interface.

The Stop Hook Configuration

The Stop hook operates differently from lifecycle hooks. It executes stop-review-gate-hook.mjs with a generous 900-second timeout (15 minutes), allowing time for comprehensive Codex reviews. Unlike the lifecycle hooks, this script can return a blocking decision that prevents Claude from closing the session, effectively creating a mandatory review gate before code changes are finalized.

Implementing the Stop Review Gate

The plugins/codex/scripts/stop-review-gate-hook.mjs file contains the most complex hook implementation, orchestrating a multi-step validation process that can halt session completion.

Input Processing from STDIN

When the Stop hook triggers, Claude Code pipes a JSON payload to the script via STDIN. The hook reads this input synchronously to extract session context:

const raw = fs.readFileSync(0, "utf8").trim();
const input = raw ? JSON.parse(raw) : {};

The payload contains the current working directory (cwd), the last assistant message from Claude, and an optional session_id, allowing the hook to contextualize its review based on the actual conversation state.

Configuration Validation

Before spawning a review, the hook loads plugin configuration via getConfig() from lib/state.mjs. It checks whether the stopReviewGate option is enabled in the workspace configuration. If disabled, the script exits silently, allowing the session to close normally without overhead.

Spawning the Codex Review Process

When the review gate is active, the hook constructs a prompt using stop-review-gate.md and executes the Codex companion as a child process:

const result = spawnSync(process.execPath,
    [scriptPath, "task", "--json", prompt],
    { cwd, env: childEnv, encoding: "utf8", timeout: STOP_REVIEW_TIMEOUT_MS });

This invocation runs codex-companion.mjs with a 15-minute timeout, sending the prompt containing the previous Claude response for independent review.

Parsing and Returning Decisions

The hook parses the JSON output from the Codex process, examining the rawOutput field for the first line:

if (firstLine.startsWith("ALLOW:"))      return { ok: true };
if (firstLine.startsWith("BLOCK:"))      return { ok: false, reason: /* extracted message */ };

If the review detects issues, the hook emits a blocking decision to STDOUT:

emitDecision({
  decision: "block",
  reason: review.reason
});

Claude Code interprets this JSON response and prevents session closure, displaying the reason to the user so they can address the reported issues before attempting to exit again.

Session Lifecycle Management

The session-lifecycle-hook.mjs script handles the Start and End events through a unified implementation pattern. Like the Stop hook, it reads JSON from STDIN to receive context, but focuses on state management rather than blocking decisions. It interacts with lib/job-control.mjs to sort jobs and determine running status, ensuring that SessionEnd cleanup only cancels truly lingering processes.

Summary

  • Declarative configuration: Hooks are defined in plugins/codex/hooks/hooks.json, mapping SessionStart, SessionEnd, and Stop events to specific Node.js scripts with configurable timeouts.
  • Review gate capability: The Stop hook in stop-review-gate-hook.mjs can block session completion by emitting a JSON decision object with decision: "block" when Codex reviews find issues.
  • STDIN-based communication: All hooks receive context via JSON on STDIN, allowing them to access the current working directory, conversation history, and session identifiers.
  • Modular architecture: Hooks leverage shared libraries including lib/state.mjs for configuration and lib/job-control.mjs for job management, keeping script logic focused and maintainable.
  • Configurable enforcement: The review gate activates only when stopReviewGate is set to true in the plugin configuration, allowing flexible adoption.

Frequently Asked Questions

What hook types does the Codex plugin support?

The plugin implements three Claude Code hooks: SessionStart and SessionEnd for lifecycle management, and Stop for pre-completion validation. These are declared in plugins/codex/hooks/hooks.json and execute scripts located in plugins/codex/scripts/.

How does the Stop hook block a Claude session?

The Stop hook writes a JSON object to STDOUT containing {"decision": "block", "reason": "..."} when the Codex review returns output starting with BLOCK:. Claude Code reads this output and prevents the session from closing, keeping the conversation open so users can fix the reported issues.

Where is the hook configuration defined?

Hook mappings are declared in plugins/codex/hooks/hooks.json at the plugin root. This file specifies which commands to run for each hook type, including timeouts (5 seconds for lifecycle hooks, 900 seconds for the Stop hook) and the Node.js executable paths using the ${CLAUDE_PLUGIN_ROOT} variable.

Can I customize the timeout for hook execution?

Yes, the timeout is configurable per hook in hooks.json. The default configuration sets 5 seconds for SessionStart and SessionEnd hooks to ensure fast startup and shutdown, while the Stop hook uses 900 seconds (15 minutes) to accommodate thorough Codex reviews. Modify these values in the JSON configuration to adjust execution limits.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →