# How Hooks Integrate with Claude Code's Lifecycle in the Codex Plugin

> Learn how Codex plugin hooks like SessionStart, SessionEnd, and Stop integrate with Claude Code's lifecycle to manage variables, background jobs, and review gates.

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

---

**The Codex plugin for Claude Code uses three automated hook scripts—SessionStart, SessionEnd, and Stop—that intercept key lifecycle events to manage environment variables, terminate background jobs, and enforce optional review gates.**

The openai/codex-plugin-cc repository implements a robust integration layer that allows Codex to participate in Claude Code's session lifecycle through declarative hooks. These hooks automatically execute at critical moments—when sessions begin, end, or prepare to stop—enabling the plugin to synchronize state, clean up resources, and enforce safety checks. Understanding how hooks integrate with Claude Code's lifecycle in the Codex plugin is essential for developers who want to leverage background job execution and automated code review gates.

## The Three Lifecycle Hooks

The plugin defines three distinct hooks in [`plugins/codex/hooks/hooks.json`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/hooks/hooks.json) that Claude Code invokes automatically. Each hook serves a specific purpose in managing the session boundary and runtime behavior.

### SessionStart: Initializing the Environment

When a Claude Code session begins—typically after executing `/codex:setup`—Claude Code invokes `plugins/codex/scripts/session-lifecycle-hook.mjs` with the argument `SessionStart` and passes a JSON payload on **stdin**. This payload contains the `session_id` and `transcript_path`.

The `handleSessionStart` function extracts these values and exposes them via environment variables:

```javascript
// session-lifecycle-hook.mjs
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]);
}

```

This makes `CODEX_COMPANION_SESSION_ID` and `CLAUDE_TRANSCRIPT_PATH` available to downstream Codex commands, allowing background jobs to associate themselves with the current Claude Code session. The hook also persists any existing `CLAUDE_PLUGIN_DATA` across the session boundary.

### SessionEnd: Cleaning Up Resources

When a Claude Code session terminates—either because the user closed the session or the plugin called `/codex:cancel`—Claude Code invokes the same lifecycle script with the argument `SessionEnd`.

The hook performs three critical cleanup operations:

1. **Broker shutdown**: Loads persisted broker information via `loadBrokerSession` and sends a shutdown request using `sendBrokerShutdown`
2. **Job termination**: Calls `cleanupSessionJobs` to iterate over any jobs with status `"queued"` or `"running"` and terminates their process trees
3. **State removal**: Removes lingering session files and state entries

The termination logic appears in `session-lifecycle-hook.mjs`:

```javascript
// session-lifecycle-hook.mjs
for (const job of removedJobs) {
  const stillRunning = job.status === "queued" || job.status === "running";
  if (!stillRunning) continue;
  try {
    terminateProcessTree(job.pid ?? Number.NaN);
  } catch { /* ignore teardown failures */ }
}

```

This ensures that background Codex processes do not survive a closed Claude Code session, preventing resource leaks and zombie processes.

### Stop Review Gate: Intercepting Session Completion

If the user enables the review gate via `/codex:setup --enable-review-gate`, Claude Code invokes `plugins/codex/scripts/stop-review-gate-hook.mjs` right before completing a turn. This hook acts as a safety checkpoint that can block the session based on Codex feedback.

The hook execution flow follows these steps:

1. Retrieves the last Claude assistant message from the session context
2. Loads the `stop-review-gate` prompt template and interpolates the message content
3. Executes the companion script (`codex-companion.mjs`) as a child process with the constructed prompt
4. Parses the JSON return value for decision markers

The hook interprets lines starting with `ALLOW:` as permission to continue, while `BLOCK:` aborts the turn and surfaces the reason to the user:

```javascript
// stop-review-gate-hook.mjs
const review = runStopReview(cwd, input);
if (!review.ok) {
  emitDecision({
    decision: "block",
    reason: review.reason
  });
}

```

This creates an automated checkpoint where Codex can review Claude Code's output before the user sees it, enabling quality gates in the development workflow.

## How Claude Code Wires the Hooks Together

**Hook registration** occurs through [`plugins/codex/hooks/hooks.json`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/hooks/hooks.json), which declares the three events and maps them to their corresponding executable scripts. When the plugin installs, Claude Code reads this manifest and registers the handlers for the session lifecycle.

