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

> Learn how background task execution works in OpenAI Codex Companion. Explore the task-worker process model for reliable, non-blocking operations.

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

---

**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

```bash

# 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

```bash

# 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

```bash

# 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.