How the Codex Plugin Resolves State Directory Locations in openai/codex-plugin-cc
The Codex plugin derives a unique state directory by hashing the canonicalized workspace root path and combining it with a sanitized directory slug, storing files under $CLAUDE_PLUGIN_DATA/state or falling back to a temporary directory.
The openai/codex-plugin-cc repository implements a deterministic file resolution system to persist runtime data across sessions. Understanding how the Codex plugin resolves state directory locations is essential for configuring persistent storage and avoiding collisions between multiple workspaces.
Step-by-Step State Directory Resolution
The resolution logic lives in plugins/codex/scripts/lib/state.mjs and executes a six-step algorithm to ensure each workspace receives a collision-free storage location.
Locating the Workspace Root
The process begins with resolveWorkspaceRoot(cwd) defined in plugins/codex/scripts/lib/workspace.mjs (lines 3–9). This function traverses up the directory tree from the supplied cwd until it identifies a Git repository root. If no Git repository is found, it falls back to the supplied cwd value.
Canonicalizing the Path
To ensure stability, the discovered root passes through fs.realpathSync.native (lines 30–36 in state.mjs). This resolves any symbolic links, guaranteeing that the same physical directory always maps to the same state identifier regardless of access method.
Generating the Directory Slug and Hash
The plugin creates a human-readable prefix by sanitizing the workspace basename, converting non-alphanumeric characters to hyphens (lines 38–40). It then computes a SHA-256 hash of the canonical path and retains the first 16 hexadecimal characters (lines 40–41). This hash ensures uniqueness even when two workspaces share identical names.
Selecting the Base Directory via Environment Variable
The parent directory for state files depends on the CLAUDE_PLUGIN_DATA environment variable (lines 10–13). When defined, the plugin uses $CLAUDE_PLUGIN_DATA/state as the root. Otherwise, it falls back to os.tmpdir() under the subdirectory codex-companion (lines 41–43).
Constructing the Final Path
The final state directory follows this pattern:
<stateRoot>/<slug>-<hash>
As implemented in lines 42–44 of state.mjs, <stateRoot> is either the environment-based path or the temporary directory, resulting in paths like /tmp/codex-companion/myproj-1a2b3c4d5e6f7g8h.
Helper Functions for State Access
Once resolveStateDir establishes the base directory, specialized helpers construct full paths for specific resources:
resolveStateFile(cwd)– Returns the path tostate.jsonresolveJobsDir(cwd)– Returns the jobs subdirectoryresolveJobFile(cwd, jobId)– Returns the path for a specific job fileresolveJobLogFile(cwd, jobId)– Returns the path for job-specific logs
These functions simply join filenames to the computed state directory.
Implementation Examples
Locate the persistent state directory for the current process:
import { resolveStateDir, resolveStateFile } from "./plugins/codex/scripts/lib/state.mjs";
const cwd = process.cwd(); // any directory inside the workspace
const stateDir = resolveStateDir(cwd); // e.g. /tmp/codex-companion/myproj-1a2b3c4d5e6f7g8h
const stateFile = resolveStateFile(cwd); // e.g. /tmp/.../state.json
console.log("State directory:", stateDir);
console.log("State file:", stateFile);
Override the base location using the CLAUDE_PLUGIN_DATA environment variable:
process.env.CLAUDE_PLUGIN_DATA = "/var/lib/codex-plugin";
import { resolveStateDir } from "./plugins/codex/scripts/lib/state.mjs";
const dir = resolveStateDir("/home/user/my-repo");
console.log(dir);
// → /var/lib/codex-plugin/state/my-repo-<hash>
Retrieve directories and paths for job-specific files:
import { resolveJobsDir, resolveJobFile } from "./plugins/codex/scripts/lib/state.mjs";
const jobsDir = resolveJobsDir(cwd);
const jobId = "job-abc123";
const jobFile = resolveJobFile(cwd, jobId);
console.log("Jobs directory:", jobsDir);
console.log("Job file path:", jobFile);
Summary
- The Codex plugin computes state directories by walking up to the Git root via
workspace.mjsand canonicalizing paths withfs.realpathSync.nativeto handle symlinks. - Directory names combine a sanitized slug with a 16-character SHA-256 hash of the canonical path, preventing collisions between identically named workspaces.
- The
CLAUDE_PLUGIN_DATAenvironment variable overrides the default temporary directory (os.tmpdir()/codex-companion) as the storage root. - Helper functions in
state.mjsprovide convenient accessors forstate.json, job directories, and log files.
Frequently Asked Questions
What happens if the workspace is not a Git repository?
If resolveWorkspaceRoot in workspace.mjs fails to locate a Git repository, it falls back to the cwd parameter provided to the function. The state directory is then derived from this fallback path, ensuring the plugin still functions outside of version-controlled projects.
Can I customize where the Codex plugin stores its state files?
Yes. Set the CLAUDE_PLUGIN_DATA environment variable to your preferred base directory. The plugin automatically appends /state to this path and stores all persistent data there, bypassing the default temporary directory location.
How does the plugin prevent state directory collisions between workspaces with the same name?
The plugin computes a SHA-256 hash of the canonicalized absolute path and appends the first 16 hexadecimal characters to the directory name. Since absolute paths are unique, this hash guarantees distinct state directories even for workspaces sharing identical basenames.
Where are job-specific files stored within the state directory?
Job files reside in a jobs subdirectory within the computed state directory. The resolveJobFile and resolveJobLogFile functions in plugins/codex/scripts/lib/state.mjs generate specific paths by joining the job ID to this subdirectory, returning fully qualified paths for job data and 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 →