How Codex Creates and Rotates Job Log Files for Background Tasks

Codex manages background task logs through a deterministic state directory system that automatically prunes entries to the most recent 50 jobs every time state is persisted, ensuring bounded disk usage while maintaining recent logs for debugging.

The openai/codex-plugin-cc repository implements a self-cleaning logging system for background operations such as code reviews, adversarial scans, and custom commands. Each execution runs as a job with dedicated log files stored in a workspace-specific state directory. Understanding how Codex creates and rotates these job log files helps developers debug failed tasks and manage disk space in long-running environments.

Resolving the State and Jobs Directory

Codex determines where to store logs through a hierarchical resolution process that begins with the workspace root and ends with a unique job-specific file path.

Locating the Workspace State Directory

The function resolveStateDir(cwd) in plugins/codex/scripts/lib/state.mjs (lines 29-44) constructs a deterministic directory path based on the current working directory. By default, Codex stores state under ${HOME}/.cache/.../codex-companion, though this can be overridden by setting the CLAUDE_PLUGIN_DATA environment variable. The function hashes the workspace path to create a unique slug, ensuring that different projects maintain isolated log environments.

Determining the Jobs Subdirectory

Once the state directory is established, resolveJobsDir(cwd) (lines 50-52 in state.mjs) simply appends the constant jobs folder to the resolved state path. This separation keeps job logs distinct from other state files like configuration or metadata stores.

Creating Job Log Files

Log creation happens on-demand when tasks begin writing output, with file paths resolved deterministically before any data is written.

Resolving Individual Log Paths

The resolveJobLogFile(cwd, jobId) function (lines 183-186 in state.mjs) returns a formatted path: ${jobsDir}/${jobId}.log. The file is created automatically upon the first write operation—typically via fs.appendFileSync or fs.writeFileSync—rather than being pre-allocated. This lazy creation ensures that empty log files never clutter the system if a job fails before producing output.

Writing Log Entries During Execution

Throughout task execution, particularly within plugins/codex/scripts/lib/job-control.mjs, Codex appends structured log entries to the resolved path. Each entry typically includes ISO 8601 timestamps, reasoning summaries from the language model, and assistant messages. The following pattern demonstrates how skill implementations write to job logs:

import { resolveJobLogFile } from "./plugins/codex/scripts/lib/state.mjs";
import fs from "node:fs";

const workspaceRoot = "/path/to/your/project";
const jobId = "review-2025-01-15-001";

const logPath = resolveJobLogFile(workspaceRoot, jobId);
// → /home/user/.cache/codex-companion/<hash>/jobs/review-2025-01-15-001.log

function log(jobId, message) {
  const path = resolveJobLogFile(workspaceRoot, jobId);
  const line = `[${new Date().toISOString()}] ${message}\n`;
  fs.appendFileSync(path, line, "utf8");
}

log(jobId, "Starting Codex review");
log(jobId, "Analyzing changed files for regression paths");

Rotating Job Log Files

Codex implements state-driven rotation, meaning cleanup occurs automatically during persistence operations rather than through time-based or size-based triggers.

Pruning Old Jobs (MAX_JOBS = 50)

When saveState(cwd, state) is called, it invokes pruneJobs(state.jobs) (lines 80-84 in state.mjs). This function sorts all jobs by their updatedAt timestamp and retains only the most recent 50 entries defined by the MAX_JOBS constant. Jobs falling outside this window are removed from the state array, making them eligible for file system cleanup.

Cleaning Up Orphaned Log Files

After pruning the state array, saveState performs physical file cleanup (lines 90-108 in state.mjs). The function identifies all files in the jobs directory matching ${id}.json and ${id}.log patterns, then deletes any files whose IDs no longer exist in the retained job list using the removeFileIfExists helper. This guarantees that every state save operation synchronizes the file system with the in-memory job registry.

import { loadState, saveState } from "./plugins/codex/scripts/lib/state.mjs";

let state = loadState(workspaceRoot);
// ... modify state.jobs array ...

// This call automatically prunes to 50 jobs and deletes orphaned .log files
saveState(workspaceRoot, state);

Summary

  • Deterministic paths: Codex uses resolveStateDir and resolveJobLogFile in state.mjs to create predictable log locations based on workspace paths and job IDs.
  • Lazy file creation: Log files are created on-demand when fs.appendFileSync is first called, not pre-allocated.
  • Hard limit: The system enforces a maximum of 50 jobs via MAX_JOBS in the pruneJobs function.
  • Synchronous cleanup: Log rotation happens during every saveState call, removing .log and .json files for jobs that exceed the retention limit.
  • Location flexibility: Default storage uses ${HOME}/.cache, but CLAUDE_PLUGIN_DATA overrides this for custom deployments.

Frequently Asked Questions

Where are Codex job log files stored by default?

By default, Codex stores job log files in ${HOME}/.cache/.../codex-companion under a hashed directory derived from the workspace path, specifically within a jobs subdirectory. You can override this location by setting the CLAUDE_PLUGIN_DATA environment variable before running Codex commands.

How many background job logs does Codex retain?

Codex retains logs for the 50 most recent jobs according to the MAX_JOBS constant defined in state.mjs. When saveState is called, the pruneJobs function sorts jobs by updatedAt and removes older entries from both the state registry and the file system.

Does Codex compress or archive old job logs before deletion?

No, Codex does not implement compression or archiving. The rotation mechanism in saveState (lines 90-108 in state.mjs) permanently deletes orphaned .log files using removeFileIfExists once they fall outside the 50-job retention window. If you need historical logs, you must back them up before state persistence occurs.

Can I manually trigger log rotation in Codex?

Yes, you can trigger rotation by calling saveState(cwd, state) from plugins/codex/scripts/lib/state.mjs. This function will prune jobs to the most recent 50 entries and delete any associated log files no longer present in the state. Simply loading the current state, making no modifications, and saving it will force an immediate cleanup of old logs.

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 →