# How the Codex Plugin Tracks Resumable Tasks Across Claude Code Sessions Using Thread IDs

> Discover how the Codex plugin tracks resumable tasks across Claude Code sessions with thread IDs. Learn how it persists thread metadata for seamless conversation continuation.

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

---

**The Codex plugin persists thread metadata in a deterministic state file per workspace, enabling the `--resume-last` flag to retrieve the most recent thread ID and continue conversations across separate Claude Code sessions.**

The openai/codex-plugin-cc repository implements a companion CLI that integrates OpenAI Codex with Claude Code. To maintain conversation continuity even when the Node process restarts, the plugin uses a filesystem-backed state management system that tracks **thread IDs** across invocations.

## State Persistence Architecture

### Deterministic State Directory Resolution

The plugin resolves a unique storage location for each workspace using the `resolveStateDir` function in `plugins/codex/scripts/lib/state.mjs`. This path is derived from a SHA-256 hash of the workspace's canonical root directory. The system checks for the `CLAUDE_PLUGIN_DATA` environment variable first, falling back to a temporary directory if unset. This guarantees that every invocation of the plugin from the same repository targets the same **state file** regardless of the specific Claude Code session.

### The state.json Structure

Within the resolved directory, the plugin maintains a [`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json) file that contains a `jobs` array. Each entry in this array represents a thread object with properties including `id`, `name`, `preview`, `ephemeral`, `cwd`, `createdAt`, `updatedAt`, and the list of visible messages. By serializing the entire thread list to disk after every turn, the plugin ensures that **thread IDs** survive process termination.

## Thread Creation and Identification

### The nextThread Helper Function

When a new conversation begins, the plugin calls `nextThread(state, cwd, ephemeral)` to generate a fresh thread record. This function creates a thread object with a unique identifier (typically formatted as `thr_<n>`), assigns timestamps, and pushes the record to the front of the `state.jobs` array. The helper is defined in the runtime logic and exercised in `tests/runtime.test.mjs`.

```javascript
// Creating a new thread for the current workspace
const thread = nextThread(state, cwd, /*ephemeral=*/ false);
thread.name = "Codex Companion Task";
thread.preview = "Initial user prompt";
state.jobs.unshift(thread);  // Most recent first
saveState(cwd, state);       // Persist to disk

```

### Thread Properties and Metadata

Each thread stores its originating **workspace** path (`cwd`) and an **ephemeral** boolean flag. The `ephemeral` flag marks transient conversations that should not be resumed later. The `updatedAt` timestamp allows the resume logic to identify the most recent eligible thread when multiple threads exist in the state file.

## Resuming Tasks Across Sessions

### The --resume-last Flag Implementation

The CLI entry point in `plugins/codex/scripts/codex-companion.mjs` parses the `--resume-last` (or `--resume`) flag. When present, the script loads the existing state via `loadState(cwd)`, filters the `jobs` array to exclude **ephemeral** threads and entries from other workspaces, and selects the first matching thread (`state.jobs[0]` after filtering). If no eligible thread exists, the process reports an error; otherwise, it extracts the thread ID for the resume operation.

```javascript
// Resuming the latest eligible thread
const state = loadState(cwd);
const candidates = state.jobs.filter(t => t.cwd === cwd && !t.ephemeral);
candidates.sort((a, b) => b.updatedAt - a.updatedAt);
const latest = candidates[0];

if (!latest) {
  throw new Error("No previous Codex task thread was found.");
}

```

### Thread ID Propagation via RPC

Once identified, the thread ID is transmitted to the Codex backend through the `thread/resume` RPC method. All subsequent communication—including `turn/started` and `item/started` events—includes this **thread ID** in the request parameters, ensuring the backend attaches the new turn to the existing conversation history.

```javascript
// Sending the resume request to the Codex backend
send({
  method: "thread/resume",
  params: { threadId: latest.id }
});

```

## Session Isolation and Security

### Workspace-Based Isolation

The plugin isolates threads by **workspace** rather than by Claude Code session ID. Because the state directory is keyed to the repository's canonical path, threads created in one session remain accessible to any future session operating on the same repository. The `cwd` property in each thread record provides an additional validation layer to prevent cross-contamination between different projects.

### Ephemeral Thread Filtering

The resume logic explicitly filters out threads where `ephemeral` is `true`. This prevents temporary diagnostic or test conversations from polluting the resume history. Only non-ephemeral threads with matching workspace paths are considered valid candidates for the `--resume-last` operation.

## Summary

- The plugin uses a **deterministic state file** ([`state.json`](https://github.com/openai/codex-plugin-cc/blob/main/state.json)) stored per workspace to persist thread metadata across process restarts.
- The `nextThread` function generates unique **thread IDs** and maintains them in the `state.jobs` array with timestamps and workspace paths.
- The `--resume-last` flag in `codex-companion.mjs` loads the state, filters for non-ephemeral threads from the current workspace, and selects the most recent entry.
- Thread IDs are propagated via the `thread/resume` RPC call, allowing Claude Code to continue conversations from previous sessions.
- Workspace-based isolation and ephemeral filtering ensure that only relevant, persistent threads are eligible for resumption.

## Frequently Asked Questions

### Where is the Codex plugin state stored?

The state is stored in a directory resolved by `resolveStateDir` in `plugins/codex/scripts/lib/state.mjs`. This location is derived from a SHA-256 hash of the workspace root and is typically located under the path specified by the `CLAUDE_PLUGIN_DATA` environment variable, or a system temporary directory if the variable is unset.

### How does the plugin distinguish between different Claude Code sessions?

The plugin distinguishes contexts by **workspace** path rather than session ID. Each thread record stores its originating `cwd` (current working directory). When resuming, the code filters the `state.jobs` array to only include threads where `cwd` matches the current repository, ensuring isolation between different projects even if they are accessed from the same machine.

### What happens if I try to resume a task with no previous threads?

If the `--resume-last` flag is provided but `loadState` returns no eligible threads—either because the state file is missing or because all existing threads are ephemeral or belong to different workspaces—the `codex-companion.mjs` script throws an error: "No previous Codex task thread was found." This prevents the creation of orphaned resume requests.

### Can ephemeral threads be resumed later?

No. Threads created with the `ephemeral` flag set to `true` are explicitly excluded from the resume logic. The filtering code in the resume path checks `!t.ephemeral` before considering a thread as a candidate, ensuring that temporary or diagnostic conversations do not appear in the resume history.