How Codex Thread IDs Are Tracked and Stored Across Sessions
Codex thread IDs are generated via a monotonic counter stored in a JSON state file that persists to disk based on the workspace root hash, enabling conversation continuity across process restarts.
The openai/codex-plugin-cc repository implements a deterministic persistence layer that assigns unique identifiers to conversation threads and maintains them across CLI sessions. Each thread receives a unique ID derived from an incrementing counter within the plugin's state object, which is automatically serialized to disk after every mutation. This architecture ensures that users can resume previous conversations using the codex resume command even after the process terminates.
Thread ID Generation Mechanics
Codex assigns thread IDs through a simple counter mechanism that guarantees uniqueness for the lifetime of a repository.
The nextThreadId Counter
Inside the plugin's state object, a counter tracks the next available identifier. When a new thread initializes, the system increments this counter and prefixes the value with "thr_" to produce human-readable IDs like thr_1, thr_2, and so on.
According to the test implementation in tests/fake-codex-fixture.mjs (lines 19-26), the nextThread() function constructs thread objects as follows:
const thread = nextThread(state, cwd, /*ephemeral=*/false);
// Returns: { id: "thr_1", cwd: "...", name: null, ... }
The ID sequence remains monotonic because the counter only increments, and the state file writes immediately after thread creation prevent race conditions.
State Persistence Architecture
The plugin isolates storage per repository using a hash-based directory structure derived from the workspace's real path.
Determining the State File Location
The resolveStateFile() function in plugins/codex/scripts/lib/state.mjs (lines 46-48) computes the persistence location by combining the environment variable CLAUDE_PLUGIN_DATA (or falling back to a temporary directory under /tmp/.../codex-companion) with a hash of the current working directory. This produces a path pattern of:
<stateRoot>/<slug>-<hash>/state.json
This approach ensures that the same repository accessed from different locations maintains separate storage contexts, while reopening from the same path consistently retrieves the existing thread history.
Loading and Saving State
The loadState(cwd) function in plugins/codex/scripts/lib/state.mjs (lines 58-74) handles state hydration by reading state.json if it exists and merging it with default state shapes for backward compatibility. Conversely, saveState(cwd, state) serializes the current state to disk after every thread creation or mutation.
// Load persisted state from disk
const state = loadState(cwd);
// Persist updated state after thread modification
saveState(cwd, state);
Thread Retrieval and Session Continuity
Once persisted, threads remain accessible across process boundaries through lookup mechanisms that validate ID existence.
The ensureThread Lookup
The ensureThread(state, threadId) function in plugins/codex/scripts/lib/state.mjs (lines 34-40) retrieves thread records by ID, throwing an error if the specified thread cannot be found. This validation ensures that resume operations target only valid, persisted conversations.
Resuming via CLI and RPC
The codex resume command leverages this persistence through the internal "thread/resume" message handler implemented in plugins/codex/scripts/lib/codex.mjs (lines 311-350). This handler uses ensureThread() to fetch the stored thread data and restore the conversation context:
# Resume the most recent thread for the current repository
$ codex resume
# Resume a specific thread by ID
$ codex resume thr_1
Internally, the system accesses state.lastTurnStart.threadId to determine which thread to resume when no explicit ID is provided.
Summary
- Codex thread IDs follow a
"thr_"prefix format generated by an incrementing counter (nextThreadId) stored in the plugin's state object. - Persistence isolation relies on workspace-path hashing to create unique storage directories per repository, preventing cross-contamination between projects.
- State serialization occurs in
plugins/codex/scripts/lib/state.mjs, wheresaveState()writes JSON to disk andloadState()hydrates the state on startup. - Session continuity is achieved through the
ensureThread()lookup function, enabling thecodex resumecommand to restore conversations across process restarts. - Monotonic uniqueness is guaranteed because the counter increments for each new thread and the state file writes atomically after thread creation.
Frequently Asked Questions
Where are Codex thread IDs physically stored on disk?
Thread IDs exist within a JSON state file located at <stateRoot>/<slug>-<hash>/state.json, where the path components derive from either the CLAUDE_PLUGIN_DATA environment variable or a fallback temporary directory combined with a hash of the workspace root. This file contains the nextThreadId counter and the complete threads array.
How does the plugin prevent thread ID collisions between different repositories?
The plugin uses resolveStateFile() to generate repository-specific storage paths by hashing the workspace's real path. This ensures that /tmp/.../codex-companion/project-a-abc123/state.json and /tmp/.../codex-companion/project-b-def456/state.json remain isolated, even when both repositories exist on the same machine.
What happens to thread IDs if the state file is deleted?
If the state file is removed manually or corrupted, loadState() returns a default state object with nextThreadId reset to 1. Consequently, the next created thread will receive thr_1 again, though this collision poses no immediate conflict since the previous threads no longer exist in the new state.
Can ephemeral threads be resumed across sessions?
No. The nextThread() function accepts an ephemeral boolean parameter. When set to true, the thread is not added to state.threads and saveState() is not invoked, meaning the thread ID is not persisted to disk and cannot be retrieved after the process terminates.
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 →