# How the Codex Plugin Integrates with Claude Code Hooks (SessionStart, SessionEnd, Stop)

> Discover how the Codex plugin integrates with Claude Code hooks SessionStart SessionEnd and Stop using Node scripts for session management cleanup and review gates Explore the openai codex plugin cc repository

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: internals
- Published: 2026-08-05

---

**The Codex plugin registers three Claude Code lifecycle hooks—SessionStart, SessionEnd, and Stop—via [`plugins/codex/hooks/hooks.json`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/hooks/hooks.json), with each hook executing a Node script that manages session state, cleanup, and optional review gates.**

The **openai/codex-plugin-cc** repository implements deep integration with Claude Code's plugin architecture by hooking into session lifecycle events. These hooks enable the plugin to persist session metadata, gracefully shut down background Codex processes, and optionally gate session termination with automated code reviews. This article breaks down the exact mechanism, file paths, and execution flow for each hook.

## Hook Registration in [`hooks.json`](https://github.com/openai/codex-plugin-cc/blob/main/hooks.json)

All three hooks are declared in [`plugins/codex/hooks/hooks.json`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/hooks/hooks.json), which Claude Code reads during plugin initialization:

```json
{
  "description": "Optional stop-time review gate for Codex Companion.",
  "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
          }
        ]
      }
    ]
  }
}

```

Key implementation details:

- `${CLAUDE_PLUGIN_ROOT}` expands to the plugin's installation directory, ensuring portable script paths.
- **SessionStart** and **SessionEnd** use a 5-second timeout—sufficient for lightweight env-file operations.
- **Stop** uses a 900-second (15-minute) timeout to accommodate full Codex review cycles.

## SessionStart Hook: Capturing Session Context

When Claude Code initializes a new session, it fires **SessionStart** and pipes a JSON payload to `session-lifecycle-hook.mjs`. The `handleSessionStart` function (lines 77-82) extracts and persists three values:

```js
function handleSessionStart(input) {
  appendEnvVar(SESSION_ID_ENV, input.session_id);
  appendEnvVar(TRANSCRIPT_PATH_ENV, input.transcript_path);
  appendEnvVar(PLUGIN_DATA_ENV, process.env[PLUGIN_DATA_ENV]);
}

```

The script receives this input structure via stdin:

```json
{
  "session_id": "1234-abcd",
  "transcript_path": "/home/me/.claude/projects/1234-abcd.jsonl",
  "cwd": "/home/me/my-repo"
}

```

Environment variables written to the Claude env file:

| Variable | Purpose |
|----------|---------|
| `CODEX_COMPANION_SESSION_ID` | Correlates Codex jobs with the active Claude session |
| `CLAUDE_TRANSCRIPT_PATH` | Enables the `transfer` command to read conversation history |
| `CLAUDE_PLUGIN_DATA` | Passes plugin-specific state between hooks |

These variables enable later hooks to scope all Codex operations to the correct session.

## SessionEnd Hook: Graceful Teardown

The **SessionEnd** hook triggers `handleSessionEnd` (lines 83-114), which orchestrates a four-stage cleanup:

```js
async function handleSessionEnd(input) {
  const cwd = input.cwd || process.cwd();
  const brokerSession = loadBrokerSession(cwd) ?? null;
  const brokerEndpoint = brokerSession?.endpoint ?? null;

  if (brokerEndpoint) {
    await sendBrokerShutdown(brokerEndpoint);
  }

  cleanupSessionJobs(cwd, input.session_id || process.env[SESSION_ID_ENV]);
  teardownBrokerSession({ endpoint: brokerEndpoint, pidFile, logFile, sessionDir, pid, killProcess: terminateProcessTree });
  clearBrokerSession(cwd);
}

```

Cleanup sequence:

1. **Broker shutdown** – If a Codex App-Server broker is running, send it a graceful shutdown request.
2. **Job termination** – `cleanupSessionJobs` kills any queued or running Codex jobs matching the session ID.
3. **Process cleanup** – `teardownBrokerSession` terminates the broker process tree and removes pid/log files.
4. **State clearing** – `clearBrokerSession` deletes persisted broker metadata from disk.

This ensures no orphaned processes survive Claude session termination.

## Stop Hook: The Review Gate

The **Stop** hook implements an optional **review gate** that can block session termination if Codex finds issues in the final Claude response. Unlike the lifecycle hooks, it runs `stop-review-gate-hook.mjs` with a 15-minute timeout.

### Activation and Flow

The gate activates only when `config.stopReviewGate` is `true`. Execution proceeds through these stages:

1. **Configuration load** – `getConfig(workspaceRoot)` reads plugin settings.
2. **Job detection** – `listJobs` → `filterJobsForCurrentSession` → `sortJobsNewestFirst` identifies running Codex work.
3. **Prompt construction** – `buildStopReviewPrompt` injects `last_assistant_message` into [`plugins/codex/prompts/stop-review-gate.md`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/prompts/stop-review-gate.md).
4. **Review execution** – `runStopReview` spawns `codex-companion.mjs` with the assembled prompt.
5. **Decision parsing** – `parseStopReviewOutput` scans for `ALLOW:` or `BLOCK:` directives.

### Blocking Decision Output

When Codex returns `BLOCK:`, the hook emits this JSON to stdout:

```js
emitDecision({
  decision: "block",
  reason: "...explanation..."
});

```

Claude Code consumes this output and surfaces the rationale to the user, preventing session termination until issues are addressed.

## Simulated Hook Invocation

Test the **SessionStart** behavior manually by piping JSON to the hook script:

```bash
cat <<EOF | node plugins/codex/scripts/session-lifecycle-hook.mjs SessionStart
{
  "session_id": "1234-abcd",
  "transcript_path": "/home/me/.claude/projects/1234-abcd.jsonl",
  "cwd": "/home/me/my-repo"
}
EOF

```

This writes export statements to `$CLAUDE_ENV_FILE` (when set), making session data available to subprocesses.

Test the **Stop** gate similarly:

```bash
cat <<EOF | node plugins/codex/scripts/stop-review-gate-hook.mjs
{
  "session_id": "1234-abcd",
  "last_assistant_message": "Here is the final implementation..."
}
EOF

```

Output will be either `{"decision":"allow"}` or `{"decision":"block","reason":"..."}`.

## Key Implementation Files

| Purpose | Path |
|---------|------|
| Hook descriptor | [`plugins/codex/hooks/hooks.json`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/hooks/hooks.json) |
| Session lifecycle script | `plugins/codex/scripts/session-lifecycle-hook.mjs` |
| Stop-gate review script | `plugins/codex/scripts/stop-review-gate-hook.mjs` |
| Review prompt template | [`plugins/codex/prompts/stop-review-gate.md`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/prompts/stop-review-gate.md) |
| Job and configuration state | `plugins/codex/scripts/lib/state.mjs` |
| Broker management utilities | `plugins/codex/scripts/lib/broker-lifecycle.mjs` |

## Summary

- **SessionStart** persistence – Stores `session_id` and `transcript_path` in environment variables for cross-hook coordination.
- **SessionEnd** cleanup – Gracefully terminates the Codex broker and all session-associated jobs via `session-lifecycle-hook.mjs`.
- **Stop** gating – Optionally blocks session termination with AI-reviewed rationale when `stopReviewGate` is enabled.
- **Timeout scaling** – 5 seconds for lightweight lifecycle hooks, 15 minutes for comprehensive code review.
- **State isolation** – All operations are scoped to the active session ID, preventing cross-session interference.

## Frequently Asked Questions

### What triggers the SessionStart and SessionEnd hooks in Claude Code?

Claude Code fires **SessionStart** when a new conversation session begins and **SessionEnd** when the session terminates normally. These are automatic lifecycle events that require no user action. The plugin's `session-lifecycle-hook.mjs` receives session metadata via stdin and handles environment setup or teardown accordingly.

### How does the Stop hook differ from SessionEnd?

**Stop** runs when the user issues `/stop` or Claude Code initiates session termination, but *before* the session actually ends. This allows the plugin to intercept and potentially block the operation. **SessionEnd** runs after termination is confirmed and handles final cleanup. The Stop hook's 15-minute timeout supports lengthy Codex reviews; SessionEnd uses 5 seconds for rapid cleanup.

### Can the review gate be disabled?

Yes. Set `stopReviewGate: false` in the plugin configuration (managed via `getConfig` in `state.mjs`). When disabled, the Stop hook script still executes but skips the review logic and immediately allows termination.

### What happens if the broker fails to shut down gracefully?

The `handleSessionEnd` function in `session-lifecycle-hook.mjs` passes `terminateProcessTree` as `killProcess` to `teardownBrokerSession`. If `sendBrokerShutdown` times out or fails, the broker process and its descendants are forcefully terminated, and all associated state files are removed via `clearBrokerSession`.