# How the OpenAI Codex Plugin Detects and Manages Concurrent Codex Sessions

> Learn how the OpenAI Codex plugin detects and manages concurrent Codex sessions using unique session IDs and job tagging for isolated operations and efficient resource cleanup.

- Repository: [OpenAI/codex-plugin-cc](https://github.com/openai/codex-plugin-cc)
- Tags: internals
- Published: 2026-07-29

---

**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)`:

```javascript
// 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:

```javascript
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`](https://github.com/openai/codex-plugin-cc/blob/main/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:

```javascript
// 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:

```javascript
// 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`](https://github.com/openai/codex-plugin-cc/blob/main/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`](https://github.com/openai/codex-plugin-cc/blob/main/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.