**Input protocol**: For `SessionStart` and `SessionEnd`, Claude Code passes a JSON payload via stdin with the following structure:

```json
{
  "hook_event_name": "SessionStart",
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/xyz.jsonl"
}

```

The `session-lifecycle-hook.mjs` script parses this input and dispatches to the appropriate handler based on the `hook_event_name` field.

**State persistence**: Both hooks rely on `plugins/codex/scripts/lib/state.mjs` to maintain job status, process IDs, and session mappings across the lifecycle. The broker lifecycle management in `plugins/codex/scripts/lib/broker-lifecycle.mjs` handles the Codex app-server startup and shutdown coordination, while `plugins/codex/scripts/lib/process.mjs` provides the `terminateProcessTree` utility for safe process cleanup.

## Practical Implementation Examples

### Enabling the Review Gate

To activate the stop review gate, execute:

```bash
/codex:setup --enable-review-gate

```

Once enabled, Claude Code automatically invokes `stop-review-gate-hook.mjs` before each turn completion, running a Codex review of the assistant's last response.

### Manually Canceling Background Jobs

While the `SessionEnd` hook automatically cleans up jobs, you can manually terminate a specific job during a session:

```bash
/codex:cancel <job-id>

```

This command triggers the same cleanup logic that the SessionEnd hook uses, calling `cleanupSessionJobs` to remove the specific job from state and terminate its process tree via `terminateProcessTree`.

### SessionStart Payload Handling

When Claude Code starts a session, it sends the initialization payload to the hook script. The following example shows how the hook processes this input to set up the environment:

```json
{
  "hook_event_name": "SessionStart",
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/xyz.jsonl"
}

```

The hook extracts these fields and writes them to the process environment, making them available to the companion script (`codex-companion.mjs`) when it executes Codex tasks.

## Summary

- **Three lifecycle hooks**—SessionStart, SessionEnd, and Stop—provide integration points between Claude Code and the Codex plugin, declared in [`plugins/codex/hooks/hooks.json`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/hooks/hooks.json).
- **SessionStart** exports `CODEX_COMPANION_SESSION_ID` and `CLAUDE_TRANSCRIPT_PATH` via `handleSessionStart` in `session-lifecycle-hook.mjs`, allowing background jobs to locate session resources.
- **SessionEnd** automatically terminates background Codex processes and shuts down the broker via `cleanupSessionJobs` and `sendBrokerShutdown`, preventing resource leaks.
- **Stop review gate** optionally intercepts turn completion via `stop-review-gate-hook.mjs`, using `codex-companion.mjs` to review Claude Code output and block the session if Codex returns a `BLOCK:` decision.
- **State management** relies on `lib/state.mjs` and `lib/broker-lifecycle.mjs` to persist job and broker information across the session boundary.

## Frequently Asked Questions

### How does the Codex plugin persist session information between Claude Code restarts?

The plugin uses `plugins/codex/scripts/lib/state.mjs` to maintain a persistent JSON store of job metadata—including process IDs, session IDs, and status—on disk. When Claude Code restarts and triggers the `SessionStart` hook, the plugin reads this state to reconnect with existing broker sessions or clean up stale jobs from previous sessions.

### What happens if the SessionEnd hook fails to terminate a background job?

The `terminateProcessTree` function in `plugins/codex/scripts/lib/process.mjs` wraps process termination in a try-catch block that silently ignores teardown failures. While the hook attempts to kill all running jobs marked as `"queued"` or `"running"`, exceptions during termination do not block the session closure, ensuring Claude Code can exit even if a process refuses to die immediately.

### Can I use the review gate without manually enabling it every session?

No, the review gate is opt-in per session. You must execute `/codex:setup --enable-review-gate` after starting Claude Code to activate the Stop hook interception. This flag is session-scoped; once the session ends, the review gate deactivates until explicitly enabled again in a new session.

### What is the difference between the Stop hook and manually canceling a job?

The **Stop hook** (`stop-review-gate-hook.mjs`) runs automatically before Claude Code completes a turn, reviewing the assistant's output to decide whether to allow the session to continue. **Manual cancellation** (`/codex:cancel`) explicitly terminates a specific background job during the session. The SessionEnd hook handles automatic cleanup of both scenarios when the Claude Code session closes.