# What Is TurnCaptureState and How Does It Capture Codex Output?

> Learn about TurnCaptureState, the state management system in openai/codex-plugin-cc, that captures every Codex interaction by saving job data to JSON files.

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

---

**TurnCaptureState is the persistent state-management system in the openai/codex-plugin-cc repository that records every interaction with Codex—each request and response—by writing job data to JSON files in a per-workspace directory.**

The `codex-plugin-cc` plugin uses TurnCaptureState to ensure no Codex output is lost, even if the process crashes. This system decouples state handling from runtime execution, enabling replay, debugging, and retrieval of previous turns for the user interface.

## How TurnCaptureState Resolves and Manages State Directories

TurnCaptureState begins by establishing a unique storage location for each workspace. The `resolveStateDir` function in `plugins/codex/scripts/lib/state.mjs` handles this resolution, falling back to a temporary folder when necessary【/cache/repos/github.com/openai/codex-plugin-cc/main/plugins/codex/scripts/lib/state.mjs#L29-L44】.

This directory isolation ensures that Codex output captured in one workspace never interferes with another. The state directory becomes the root for all subsequent job file operations.

## Creating Job Entries for Each Turn

When a new turn begins, TurnCaptureState generates a unique job identifier and registers the job in the workspace state. The `generateJobId` function produces this identifier, while `upsertJob` creates or updates the job entry【/cache/repos/github.com/openai/codex-plugin-cc/main/plugins/codex/scripts/lib/state.mjs#L24-L27】【/cache/repos/github.com/openai/codex-plugin-cc/main/plugins/codex/scripts/lib/state.mjs#L29-L34】.

Each job tracks:
- **id**: Unique turn identifier
- **status**: Current state (`running`, `completed`, `failed`, etc.)
- **createdAt** and **updatedAt**: ISO timestamps for temporal ordering

The job list maintains a maximum of **50 entries**, with the most recent turn always at the front. Older jobs are automatically pruned to prevent unbounded growth【/cache/repos/github.com/openai/codex-plugin-cc/main/plugins/codex/scripts/lib/state.mjs#L80-L84】.

## Writing Codex Output to Persistent Storage

The core capture mechanism relies on two file operations:

1. **`writeJobFile`**: Serializes the complete Codex response—streamed output, metadata, and results—to a JSON file in the state directory【/cache/repos/github.com/openai/codex-plugin-cc/main/plugins/codex/scripts/lib/state.mjs#L66-L71】.
2. **`resolveJobLogFile`**: Optionally provides a path for append-only log files, suitable for streaming partial output during long-running turns【/cache/repos/github.com/openai/codex-plugin-cc/main/plugins/codex/scripts/lib/state.mjs#L83-L86】.

Both operations use `fs.writeFileSync` for atomic writes, guaranteeing that captured output survives unexpected termination.

## Practical Implementation Example

```javascript
import {
  upsertJob,
  writeJobFile,
  readJobFile,
  listJobs,
  resolveJobLogFile,
} from "./state.mjs";

// 1️⃣ Start a new turn
const jobId = generateJobId("codex");
upsertJob(process.cwd(), { id: jobId, status: "running" });

// 2️⃣ Capture Codex's streamed output (the `output` string comes from the Codex API)
writeJobFile(process.cwd(), jobId, { output });

// 3️⃣ Optionally write a log while the turn is running
fs.appendFileSync(resolveJobLogFile(process.cwd(), jobId), "some log line\n");

// 4️⃣ When the turn finishes, update the job status
upsertJob(process.cwd(), { id: jobId, status: "completed", finishedAt: new Date().toISOString() });

// 5️⃣ later – read the captured output
const captured = readJobFile(resolveJobFile(process.cwd(), jobId));
console.log(captured.output);

// 6️⃣ List recent turns
console.log(listJobs(process.cwd()));

```

## Key Files in the TurnCaptureState Architecture

| File | Purpose |
|------|---------|
| `plugins/codex/scripts/lib/state.mjs` | Core state-management utilities—directory resolution, job CRUD, and file I/O. The backbone of TurnCaptureState. |
| `plugins/codex/scripts/lib/codex.mjs` | Codex API wrapper; invokes `upsertJob` and `writeJobFile` to persist each turn. |
| `plugins/codex/scripts/lib/process.mjs` | Command execution orchestration; uses state helpers for turn persistence. |
| `plugins/codex/scripts/lib/job-control.mjs` | Higher-level job lifecycle management (start/stop, log handling). |
| `plugins/codex/scripts/lib/render.mjs` | UI rendering of stored job data for displaying previous Codex responses. |

These modules form a cohesive pipeline: the API layer captures output, the state layer persists it, and the render layer surfaces it to users.

## Summary

- **TurnCaptureState** persists every Codex interaction as a discrete job with unique ID, timestamps, and output content.
- State is stored per-workspace using `resolveStateDir` with temporary fallback, ensuring isolation and reliability.
- **Job lifecycle**: `generateJobId` → `upsertJob` → `writeJobFile` → status update, with automatic pruning to 50 jobs.
- Files are written atomically via `fs.writeFileSync`, making captured output crash-resistant.
- The architecture separates concerns across `state.mjs`, `codex.mjs`, `process.mjs`, `job-control.mjs`, and `render.mjs`.

## Frequently Asked Questions

### What happens if the Codex plugin crashes mid-turn?

The captured output persists. TurnCaptureState writes job files using `fs.writeFileSync` immediately upon receiving Codex output, so data is flushed to disk before acknowledgment. When the plugin restarts, `readJobFile` and `listJobs` can retrieve incomplete or completed turns from the state directory.

### How does TurnCaptureState handle multiple concurrent Codex requests?

Each turn receives a unique job ID via `generateJobId`, and state files are scoped to individual job IDs. Concurrent writes target distinct files, eliminating race conditions. The job list itself is read-modify-written atomically through `upsertJob`.

### What is the retention policy for captured turns?

The system retains a maximum of **50 jobs** per workspace. When `upsertJob` adds a new turn exceeding this limit, older entries are pruned from the front of the list. This cap prevents state directory bloat while preserving recent context【/cache/repos/github.com/openai/codex-plugin-cc/main/plugins/codex/scripts/lib/state.mjs#L80-L84】.

### Can I access captured Codex output from another process?

Yes. Because TurnCaptureState uses the filesystem as its backing store, any process with read access to the workspace's state directory can invoke `readJobFile` or `listJobs` from `plugins/codex/scripts/lib/state.mjs`. This enables external tooling, CI pipelines, or debugging scripts to inspect Codex interactions without plugin dependency.