# How Thread Persistence and Ephemeral Threads Work in the Codex Plugin Architecture

> Understand thread persistence and ephemeral threads in Codex plugins. Learn how the ephemeral parameter controls thread lifespan beyond the current turn.

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

---

**Thread persistence in the Codex plugin is controlled by the `ephemeral` parameter sent in ThreadStart requests—when set to `false`, threads survive beyond the current turn; when `true` (the default), threads are automatically deleted after completion.**

The openai/codex-plugin-cc repository implements a lightweight RPC layer that communicates with the Codex app-server to manage conversation state. Every review or task run creates a thread on the server, but the plugin client—not the server—decides whether that thread persists. This article breaks down the internal mechanics of thread lifecycle management in `plugins/codex/scripts/lib/codex.mjs`.

## Thread Creation and the Ephemeral Flag

The `startThread` function initiates all thread lifecycles by calling the Codex RPC endpoint `thread/start`. The critical parameter `ephemeral` is constructed by `buildThreadParams`, which supplies a **default value of `true`** unless explicitly overridden.

```javascript
// plugins/codex/scripts/lib/codex.mjs
function buildThreadParams(cwd, options = {}) {
  return {
    cwd,
    // ...
    ephemeral: options.ephemeral ?? true  // defaults to ephemeral
  };
}

```

This default ensures that one-off operations don't leave orphaned threads on the server. The `??` operator guarantees that even an explicit `undefined` falls back to `true`.

## Creating Persistent Threads with `persistThread`

When the CLI requires a thread that survives for later resumption, it passes `persistThread: true` to `runAppServerTurn`. The function translates this flag into `ephemeral: false` in the thread parameters.

```javascript
// plugins/codex/scripts/lib/codex.mjs
// Inside runAppServerTurn:
const response = await startThread(client, cwd, {
  // ...
  ephemeral: options.persistThread ? false : true,
  threadName: options.persistThread ? options.threadName : options.threadName ?? null
});

```

The **conditional logic** ensures:
- `persistThread: true` → `ephemeral: false` (thread persists)
- `persistThread: false` or absent → `ephemeral: true` (thread ephemeral)

The `threadName` parameter receives special handling for persistent threads, allowing users to assign meaningful identifiers to long-running conversations.

## Resuming Persistent Threads

Resumed threads enforce `ephemeral: false` unconditionally. The `resumeThread` call in `runAppServerTurn` hardcodes this value to prevent accidental deletion of existing conversation history.

```javascript
// plugins/codex/scripts/lib/codex.mjs (lines 1116-1118)
const response = await resumeThread(client, options.resumeThreadId, cwd, {
  // ...
  ephemeral: false
});

```

This **guarantees thread integrity**—a resumed thread can never be converted to ephemeral status mid-conversation, protecting against data loss.

## Ephemeral Thread Behavior

When `ephemeral: true` (explicit or default), the Codex app-server automatically garbage-collects the thread after the turn completes. This design optimizes for:

- **Code reviews** where no follow-up is expected
- **CI/CD integrations** where transient analysis is sufficient
- **Resource conservation** on the app-server

The plugin does not track ephemeral threads in local state—once the turn finishes, no record remains client-side.

## State Tracking and Verification

The `captureTurn` function constructs a `TurnCaptureState` object that persists job metadata and thread identifiers to disk via `plugins/codex/scripts/lib/state.mjs`. While the `ephemeral` flag itself is not stored in state, thread persistence is **implicitly verified** by presence checks against the server.

The test suite validates this behavior through fake server state inspection:

```javascript
// tests/runtime.test.mjs (line 238)
// Verifies persistent thread creation:
assert.strictEqual(fakeState.threads[0].ephemeral, false);

```

This assertion confirms that `persistThread: true` correctly propagates to the RPC layer.

## Practical Usage Examples

### Start a persistent task thread

```javascript
await runAppServerTurn(cwd, {
  prompt: "investigate flaky test",
  persistThread: true,          // thread survives after turn
  threadName: "Flaky-Test-Investigation"
});
// Under the hood: ephemeral: false sent to thread/start

```

### Start an ephemeral review thread (default)

```javascript
await runAppServerReview(cwd, {
  delivery: "inline"
  // no persistThread flag → ephemeral: true automatic
});
// Thread deleted after review completion

```

### Resume a persistent thread

```javascript
await runAppServerTurn(cwd, {
  resumeThreadId: "thr_42"     // previous persistent thread
  // ephemeral: false enforced for resume
});

```

## Key Source Files

| File | Purpose |
|------|---------|
| `plugins/codex/scripts/lib/codex.mjs` | Core RPC wrapper—implements `startThread`, `resumeThread`, `buildThreadParams`, and `ephemeral` flag logic |
| `plugins/codex/scripts/lib/state.mjs` | Disk persistence for job state and thread metadata |
| `tests/runtime.test.mjs` | Validates thread persistence through `fakeState.threads` inspection |
| `plugins/codex/scripts/codex-companion.mjs` | CLI entry point invoking `runAppServerTurn` and `runAppServerReview` |

## Summary

- **Default behavior**: All threads are **ephemeral** (`ephemeral: true`) unless explicitly requested otherwise
- **Persistence trigger**: The `persistThread: true` option in `runAppServerTurn` sets `ephemeral: false`
- **Resume protection**: Resumed threads always use `ephemeral: false` to preserve conversation history
- **Server-side cleanup**: The Codex app-server handles actual thread deletion based on the flag
- **No client-side tracking**: Ephemeral threads leave no local state; persistent threads are tracked via `TurnCaptureState`

## Frequently Asked Questions

### How does the Codex plugin decide whether to keep a thread after a task completes?

The plugin examines the `persistThread` option passed to `runAppServerTurn`. When `true`, it sends `ephemeral: false` to the `thread/start` RPC; otherwise it defaults to `ephemeral: true`. The app-server then handles cleanup based on this flag.

### Can I convert an ephemeral thread to persistent after creation?

No. The `ephemeral` flag is set at thread creation time via `startThread` or `resumeThread`. To persist work, you must specify `persistThread: true` when initiating the task, or use `resumeThreadId` with an already-persistent thread.

### Where is thread persistence state stored in the Codex plugin?

Persistent thread metadata is written to disk via `plugins/codex/scripts/lib/state.mjs` as part of `TurnCaptureState`. The `ephemeral` flag itself is not stored—instead, the presence of a thread ID in state implies persistence, while its absence indicates an ephemeral thread that was cleaned up.

### What happens if I resume a thread without specifying persistence options?

The `resumeThread` function hardcodes `ephemeral: false` regardless of caller options. This ensures that resuming a thread never accidentally deletes existing conversation history on the Codex app-server.