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

> Discover how Codex plugin implements hooks using declarative JSON and Node.js scripts for automated session management and review gates. Understand lifecycle automation.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: deep-dive
- Published: 2026-07-03

---

**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`](https://github.com/openai/codex-plugin-cc/blob/main/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.

```json
{
  "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:

```javascript
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`](https://github.com/openai/codex-plugin-cc/blob/main/stop-review-gate.md)** and executes the Codex companion as a child process:

```javascript
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:

```javascript
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:

```javascript
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`](https://github.com/openai/codex-plugin-cc/blob/main/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`](https://github.com/openai/codex-plugin-cc/blob/main/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`](https://github.com/openai/codex-plugin-cc/blob/main/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`](https://github.com/openai/codex-plugin-cc/blob/main/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.