# How the Codex Plugin Handles Job Cancellation and Process Termination

> Discover how the Codex plugin ensures job cancellation and process termination through its multi-stage pipeline for graceful interruption and forceful termination.

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

---

**The Codex plugin implements deterministic job cancellation through a multi-stage pipeline that attempts graceful turn interruption before forcefully terminating the process tree and persisting terminal state to disk.**

The `openai/codex-plugin-cc` repository provides a robust mechanism for terminating background coding tasks via both CLI slash commands and programmatic APIs. Understanding the **Codex plugin job cancellation** flow requires examining how the system resolves active jobs, interrupts in-flight operations, and maintains repository state consistency.

## Command Entry Point and Slash Command Integration

The user-facing entry point for cancellation is defined in **[`plugins/codex/commands/cancel.md`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/commands/cancel.md)**. This markdown configuration maps the `/codex:cancel` slash command to the Node.js companion script, passing the `cancel` sub-command and any provided arguments to `codex-companion.mjs`.

When a user invokes `/codex:cancel` or `/codex:cancel <job-id>` within the Claude interface, the plugin translates this into a shell execution of the companion script with appropriate flags.

## The Cancellation Pipeline in codex-companion.mjs

The core orchestration logic resides in the **`handleCancel`** function within **`plugins/codex/scripts/codex-companion.mjs`** (lines 63-87). This function coordinates the entire termination sequence through four distinct phases.

### Resolving the Target Job with resolveCancelableJob

First, `handleCancel` extracts the optional job ID (`reference`) and current working directory from CLI arguments. It then delegates job resolution to **`resolveCancelableJob`** defined in **`plugins/codex/scripts/lib/job-control.mjs`** (lines 81-108).

The resolution logic performs the following steps:

- Lists all jobs in the repository and filters for status values of `queued` or `running`
- If a specific job ID is provided, validates that the job matches the identifier
- When no ID is provided, scopes the search to jobs belonging to the current Claude session
- Throws explicit errors for ambiguous states, such as multiple active jobs without a specified reference
- Returns a clear error when no cancellable jobs exist in the target scope

### Graceful Turn Interruption Before Process Termination

Before killing any processes, the system attempts a **turn interrupt** against the shared app server via `interruptAppServerTurn`. This preemptive step gives the server an opportunity to clean up any in-flight Codex turns and release resources gracefully.

The interrupt attempt is tracked via the `turnInterruptAttempted` and `turnInterrupted` boolean flags, which are included in the final cancellation report.

### Process Tree Termination and State Persistence

Once the target job is resolved and the interrupt attempted, the pipeline executes the following deterministic cleanup sequence:

1. **Process termination**: Calls `terminateProcessTree` on the job's process ID (`job.pid`) to ensure all child processes are killed
2. **Log entry**: Appends "Cancelled by user." to the job's log file for audit purposes
3. **Metadata update**: Rewrites the job's JSON metadata with `status: "cancelled"`, `phase: "cancelled"`, a `completedAt` timestamp, and `errorMessage: "Cancelled by user."`
4. **Index synchronization**: Upserts the updated job into the job index via `upsertJob`

## Output and Reporting

The final stage of the pipeline generates user-facing output through **`renderCancelReport`** in **`plugins/codex/scripts/lib/render.mjs`**. This function formats the cancellation result into a human-readable message (or JSON when `--json` is specified).

The report payload includes:
- `jobId`: The identifier of the cancelled job
- `status`: The final status (always "cancelled")
- `title`: The human-readable job title
- `turnInterruptAttempted`: Boolean indicating if the turn interrupt was attempted
- `turnInterrupted`: Boolean indicating if the interrupt succeeded

## Programmatic Cancellation API

Developers can trigger cancellation programmatically by importing the handler directly:

```javascript
import { handleCancel } from "./plugins/codex/scripts/codex-companion.mjs";

// Cancel specific job with JSON output
await handleCancel(["cancel", "task-1234", "--json"]);

```

This enables integration with external automation tools or other plugin systems that need to manage Codex job lifecycles.

## Command Line Examples

Cancel a specific job by ID:

```bash
$ codex cancel task-1234
{
  "jobId": "task-1234",
  "status": "cancelled",
  "title": "Run my‑script",
  "turnInterruptAttempted": true,
  "turnInterrupted": true
}

```

Cancel the active job for the current session (requires unique active job):

```bash
$ codex cancel

```

If multiple active jobs exist without specifying an ID, the command returns an error: "Multiple Codex jobs are active. Pass a job id to /codex:cancel."

## Summary

- **Entry point**: The `/codex:cancel` command is defined in [`plugins/codex/commands/cancel.md`](https://github.com/openai/codex-plugin-cc/blob/main/plugins/codex/commands/cancel.md) and delegates to `codex-companion.mjs`
- **Job resolution**: `resolveCancelableJob` in `job-control.mjs` validates the target job against the current session or specified ID
- **Graceful shutdown**: The system attempts `interruptAppServerTurn` before process termination to clean up in-flight operations
- **Forceful termination**: `terminateProcessTree` kills the process tree using the stored `job.pid`
- **State persistence**: Job metadata is updated with `status: "cancelled"`, timestamps, and error messages, then synced to the job index
- **Feedback**: Results are formatted via `renderCancelReport` for CLI or JSON consumption

## Frequently Asked Questions

### How does the Codex plugin determine which job to cancel when no ID is provided?

When invoked without a job ID, the system calls `resolveCancelableJob` which filters for jobs with status `queued` or `running` and scopes the search to the current Claude session. If exactly one active job exists in that session, it selects that job. If zero or multiple active jobs exist, it throws an explicit error requiring the user to specify the job ID.

### What happens if the turn interrupt fails during cancellation?

The cancellation pipeline attempts the turn interrupt via `interruptAppServerTurn` before process termination, but the operation continues regardless of the interrupt's success. The final report includes both `turnInterruptAttempted` and `turnInterrupted` booleans, allowing callers to determine if the graceful shutdown succeeded or if the process required forceful termination.

### Where is the cancellation state persisted after a job is terminated?

After termination, the plugin updates the job's metadata JSON file with `status: "cancelled"`, `phase: "cancelled"`, a `completedAt` timestamp, and `errorMessage: "Cancelled by user."`. It also appends an entry to the job's log file. The updated record is then upserted into the repository's job index via `upsertJob` to maintain consistency across the codebase.

### Can I cancel jobs from outside the Claude interface?

Yes, the cancellation logic is exposed through the `handleCancel` function in `codex-companion.mjs`, which can be imported and invoked programmatically from other Node.js scripts or plugins. The function accepts standard CLI argument arrays, allowing integration with external task runners or automation systems that manage Codex job lifecycles.