How the OpenAI Codex Plugin Detects and Manages Concurrent Codex Sessions

The OpenAI Codex plugin detects concurrent sessions by writing a unique session ID to the CODEX_COMPANION_SESSION_ID environment variable, tags every job with that ID, and filters all operations by session to isolate work while cleaning up resources when sessions end.

The openai/codex-plugin-cc repository implements a robust concurrency model that allows multiple Codex sessions to run simultaneously without interfering with each other. By leveraging environment variables and per-job metadata, the plugin can detect and manage concurrent Codex sessions while sharing a common state store and preventing resource leaks.

Session Detection via Environment Variables

The plugin identifies each running Codex session through the CODEX_COMPANION_SESSION_ID environment variable (exposed internally as SESSION_ID_ENV). When a session starts, the session‑lifecycle‑hook (plugins/codex/scripts/session-lifecycle-hook.mjs) receives a JSON payload containing session_id from the Codex runtime.

The hook writes this value to the process environment using appendEnvVar(SESSION_ID_ENV, input.session_id):

// session-lifecycle-hook.mjs – invoked by Codex on session start
function handleSessionStart(input) {
  // input.session_id is supplied by the Codex runtime
  appendEnvVar(SESSION_ID_ENV, input.session_id);   // sets CODEX_COMPANION_SESSION_ID
}

All subsequent plugin processes inherit this variable, allowing any component to query process.env.CODEX_COMPANION_SESSION_ID to determine which session they belong to.

Job Tracking and Session Tagging

Every tracked job created by the plugin includes the current sessionId in its record. In plugins/codex/scripts/lib/tracked-jobs.mjs, the createJobRecord function adds the sessionId field when persisting a job:

import { createJobRecord } from "./tracked-jobs.mjs";

const job = createJobRecord({
  id: generateJobId(),
  title: "Run tests",
  workspaceRoot: resolveWorkspaceRoot(cwd)
});
/* job now contains: { ..., sessionId: process.env.CODEX_COMPANION_SESSION_ID } */

The state file (state.json) therefore holds a list of jobs, each tagged with the session that created it. This tagging mechanism enables the plugin to distinguish which work belongs to which session even when multiple sessions run concurrently.

Concurrent Session Isolation

When multiple sessions run in parallel, their jobs coexist in the same state file but remain distinct because each job carries its own sessionId. Any operation that needs to act on the current session filters the jobs list by this identifier.

For example, the stop‑review‑gate hook filters jobs to find only those matching the current session:

// stop-review-gate-hook.mjs (simplified)
const sessionJobs = state.jobs.filter(job => job.sessionId === sessionId);

Similarly, the job‑control utility in plugins/codex/scripts/lib/job-control.mjs selects jobs belonging to the current session by checking job.sessionId === process.env.CODEX_COMPANION_SESSION_ID, and the codex‑companion script returns only jobs for the active session when queried.

Session Teardown and Cleanup

On a SessionEnd event, the session‑lifecycle‑hook invokes cleanupSessionJobs, which loads the state, removes all jobs whose sessionId matches the ending session, and writes the trimmed list back to disk:

// Invoked by the SessionEnd hook
function cleanupSessionJobs(cwd, endingSessionId) {
  const state = loadState(cwd);
  const remainingJobs = state.jobs.filter(job => job.sessionId !== endingSessionId);
  saveState(cwd, { ...state, jobs: remainingJobs });
}

This ensures that terminated sessions do not leave orphaned job records in the shared state.

Broker Process Management

The plugin creates a separate broker process per workspace rather than per session. However, the broker lifecycle is tied to session state through utilities in plugins/codex/scripts/lib/broker-lifecycle.mjs (functions like ensureBrokerSession and teardownBrokerSession). When the final session ends, the broker is shut down to prevent stray processes from lingering, ensuring clean resource management across concurrent operations.

Summary

  • Environment Variable Detection: The plugin writes CODEX_COMPANION_SESSION_ID via session-lifecycle-hook.mjs to mark processes with their session identity.
  • Per-Job Tagging: Every job record includes sessionId via createJobRecord in tracked-jobs.mjs, enabling correlation of work to specific sessions.
  • Session Isolation: Components filter the job list by sessionId (as seen in stop-review-gate-hook.mjs and job-control.mjs) to operate only on relevant data.
  • Automatic Cleanup: The cleanupSessionJobs function purges jobs from state.json when their session ends, and the broker process terminates when the last session closes.

Frequently Asked Questions

How does the plugin distinguish between different Codex sessions?

The plugin distinguishes sessions by the CODEX_COMPANION_SESSION_ID environment variable. When a session starts, the lifecycle hook writes the runtime-provided session_id to this variable, and all child processes inherit it. Every job created thereafter captures this value in its sessionId field.

What happens to jobs when a Codex session ends?

When a session ends, the cleanupSessionJobs function loads the shared state, filters out all jobs where job.sessionId matches the ending session, and writes the remaining jobs back to state.json. This removes the terminated session's jobs while preserving work from concurrent sessions.

Can multiple sessions share the same broker process?

Yes, the plugin creates one broker process per workspace, not per session. Multiple concurrent sessions within the same workspace share a single broker. However, the broker is shut down only when the final active session ends, preventing resource leaks while allowing session reuse of the broker connection.

How does concurrent session isolation prevent cross-session interference?

The plugin isolates sessions by filtering all job operations through the sessionId field. Whether stopping jobs, checking review gates, or listing active work, components like codex-companion.mjs and job-control.mjs verify that job.sessionId matches process.env.CODEX_COMPANION_SESSION_ID before acting on a record, ensuring sessions cannot access or modify each other's data.

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 →