# What Happens When a Codex Job Is Cancelled Mid-Execution

> Discover what happens when a Codex job is cancelled mid-execution. Learn how the system terminates processes, interrupts remote turns, updates state, and confirms cancellation.

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

---

**When a Codex job is cancelled mid-execution, the system terminates the underlying process, attempts to interrupt any remote model turn, updates the persistent state file to "cancelled", and returns a JSON confirmation to the user.**

The `openai/codex-plugin-cc` repository implements a robust cancellation pipeline that safely halts running jobs whether they are executed locally or brokered through a shared application server. When a user issues the `/codex:cancel` command or invokes the cancellation script directly, the request traverses a chain of modules—from the companion CLI entry point to the core job-control logic—to ensure clean termination and accurate state persistence.

## The Cancellation Pipeline

The cancellation process follows six distinct stages, each handled by specific modules in the codebase to ensure safe interruption and state consistency.

### Command Dispatch via the Codex Companion

The cancellation request originates at the **Codex companion** entry point located in `plugins/codex/scripts/codex-companion.mjs`. This script parses command-line arguments and routes the cancel request to the job-control logic. According to the source code at line 86, the companion validates the input flags (such as `--json` for machine-readable output) before forwarding the operation to the underlying control module.

### Job Lookup and Validation

The **job-control module** (`plugins/codex/scripts/lib/job-control.mjs`) receives the request and performs a lookup to identify the target job. If no specific job ID is provided, the system searches for a single active job in the session state. As implemented in lines 300–307, the logic throws descriptive errors when multiple active jobs exist (requiring disambiguation) or when no active jobs are found. This validation prevents accidental termination of the wrong process.

### Turn Interrupt for Brokered Jobs

For jobs brokered through the shared app-server, the cancellation routine attempts a **turn-interrupt** operation before killing the underlying process. This signals the remote executor to stop the current model turn gracefully. The system records the outcome of this attempt in the cancellation payload using the boolean fields `turnInterruptAttempted` and `turnInterrupted`, as shown in the test suite at `tests/runtime.test.mjs` lines 1771–1779. This mechanism ensures that remote resources are released properly rather than being abruptly severed.

### Process Termination and State Update

Once the target job is identified, the system executes the termination sequence:

- The process ID (`pid`) is killed using standard process termination signals
- The `pid` field is cleared from the job record
- The job status is set to **"cancelled"**
- The `cancelledAt` timestamp is recorded

This logic is implemented in `plugins/codex/scripts/lib/job-control.mjs` at lines 113–114. The immediate state update ensures that the job cannot be resumed or mistaken for an active process.

### Persistence and User Feedback

The updated job object—including the nullified `pid`, "cancelled" status, and `cancelledAt` timestamp—is written to the on-disk state file. As confirmed in `tests/runtime.test.mjs` lines 1628–1633, this persistence guarantees that subsequent `/codex:status` queries reflect the cancellation accurately.

The CLI then outputs a JSON payload (or human-readable message) confirming the operation. The response includes the `jobId`, final status, and turn-interrupt results, as validated in `tests/runtime.test.mjs` lines 1776–1780. If the job was already completed, failed, or previously cancelled, the system returns a helpful error message (see `job-control.mjs` line 172) instead of attempting termination.

## How to Cancel a Codex Job

You can cancel jobs using the slash command in compatible editors or via the companion CLI directly.

Cancel the currently active job (only works if exactly one job is running):

```bash
/codex:cancel

```

Cancel a specific job by ID with JSON output:

```bash
/codex:cancel my-task-123 --json

```

Via the companion script:

```bash
node scripts/codex-companion.mjs cancel my-task-123 --json

```

## Programmatic Cancellation in Node.js

You can invoke the same cancellation logic programmatically using the job-control module:

```javascript
import { cancelJob } from "./plugins/codex/scripts/lib/job-control.mjs";

// Cancel by job ID with JSON output enabled
const result = await cancelJob({ jobId: "my-task-123", json: true });

console.log(result);
// {
//   jobId: "my-task-123",
//   status: "cancelled",
//   turnInterruptAttempted: true,
//   turnInterrupted: true
// }

```

## Cancellation States and Error Handling

The system enforces strict state validation to prevent invalid operations:

- **Already completed jobs**: Returns an error indicating the job has finished (line 172 in `job-control.mjs`)
- **Already cancelled jobs**: Returns a descriptive error preventing duplicate cancellation attempts
- **Failed jobs**: Returns an error stating the job has already failed and cannot be cancelled

The following table maps job states to cancellation behavior:

| Current State | Cancellation Result |
|---------------|---------------------|
| `running` | Process killed, state set to `cancelled` |
| `cancelled` | Error: "Job already cancelled" |
| `completed` | Error: "Job already completed" |
| `failed` | Error: "Job already failed" |

## Summary

- **Command routing**: Cancellation requests enter through `codex-companion.mjs` and flow to `job-control.mjs`
- **Turn interrupt**: Brokered jobs receive a graceful stop signal before process termination
- **Process cleanup**: Local jobs have their PID killed and cleared, with `cancelledAt` timestamps recorded
- **State persistence**: All changes are written to disk immediately so status queries reflect the update
- **Error handling**: Attempting to cancel finished, failed, or already-cancelled jobs returns descriptive errors rather than silent failures

## Frequently Asked Questions

### Can I cancel a Codex job that has already completed?

No. According to the source code in `plugins/codex/scripts/lib/job-control.mjs` at line 172, the system validates the job state before attempting cancellation. If the job status is `completed`, `failed`, or already `cancelled`, the function throws an error explaining why the operation cannot proceed. This prevents users from accidentally interfering with finalized job records.

### What is a "turn interrupt" in Codex job cancellation?

A **turn interrupt** is a mechanism used for brokered jobs that execute on remote application servers. Before killing the local process handle, the system attempts to send an interrupt signal to the remote executor to stop the current model turn gracefully. As shown in `tests/runtime.test.mjs` lines 1771–1779, the system tracks whether this attempt was made (`turnInterruptAttempted`) and whether it succeeded (`turnInterrupted`), ensuring proper resource cleanup on distributed infrastructure.

### Where is the cancellation status persisted?

The cancellation status is persisted to the on-disk state file managed by the job-control module. As demonstrated in `tests/runtime.test.mjs` lines 1628–1633, the updated job object—including the "cancelled" status, nullified `pid`, and `cancelledAt` timestamp—is written to storage immediately after termination. This ensures that the `/codex:status` command and subsequent operations see the accurate, updated state even if the CLI process restarts.

### How can I verify a job was cancelled successfully?

Use the `--json` flag when cancelling to receive a structured confirmation, or query the job status afterward. A successful cancellation returns a JSON object containing the `jobId`, `status: "cancelled"`, and turn-interrupt results. If you query status later via `/codex:status` or the status command, the persistent state file will reflect the `cancelled` status and the `cancelledAt` timestamp, confirming the operation completed.