# How Codex Thread IDs, Turn IDs, and Session IDs Are Correlated in the Claude Code Plugin

> Understand how Codex Thread IDs, Turn IDs, and Session IDs correlate in the Claude Code Plugin. Discover how these IDs track operations, exchanges, and sessions for the openai codex plugin cc.

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

---

**The openai/codex-plugin-cc correlates Thread IDs, Turn IDs, and Session IDs through a lifecycle where Thread IDs scope top-level Codex operations, Turn IDs track individual prompt-response exchanges, and Session IDs bind these to the originating Claude session via environment variables and persisted job records.**

The openai/codex-plugin-cc repository provides a Codex integration for Claude Code that manages AI-assisted reviews and tasks using three distinct identifiers. Understanding how Codex thread IDs, turn IDs, and session IDs interact is essential for tracking work, resuming threads, and filtering jobs by the current Claude session.

## The Three Identifier Types

### Thread ID

A **Thread ID** (e.g., `thr_1`) represents a unique Codex thread—the top-level container for a review or task. According to the source code in `plugins/codex/scripts/lib/codex.mjs`, this identifier originates from the Codex app-server when a thread is started via `thread/start` or resumed via `thread/resume`. The plugin captures this value in `TurnCaptureState.threadId` and persists it to job records through `tracked-jobs.createJobRecord`.

### Turn ID

A **Turn ID** identifies a single prompt-response exchange within a thread. The server generates this value during `turn/start` and references it in `turn/completed` notifications. As implemented in `codex.mjs`, the plugin stores the Turn ID in `TurnCaptureState.turnId` and maintains a `threadTurnIds` map to correlate incoming notifications with the correct turn using the `belongsToTurn` function.

### Session ID

The **Session ID** links Codex work to the specific Claude session that spawned it (e.g., `sess-native-transfer`). Derived from the Claude session file name (`*.jsonl`) when running `/codex:transfer`, this identifier propagates through the `CODEX_COMPANION_SESSION_ID` environment variable as defined in `tracked-jobs.mjs`. The plugin filters jobs by this value in `job-control.mjs` and `stop-review-gate-hook.mjs` to ensure users only see work belonging to the active Claude session.

## Correlation Flow and Lifecycle

The plugin correlates these identifiers through a six-stage lifecycle that spans from thread creation to job filtering:

1. **Creating a thread** – The `runAppServerReview` or `runAppServerTurn` functions call `startThread`, which sends a `thread/start` request to the app server. The server returns a `thread.id` that becomes the **Thread ID**:

   ```javascript
   // In codex.mjs (lines 33-35)
   const thread = await client.request("thread/start", buildThreadParams(cwd, options));
   const sourceThreadId = thread.thread.id;   // <-- Thread ID
   ```

2. **Capturing a turn** – The `captureTurn` function initializes a `TurnCaptureState` with the thread's ID and registers a notification handler. When the turn starts, the plugin extracts the **Turn ID** from the response:

   ```javascript
   // In codex.mjs (lines 85-90)
   state.turnId = response.turn?.id ?? null;      // <-- Turn ID
   state.threadTurnIds.set(state.threadId, state.turnId);
   ```

3. **Linking notifications** – Every incoming notification contains a `threadId` and optionally a `turn.id`. The `belongsToTurn` function validates whether a message belongs to the active turn by comparing the stored `threadTurnIds` map against the extracted turn ID:

   ```javascript
   // In codex.mjs (lines 96-104)
   const trackedTurnId = state.threadTurnIds.get(messageThreadId) ?? null;
   const messageTurnId = extractTurnId(message);
   return trackedTurnId === null || messageTurnId === null || messageTurnId === trackedTurnId;
   ```

4. **Storing progress** – The `createJobProgressUpdater` function receives progress events containing `threadId` and `turnId`. When these values change, the updater patches the persisted job record via `upsertJob`, ensuring the job file always carries the latest **Thread ID** and **Turn ID**:

   ```javascript
   // In tracked-jobs.mjs (lines 86-96)
   const updater = createJobProgressUpdater(workspaceRoot, jobId);
   updater({ message: "Running command", threadId, turnId, phase: "running" });
   ```

5. **Session ID linkage** – When transferring a Claude session via `/codex:transfer`, the plugin extracts the session name from the source file using `path.basename(sourcePath, ".jsonl")` and stores it in `CODEX_COMPANION_SESSION_ID`. The `createJobRecord` function adds this **Session ID** to job metadata if the variable is present:

   ```javascript
   // In tracked-jobs.mjs (lines 60-67)
   const sessionId = env[options.sessionIdEnv ?? SESSION_ID_ENV];
   return { ...base, createdAt: nowIso(), ...(sessionId ? { sessionId } : {}) };
   ```

6. **Filtering by session** – UI commands like `/codex:status` and `/codex:result` retrieve the current session ID via `getCurrentClaudeSessionId` and filter jobs accordingly. This ensures only Codex work tied to the active Claude session is displayed:

   ```javascript
   // In stop-review-gate-hook.mjs (lines 41-45)
   const sessionId = getCurrentSessionId(); // reads CODEX_COMPANION_SESSION_ID
   const jobsForSession = state.jobs.filter(j => j.sessionId === sessionId);
   ```

## Key Files and Architecture

The following files in the openai/codex-plugin-cc repository implement the identifier correlation logic:

| File | Relevant Concepts |
|------|-------------------|
| [`plugins/codex/scripts/lib/app-server-protocol.d.ts`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/scripts/lib/app-server-protocol.d.ts) | Type definitions for Thread ID, Turn ID, and related RPC payloads. |
| `plugins/codex/scripts/lib/codex.mjs` | Core logic for creating/resuming threads, capturing turns, and extracting IDs. |
| `plugins/codex/scripts/lib/tracked-jobs.mjs` | Job creation, persistence, and the `CODEX_COMPANION_SESSION_ID` handling. |
| `plugins/codex/scripts/lib/job-control.mjs` | Functions that filter jobs by the current Session ID. |
| `plugins/codex/scripts/stop-review-gate-hook.mjs` | Example of using the Session ID to locate relevant jobs for the review gate. |
| `plugins/codex/scripts/codex-companion.mjs` | Entry point that sets the Session ID and orchestrates thread/turn interactions. |

## Summary

- **Thread IDs** scope all work for a particular Codex operation and originate from the `thread/start` or `thread/resume` RPC calls in `codex.mjs`.
- **Turn IDs** distinguish individual prompt-response cycles within a thread, captured via `captureTurn` and validated through `belongsToTurn`.
- **Session IDs** link Codex activities to the originating Claude session via the `CODEX_COMPANION_SESSION_ID` environment variable, enabling per-session job filtering in `job-control.mjs`.
- The plugin persists all three identifiers through `TurnCaptureState` objects and job records, enabling precise tracking, resumption, and display of Codex activity across Claude sessions.

## Frequently Asked Questions

### How does the plugin distinguish between different turns in the same thread?

The plugin uses the `belongsToTurn` function in `codex.mjs` to compare the Turn ID extracted from incoming notifications against the Turn ID stored in the `threadTurnIds` map. This ensures that progress updates and completions are attributed to the correct prompt-response cycle within the active thread.

### Where does the Session ID come from when transferring a Claude session?

When you run `/codex:transfer`, the plugin extracts the Session ID from the Claude session file name (removing the `.jsonl` extension) using `path.basename(sourcePath, ".jsonl")`. This value is then stored in the `CODEX_COMPANION_SESSION_ID` environment variable and propagated to all child processes and job records.

### Can a job record contain multiple Turn IDs?

Yes, job records maintain a `threadTurnIds` map that associates Thread IDs with their corresponding Turn IDs. As the `createJobProgressUpdater` function in `tracked-jobs.mjs` receives progress events, it patches the job record with the current `threadId` and `turnId`, preserving the history of turns within the persisted job file.

### How does the plugin filter which jobs to display in the UI?

UI commands such as `/codex:status` and `/codex:result` call `getCurrentSessionId` to retrieve the value of `CODEX_COMPANION_SESSION_ID`. The plugin then filters the job list to include only records where `job.sessionId` matches the current session ID, ensuring users see only Codex work relevant to their active Claude session.