# How Codex Thread IDs Are Tracked and Stored Across Sessions

> Learn how Codex thread IDs persist across sessions using a monotonic counter stored in a JSON state file. Ensure conversation continuity with OpenAI's Codex.

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

---

**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:

```javascript
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`](https://github.com/openai/codex-plugin-cc/blob/main/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.

```javascript
// 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:

```bash

# 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`, where `saveState()` writes JSON to disk and `loadState()` hydrates the state on startup.
- **Session continuity** is achieved through the `ensureThread()` lookup function, enabling the `codex resume` command 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`](https://github.com/openai/codex-plugin-cc/blob/main//tmp/.../codex-companion/project-a-abc123/state.json) and [`/tmp/.../codex-companion/project-b-def456/state.json`](https://github.com/openai/codex-plugin-cc/blob/main//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.