How Codex Thread IDs, Turn IDs, and Session IDs Are Correlated in the Claude Code Plugin
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:
-
Creating a thread – The
runAppServerRevieworrunAppServerTurnfunctions callstartThread, which sends athread/startrequest to the app server. The server returns athread.idthat becomes the Thread ID:// In codex.mjs (lines 33-35) const thread = await client.request("thread/start", buildThreadParams(cwd, options)); const sourceThreadId = thread.thread.id; // <-- Thread ID -
Capturing a turn – The
captureTurnfunction initializes aTurnCaptureStatewith the thread's ID and registers a notification handler. When the turn starts, the plugin extracts the Turn ID from the response:// In codex.mjs (lines 85-90) state.turnId = response.turn?.id ?? null; // <-- Turn ID state.threadTurnIds.set(state.threadId, state.turnId); -
Linking notifications – Every incoming notification contains a
threadIdand optionally aturn.id. ThebelongsToTurnfunction validates whether a message belongs to the active turn by comparing the storedthreadTurnIdsmap against the extracted turn ID:// In codex.mjs (lines 96-104) const trackedTurnId = state.threadTurnIds.get(messageThreadId) ?? null; const messageTurnId = extractTurnId(message); return trackedTurnId === null || messageTurnId === null || messageTurnId === trackedTurnId; -
Storing progress – The
createJobProgressUpdaterfunction receives progress events containingthreadIdandturnId. When these values change, the updater patches the persisted job record viaupsertJob, ensuring the job file always carries the latest Thread ID and Turn ID:// In tracked-jobs.mjs (lines 86-96) const updater = createJobProgressUpdater(workspaceRoot, jobId); updater({ message: "Running command", threadId, turnId, phase: "running" }); -
Session ID linkage – When transferring a Claude session via
/codex:transfer, the plugin extracts the session name from the source file usingpath.basename(sourcePath, ".jsonl")and stores it inCODEX_COMPANION_SESSION_ID. ThecreateJobRecordfunction adds this Session ID to job metadata if the variable is present:// In tracked-jobs.mjs (lines 60-67) const sessionId = env[options.sessionIdEnv ?? SESSION_ID_ENV]; return { ...base, createdAt: nowIso(), ...(sessionId ? { sessionId } : {}) }; -
Filtering by session – UI commands like
/codex:statusand/codex:resultretrieve the current session ID viagetCurrentClaudeSessionIdand filter jobs accordingly. This ensures only Codex work tied to the active Claude session is displayed:// 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 |
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/startorthread/resumeRPC calls incodex.mjs. - Turn IDs distinguish individual prompt-response cycles within a thread, captured via
captureTurnand validated throughbelongsToTurn. - Session IDs link Codex activities to the originating Claude session via the
CODEX_COMPANION_SESSION_IDenvironment variable, enabling per-session job filtering injob-control.mjs. - The plugin persists all three identifiers through
TurnCaptureStateobjects 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →