How Background Task Execution Works in OpenAI Codex Companion: Task-Worker Process Model Explained

Background task execution in Codex Companion uses a task-worker process model where the CLI spawns a detached child process to run the task while persisting job state to disk for reliability and non-blocking operation.

This article breaks down the complete lifecycle of a background Codex task as implemented in the openai/codex-plugin-cc repository. You'll learn how jobs are created, queued, executed in isolated workers, tracked, and cancelled—all without blocking your terminal.

Overview of the Task-Worker Process Model

The Codex Companion CLI supports two execution modes for any task:

  • Foreground (blocking) — The task runs directly in your shell, displaying live output until completion.
  • Background (detached) — The CLI immediately returns a job ID and delegates execution to a separate worker process.

The task-worker process model enables the background mode through a clear separation of responsibilities: the main CLI handles orchestration and persistence, while a spawned child process handles the actual task execution. This design provides three core benefits: a non-blocking user interface, crash-resistant progress tracking via disk-persisted state, and safe parallelism through process isolation.

Step-by-Step: How a Background Task Executes

1. Job Creation and Persistence

When you invoke codex task --background, the CLI first constructs a job record containing the task ID, title, summary, and other metadata. This record is written as a JSON file to the workspace state directory.

The key functions involved are:

  • createCompanionJob() — Builds the initial job structure.
  • upsertJob() — Persists the job to state/jobs/<job-id>.json.

In plugins/codex/scripts/lib/state.mjs, upsertJob handles atomic writes to prevent corruption during concurrent access (lines 1289–1318). The job file serves as the source of truth throughout the task lifecycle.

2. Enqueueing and Worker Spawning

The handleTask function in plugins/codex/scripts/codex-companion.mjs detects the --background flag and routes execution through enqueueBackgroundTask (lines 688–708). This function:

  • Writes a status: "queued" record to the job file.
  • Creates a dedicated log file for this job.
  • Spawns a detached child process via spawnDetachedTaskWorker (lines 671–681).

The detachment is critical: the child process runs independently of the parent shell session, surviving even if the original terminal closes.

3. The Detached Worker Execution

The spawned process launches with the task-worker sub-command, receiving two arguments: the workspace directory and the job ID. The entry point handleTaskWorker (lines 838–845) performs the following:

  1. Calls readStoredJob() to load the persisted job metadata.
  2. Extracts the original task request payload from the job record.
  3. Re-creates the progress logger pointing to the job's log file.
  4. Invokes runTrackedJob(), which ultimately calls executeTaskRun() (lines 859–871) to run the actual Codex interaction.

This design ensures the worker has zero dependency on the parent process state—it reconstructs everything needed from disk.

4. Progress Tracking and Log Streaming

While executing, the worker writes structured progress events to the job's log file through createTrackedProgress and createProgressReporter. The main CLI (or UI) can query this log at any time via readJobProgressPreview in plugins/codex/scripts/lib/job-control.mjs (lines 61–76).

This enables commands like codex status <job-id> to show real-time task progress without requiring a persistent connection to the worker process.

5. Status Updates and Completion

Upon task completion—whether success, failure, or cancellation—the worker updates the job JSON file through upsertJob, setting status to "completed", "failed", or "cancelled". The buildStatusSnapshot function in job-control.mjs (lines 13–31) aggregates these files to present a unified view of all jobs.

6. Cancellation and Process Cleanup

To cancel a running background task, the /codex:cancel <job-id> command executes a two-phase shutdown:

  1. Graceful interruption — Calls interruptAppServerTurn() to signal the Codex service to stop the current turn.
  2. Forceful termination — If needed, invokes terminateProcessTree() from plugins/codex/scripts/lib/process.mjs (lines 57–101) to kill the entire detached process tree, including any spawned subprocesses.

The terminateProcessTree implementation uses platform-specific APIs (POSIX signals on Unix, job objects on Windows) to ensure no orphaned processes remain.

Code Examples

Running a Background Task


# Start a task without blocking your terminal

codex task --background "investigate the flaky test"

Expected output:


Codex Task started in the background as task-kw9a1z-1c2d3e. Check /codex:status task-kw9a1z-1c2d3e for progress.

Checking Task Status


# Poll for current progress

codex status task-kw9a1z-1c2d3e

Sample status report:


Running: Codex Task
  Phase: investigating
  Elapsed: 12s
  Log preview:
    [turn started] …
    [running command] npm test

Cancelling a Task


# Terminate a running background task

codex cancel task-kw9a1z-1c2d3e

Cancellation confirmation:


Cancelled Codex Task task-kw9a1z-1c2d3e.
Turn interrupt attempted: true, interrupted: true

Key Implementation Files

File Purpose
plugins/codex/scripts/codex-companion.mjs Main CLI entry point; handles task, task-worker, status, and cancel sub-commands.
plugins/codex/scripts/lib/state.mjs Persistence layer for job JSON files and global state; provides upsertJob, writeJobFile.
plugins/codex/scripts/lib/job-control.mjs Status inspection and progress preview; implements buildStatusSnapshot, readJobProgressPreview.
plugins/codex/scripts/lib/process.mjs Cross-platform process utilities; terminateProcessTree for reliable worker shutdown.
plugins/codex/scripts/lib/tracked-jobs.mjs Execution wrapper; runTrackedJob and progress reporter creation.

Summary

  • Background execution uses the --background flag to delegate tasks to detached worker processes.
  • Job persistence via JSON files in state/jobs/ enables crash recovery and status polling.
  • Process isolation through spawnDetachedTaskWorker ensures non-blocking operation and safe parallelism.
  • Progress tracking works through structured log files read by readJobProgressPreview.
  • Reliable cancellation combines graceful turn interruption with terminateProcessTree for forced cleanup.

Frequently Asked Questions

How does the task-worker process model prevent blocking the CLI?

The main CLI spawns a detached child process via spawnDetachedTaskWorker and immediately returns control to the user. The worker process inherits only the job ID and workspace path, reconstructing all necessary state from disk. This allows the parent process to exit or continue handling other commands while the task runs independently.

What happens if the worker process crashes?

The job state is persisted to disk before the worker spawns (upsertJob writes status: "queued"). If the worker crashes without updating status, the job remains in "running" state until manually inspected. The buildStatusSnapshot function in job-control.mjs can detect stale jobs by comparing timestamps against process existence checks.

Can multiple background tasks run simultaneously?

Yes. Each background task receives a unique job ID and isolated log file. Workers run in separate OS processes with no shared memory, enabling true parallelism limited only by system resources and any Codex rate limits.

How is cancellation guaranteed to clean up all subprocesses?

The terminateProcessTree function in process.mjs uses platform-specific mechanisms: on Unix, it sends SIGTERM (then SIGKILL) to the entire process group; on Windows, it uses job objects to ensure all descendant processes terminate together. This prevents orphaned shells or long-running commands from continuing after cancellation.

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 →