# How Background Jobs Are Executed and Cancelled in Codex

> Discover how background jobs execute and cancel in Codex. Learn about queuing, worker processes, and PID-based signal termination for efficient task management.

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

---

**The Codex plugin runs long-running tasks as detached background processes by queuing tracked jobs, spawning worker processes via `enqueueBackgroundTask`, and cancelling them via PID-based signal termination using `cancelJob`.**

The `openai/codex-plugin-cc` repository implements a robust background execution system that allows developers to run long-running Codex tasks without blocking their IDE or terminal. Understanding how background jobs are executed and cancelled in Codex is essential for managing resource-intensive operations like automated code reviews or complex refactoring tasks. This article examines the complete lifecycle—from the initial `--background` flag detection through to process termination and status tracking.

## Detecting the `--background` Flag and Routing Commands

When you invoke commands like `/codex:task` or `/codex:review` with the `--background` flag, the companion script routes the request to specialized handlers. The `handleTask` and `handleReview` functions redirect execution to the background task path, triggering the job creation workflow rather than running synchronously.

## Enqueueing Tracked Jobs in `codex-companion.mjs`

The `enqueueBackgroundTask` function in `plugins/codex/scripts/codex-companion.mjs` (lines 84–98) orchestrates the initial job setup. It generates a unique job ID, initializes progress tracking via `createTrackedProgress`, and persists the job record with a "queued" status before spawning the worker.

```javascript
// plugins/codex/scripts/codex-companion.mjs
// Lines 84-98 – enqueueBackgroundTask
const { logFile } = createTrackedProgress(job);
appendLogLine(logFile, "Queued for background execution.");

const child = spawnDetachedTaskWorker(cwd, job.id);
const queuedRecord = { …job, status: "queued", pid: child.pid ?? null, logFile, request };
writeJobFile(job.workspaceRoot, job.id, queuedRecord);
upsertJob(job.workspaceRoot, queuedRecord);

```

This code captures the worker's PID immediately upon spawning, which is critical for later cancellation, and writes the initial state to a JSON job file in the workspace.

## Spawning Detached Worker Processes

To ensure tasks survive after the parent process exits, the system uses `spawnDetachedTaskWorker` (lines 71–80 in `codex-companion.mjs`). This function launches a new Node.js process with `detached: true` and `stdio: "ignore"`, allowing the worker to run independently of the IDE's process lifecycle.

```javascript
// plugins/codex/scripts/codex-companion.mjs
// Lines 71-80 – spawnDetachedTaskWorker
const child = spawn(process.execPath,
    [scriptPath, "task‑worker", "--cwd", cwd, "--job-id", jobId],
    { cwd, env: process.env, detached: true, stdio: "ignore", windowsHide: true });
child.unref();

```

The `child.unref()` call ensures the parent event loop does not wait for the detached process to exit, enabling true "fire-and-forget" execution.

## Worker Execution and Lifecycle Management

Once detached, the worker process reads the stored job configuration using `readStoredJob` from `plugins/codex/scripts/lib/job-control.mjs`. It executes the actual Codex CLI task through `executeTaskRun`, streaming progress updates to the log file. Upon completion, the worker writes the final status—either "completed" or "failed"—back to the job file using `writeJobFile`.

## Cancelling Background Jobs via PID Signal Termination

The cancellation mechanism relies on process IDs stored during enqueueing. When you invoke `/codex:cancel <job-id>`, the system loads the job record and sends a `SIGTERM` signal to the worker process (lines 183–190 in `job-control.mjs`).

```javascript
// plugins/codex/scripts/lib/job-control.mjs
// (excerpt – cancellation logic)
const job = readStoredJob(workspaceRoot, jobId);
if (job?.pid) {
    try { process.kill(job.pid, "SIGTERM"); } catch (_) {}
}
const cancelled = { …job, status: "cancelled", phase: "cancelled", pid: null };
writeJobFile(workspaceRoot, jobId, cancelled);

```

After signalling the process, the code updates the job file to reflect the "cancelled" status and clears the PID to prevent orphaned references.

## Monitoring Status and Retrieving Results

The `/codex:status` command reads the JSON job files from the workspace to report the current phase (queued, running, completed, or cancelled). For finished tasks, `/codex:result` retrieves the final output from the persisted log files generated by `createTrackedProgress`.

## Practical Usage Examples

Start a long-running task in the background, check its progress, and cancel it if needed:

```bash

# 1️⃣ Start a long-running task in the background

/codex:task --background "investigate flaky test"

# 2️⃣ Check progress (replace <job-id> with the ID shown after launch)

/codex:status <job-id>

# 3️⃣ Cancel the job if it’s no longer needed

/codex:cancel <job-id>

```

For programmatic control, you can import the internal functions directly:

```javascript
// Minimal illustration of the enqueue-and-cancel flow (internal)
import { enqueueBackgroundTask } from "./codex-companion.mjs";
import { cancelJob } from "./lib/job-control.mjs";

const job = buildTaskJob(workspaceRoot, metadata, false);
const { payload } = enqueueBackgroundTask(cwd, job, request);
// payload.jobId => e.g. "task-abc123"

// Later …
await cancelJob(workspaceRoot, payload.jobId);

```

## Summary

- **Background execution** begins when commands include the `--background` flag, triggering `enqueueBackgroundTask` in `codex-companion.mjs`.
- **Detached workers** are spawned with `detached: true` and `stdio: "ignore"` to survive parent process termination, then tracked via PID.
- **Job persistence** uses JSON files and log tracking in the workspace root, updated through helpers in `lib/job-control.mjs` and `lib/tracked-jobs.mjs`.
- **Cancellation** sends `SIGTERM` to the stored PID and updates the job status to "cancelled", ensuring clean termination.
- **Status monitoring** reads persisted job files to report real-time progress via `/codex:status` and final results via `/codex:result`.

## Frequently Asked Questions

### How does the Codex plugin prevent background jobs from terminating when the IDE closes?

The plugin uses `spawnDetachedTaskWorker` with `detached: true` and `child.unref()` to create a completely independent Node.js process. This detaches the worker from the parent's process group, ensuring it continues running even if the IDE or terminal that launched it exits.

### What signal does the Codex plugin use to cancel background jobs?

The cancellation handler in `lib/job-control.mjs` sends a standard `SIGTERM` signal to the worker process using `process.kill(job.pid, "SIGTERM")`. If the process does not exist or has already exited, the error is caught silently and the job status is updated to "cancelled" regardless.

### Where does the Codex plugin store job status and log files?

Job metadata and status are stored as JSON files in the workspace root, written via `writeJobFile` and read via `readStoredJob`. Log files are created by `createTrackedProgress` in `lib/tracked-jobs.mjs` and appended to during execution, allowing `/codex:status` and `/codex:result` to report progress and final output.

### Can I run multiple background jobs simultaneously in the Codex plugin?

Yes, the system supports concurrent execution. Each job receives a unique ID generated during `enqueueBackgroundTask`, and the `upsertJob` function maintains a registry of active jobs. Each worker runs as an independent detached process, so you can queue multiple tasks and cancel or monitor them individually by their job IDs.