How to Manage Multiple Concurrent Codex Sessions Effectively
The Codex plugin for Claude Code isolates concurrent sessions using a per-session bookkeeping system centered on the CODEX_COMPANION_SESSION_ID environment variable and filtered state persistence.
The openai/codex-plugin-cc repository provides a lightweight but robust architecture for running multiple Claude Code sessions simultaneously without job interference. By leveraging environment-scoped session identifiers and disk-based state management, the plugin ensures that background Codex jobs remain isolated to their originating terminal sessions while remaining observable through filtered commands.
Understanding the Session Isolation Architecture
The plugin implements session isolation through four core modules that handle lifecycle events, job persistence, and state filtering.
Session Lifecycle Hooks (session-lifecycle-hook.mjs)
The session-lifecycle-hook.mjs module manages the beginning and end of Claude Code sessions. On startup, it writes the generated session identifier to the CODEX_COMPANION_SESSION_ID environment variable (defined at line 20). When a session ends, the cleanupSessionJobs function (lines 42-74) terminates any still-running processes belonging to that specific session and removes their entries from the state file, preventing resource leaks across sessions.
Job Tracking and ID Generation (tracked-jobs.mjs)
Every background job receives automatic session attribution through tracked-jobs.mjs. The module re-exports SESSION_ID_ENV (line 6) and the createJobRecord function (lines 60-67) automatically injects the current sessionId into each job payload. This ensures that when you spawn a background review with /codex:review --background, the job record carries the originating session's identifier regardless of when or where you check its status.
Persistent State Management (state.mjs)
Job metadata persists under a per-workspace directory structure managed by state.mjs. The resolveStateDir function (lines 29-44) constructs a deterministic path based on the workspace location and optional CLAUDE_PLUGIN_DATA root, storing all data in ~/.codex/state/.... This stateless approach means you can restart Claude Code or the underlying Node process without losing job information, as everything resides on disk rather than in process memory.
Job Control and Filtering (job-control.mjs)
The job-control.mjs module provides session-scoped visibility through filterJobsForCurrentSession (lines 19-25). This function narrows the global job list to entries matching the current environment's CODEX_COMPANION_SESSION_ID. The buildStatusSnapshot function (lines 13-31) merges this filter with the complete job list to power the /codex:status command, defaulting to the current session while supporting an all:true option for cross-session debugging.
How Session Isolation Works in Practice
The system operates through a five-step flow that maintains strict boundaries between concurrent sessions:
-
Session initialization – When Claude Code launches,
handleSessionStart(lines 77-81) generates a unique session ID and writes it toCODEX_COMPANION_SESSION_IDin the environment. -
Job creation – Every slash command that spawns background work calls
createJobRecord, which automatically tags the job with the current session ID from the environment. -
Status queries – The
/codex:statuscommand usesbuildStatusSnapshotwithfilterJobsForCurrentSession, ensuring you only see jobs belonging to your current terminal session. -
Session termination –
handleSessionEndinvokescleanupSessionJobsto locate all jobs matching the ending session's ID, terminate their process trees viaterminateProcessTree, and purge their records fromstate.json. -
Concurrent operation – Because each session carries a unique identifier and the state store filters by that token, multiple sessions can simultaneously run
/codex:review --backgroundwithout job records intermingling.
Practical Examples for Concurrent Workflows
The following patterns demonstrate how to leverage session isolation when working across multiple terminals or CI pipelines.
Running Multiple Background Jobs
From within a single Claude Code session, spawn independent background reviews:
# Job A and B both belong to the current session X
codex:review --background # → creates job A
codex:review --background # → creates job B
Working Across Separate Sessions
Open a second terminal to start a distinct session with its own isolated jobs:
# In terminal 2: new session Y with automatic ID generation
codex:review --background # → creates job C (session Y)
# Check status in terminal 2 - sees only job C
codex:status
# Check status in terminal 1 - sees jobs A and B but not C
codex:status
Viewing All Sessions for Debugging
Administrators can bypass session filtering to see the global job state:
# Shows jobs A, B, and C across all sessions
codex:status --all
Retrieving Results and Cleanup
Access specific job outputs and manage lifecycle:
# Get final output from any job in your current session
codex:result <job-id>
# Cancel a running job
codex:cancel <job-id>
Scripting with Manual Session IDs
For CI pipelines or automated workflows, manually export the session identifier before running commands:
export CODEX_COMPANION_SESSION_ID=$(uuidgen)
codex:review --background
codex:status
Summary
- Environment-based isolation: The
CODEX_COMPANION_SESSION_IDvariable insession-lifecycle-hook.mjsguarantees every session carries a unique token that propagates to all child jobs. - Automatic job tagging: The
createJobRecordfunction intracked-jobs.mjsensures every background job persists with its originating session ID, preventing cross-session attribution errors. - Disk-based state: The
resolveStateDirimplementation instate.mjsstores job metadata under~/.codex/state/, making the system resilient to process restarts. - Session-scoped queries: The
filterJobsForCurrentSessionfunction injob-control.mjsdefaults/codex:statusto show only the current session's jobs while supporting--allfor global visibility. - Clean resource reclamation: The
cleanupSessionJobsfunction automatically terminates orphaned processes and purges stale records when a session ends.
Frequently Asked Questions
How does the Codex plugin prevent jobs from one session appearing in another?
The plugin filters all job queries through filterJobsForCurrentSession in job-control.mjs, which compares the CODEX_COMPANION_SESSION_ID environment variable against the sessionId field stored in each job record. Since session-lifecycle-hook.mjs generates a unique UUID for each Claude Code session and tracked-jobs.mjs embeds this ID into every new job, the default /codex:status command only displays jobs matching your current terminal's session token.
Can multiple Claude Code sessions run background jobs simultaneously without conflicts?
Yes. Because the state file (state.json) managed by state.mjs stores jobs from all sessions and each job carries its own sessionId, concurrent sessions can spawn background processes independently. The buildStatusSnapshot function separates these by session ID while maintaining a single global state file, allowing truly parallel execution without file-locking conflicts or job ID collisions.
What happens to background jobs when I close a Claude Code session?
The handleSessionEnd function in session-lifecycle-hook.mjs automatically calls cleanupSessionJobs, which locates all jobs in state.json matching the ending session's ID. It sends termination signals to any running processes via terminateProcessTree and removes those job records from the state file. This prevents orphaned processes from consuming CPU and memory after the parent session closes.
How can I view jobs from all sessions at once?
Use the --all flag with the status command. This invokes buildStatusSnapshot with the all:true option, bypassing the filterJobsForCurrentSession filter. According to the implementation in job-control.mjs (lines 13-31), this returns the complete job list across every active and completed session, useful for debugging or administrative oversight when you need the full picture of background work in the workspace.
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 →