How Thread Persistence and Naming Work in OpenAI Codex Companion

TLDR: The OpenAI Codex Companion persists conversation threads to a JSON file in a hashed workspace directory, allowing users to resume sessions after closing the IDE.

The openai/codex-plugin-cc repository implements a lightweight thread persistence system that tracks every interaction in durable storage. This architecture enables developers to pause coding sessions, close their IDE, and resume conversations exactly where they left off. Understanding how thread persistence and naming function reveals the mechanisms behind session continuity and thread management.

Where Threads Are Stored

All thread information is stored in a JSON file called state.json inside a per-workspace state directory. The system resolves this directory through the resolveStateDir(cwd) function defined in plugins/codex/scripts/lib/state.mjs.

This function hashes the workspace root to create a unique path:

/tmp/codex-companion/<workspace‑slug>-<hash>/state.json

On process startup, loadState reads this file. If the file is missing or corrupted, the system falls back to an empty defaultState. The state file is written atomically using fs.writeFileSync each time a thread is modified, ensuring data integrity even if the process terminates unexpectedly.

Thread Data Structure

The threads array in state.json contains thread objects with specific properties:

  • id – Unique thread identifier (e.g., thr_1).
  • name – Optional human-readable name set via the thread/name/set command.
  • preview – Short excerpt of the first user message for UI lists.
  • ephemeral – Boolean flag for temporary threads that are not persisted across sessions.
  • createdAt and updatedAt – ISO timestamps for ordering and tracking.
  • cwd – The working directory associated with the thread.
  • visibleMessages – Array of messages displayed in the UI.

The array is maintained in sorted order by updatedAt (newest first), enabling O(1) access to the latest thread.

Creating a New Thread

When a client sends a thread/start request, the handler in plugins/codex/scripts/lib/render.mjs executes the nextThread function. This generates a fresh ID (thr_<counter>), timestamps the creation, and pushes the new thread onto state.threads.

The implementation immediately persists the change through updateState → saveState:

case "thread/start": {
  const thread = nextThread(state, message.params.cwd, message.params.ephemeral);
  // ... reply with buildThread(thread) ...
  send({ method: "thread/started", params: { thread: { id: thread.id } } });
}

Starting a Thread Example

await codex.send({
  method: "thread/start",
  params: { cwd: process.cwd(), ephemeral: false, model: "gpt-5.4" }
});
// Returns: { thread: { id: "thr_1", ... } }

Naming Threads

Thread names are mutable and persisted across sessions. The client issues a thread/name/set request to update the display name:

case "thread/name/set": {
  const thread = ensureThread(state, message.params.threadId);
  thread.name = message.params.name;       // Persisted name
  thread.updatedAt = now();
  // ... reply with updated thread ...
}

The ensureThread function validates that the thread ID exists, throwing an unknown thread error if not found. After assignment, the system saves the entire state back to disk.

Setting a Thread Name Example

await codex.send({
  method: "thread/name/set",
  params: { threadId: "thr_1", name: "Feature X Refactor" }
});
// thread.name is now persisted in state.json

Listing and Resuming Threads

The system provides two primary methods for thread retrieval:

thread/list filters threads by cwd and optional search terms, returning the array sorted by updatedAt. This powers the "Recent threads" UI view.

thread/resume looks up a thread by ID, updates its timestamp to the current time, and returns the full thread object including visibleMessages. The client can then replay these messages to reconstruct the conversation state.

Listing and Resuming Examples

// List recent threads
const { data: threads } = await codex.send({
  method: "thread/list",
  params: { cwd: process.cwd(), searchTerm: "" }
});
// Returns array ordered by updatedAt

// Resume a specific thread
await codex.send({
  method: "thread/resume",
  params: { threadId: "thr_1", model: "gpt-5.4" }
});
// Returns full thread object with visibleMessages

Persistence Guarantees and Limits

The persistence layer guarantees atomic writes through synchronous file operations. While pruneJobs maintains a maximum of 50 job files, thread entries have no hard limit—they persist indefinitely until explicitly deleted by the user.

Ephemeral threads (those with ephemeral: true) bypass persistence and are discarded when the session ends, making them suitable for temporary design challenges or throwaway experiments.

Summary

  • Threads are stored in state.json within a hashed workspace directory resolved by resolveStateDir in plugins/codex/scripts/lib/state.mjs.
  • The thread/start command in render.mjs initializes new threads with unique IDs and timestamps.
  • thread/name/set updates human-readable names through ensureThread validation and immediate disk persistence.
  • thread/list and thread/resume provide filtered retrieval and session continuity by managing visibleMessages.
  • All state changes are written atomically using fs.writeFileSync, ensuring durability across process restarts.

Frequently Asked Questions

Where is thread data physically stored?

Thread data is stored in a JSON file named state.json located at /tmp/codex-companion/<workspace-slug>-<hash>/state.json. The exact path is determined by hashing the workspace root directory via resolveStateDir in plugins/codex/scripts/lib/state.mjs.

How do I rename an existing thread?

Send a thread/name/set request with the thread ID and desired name. The system validates the ID through ensureThread and immediately persists the change to state.json, making the name available across IDE restarts.

What is the difference between ephemeral and persistent threads?

Persistent threads are saved to disk and available after restarting the IDE. Ephemeral threads (created with ephemeral: true) exist only in memory and are discarded when the session ends, making them ideal for temporary experiments.

Is there a limit to how many threads can be saved?

Thread entries have no hard limit in the state file. However, the system prunes job files to a maximum of 50 through pruneJobs. Threads persist until explicitly deleted by the user, regardless of age.

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 →