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
resolveStateDirandresolveJobLogFileinstate.mjsto create predictable log locations based on workspace paths and job IDs. - Lazy file creation: Log files are created on-demand when
fs.appendFileSyncis first called, not pre-allocated. - Hard limit: The system enforces a maximum of 50 jobs via
MAX_JOBSin thepruneJobsfunction. - Synchronous cleanup: Log rotation happens during every
saveStatecall, removing.logand.jsonfiles for jobs that exceed the retention limit. - Location flexibility: Default storage uses
${HOME}/.cache, butCLAUDE_PLUGIN_DATAoverrides 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →