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

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 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.

// 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.

// 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.

// 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) 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.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →