What Happens When a Codex Task Is Cancelled by the Plugin: Complete Flow
When a Codex task is cancelled by the plugin, the system resolves the active job, sends a turn-interrupt request to the shared app-server, kills the background Node process, persists the updated state to disk, and returns a JSON payload confirming the cancellation status.
The openai/codex-plugin-cc repository provides a Claude Code plugin that manages Codex tasks through a command-line interface. When users invoke the /codex:cancel command, the plugin executes a precise five-step cancellation sequence defined in the companion scripts and job control modules.
The Cancellation Flow
Job Resolution via resolveCancelableJob()
The cancellation process begins in plugins/codex/scripts/lib/job-control.mjs with the resolveCancelableJob() function. This utility examines all active jobs—those with status queued or running—to identify the cancellation target.
If the user provides a specific job ID, the function matches that reference against the active list. If no ID is provided, the system filters jobs to the current Claude session using the SESSION_ID_ENV environment variable. The function throws descriptive errors for ambiguous, missing, or non-existent jobs, such as "Multiple Codex jobs are active. Pass a job id to /codex:cancel."
Graceful Turn-Interrupt to the App-Server
Before terminating the process, the plugin attempts to gracefully halt any in-flight Claude turn by sending a turn-interrupt request to the shared app-server. This RPC call is implemented in plugins/codex/scripts/lib/app-server.mjs and ensures that the Claude session receives immediate notification of the cancellation.
As verified in tests/runtime.test.mjs, the turn-interrupt mechanism returns a boolean indicating success, which propagates into the final result payload.
Process Termination and State Mutation
Once the interrupt request completes, the plugin kills the underlying Node process associated with the job, clearing job.pid from memory. The job record undergoes immediate mutation:
statusis set to"cancelled"phaseis updated to"cancelled"(viaenrichJob())cancelledAtandcompletedAttimestamps are recorded
Persistent State Updates
The mutated job state is written to disk through plugins/codex/scripts/lib/state.mjs. The readJobFile() and writeJobFile() functions handle the atomic update of the JSON job record, ensuring that subsequent status or result commands observe the cancelled state.
JSON Result Payload
The CLI entry point in plugins/codex/scripts/codex-companion.mjs formats the outcome as a JSON object and prints it to stdout. This payload includes the job ID, final status, and turn-interrupt results, making the cancellation observable to downstream automation tools.
Practical Examples
Cancel the most recent active job in the current session:
node scripts/codex-companion.mjs cancel --json
Cancel a specific job by ID:
node scripts/codex-companion.mjs cancel task-live --json
The resolveCancelableJob() implementation in job-control.mjs handles the selection logic:
export function resolveCancelableJob(cwd, reference, options = {}) {
const workspaceRoot = resolveWorkspaceRoot(cwd);
const jobs = sortJobsNewestFirst(listJobs(workspaceRoot));
const activeJobs = jobs.filter((job) => job.status === "queued" || job.status === "running");
if (reference) {
const selected = matchJobReference(activeJobs, reference);
if (!selected) {
throw new Error(`No active job found for "${reference}".`);
}
return { workspaceRoot, job: selected };
}
const sessionScopedActiveJobs = filterJobsForCurrentSession(activeJobs, options);
if (sessionScopedActiveJobs.length === 1) {
return { workspaceRoot, job: sessionScopedActiveJobs[0] };
}
// Error handling for ambiguous or missing jobs...
}
After a successful cancellation, the CLI prints this JSON structure:
{
"jobId": "task-live",
"status": "cancelled",
"turnInterruptAttempted": true,
"turnInterrupted": true
}
Summary
- Job resolution occurs through
resolveCancelableJob()injob-control.mjs, which filters active jobs by status and session ID. - Graceful interruption is attempted via the app-server RPC before process termination.
- Process cleanup kills the Node PID and updates the job record with
cancelledstatus and timestamps. - State persistence uses
state.mjsto atomically write the updated job to disk. - Observable results are returned as JSON containing the job ID, status, and turn-interrupt flags.
Frequently Asked Questions
What happens if multiple Codex jobs are running when I cancel?
The plugin throws an error requiring you to specify the job ID. According to the source code in job-control.mjs, when multiple jobs have status queued or running, the resolveCancelableJob() function detects the ambiguity and prompts: "Multiple Codex jobs are active. Pass a job id to /codex:cancel."
Does cancelling a job immediately stop the Claude turn?
Yes. The plugin sends a turn-interrupt request to the app-server before killing the process. As shown in the test suite and result payload, the turnInterrupted field returns true when the interrupt succeeds, ensuring any in-flight Claude operation halts gracefully.
Where is the cancelled job state stored?
The job state is persisted in JSON files managed by plugins/codex/scripts/lib/state.mjs. The writeJobFile() function updates the record with status: "cancelled", timestamps, and phase information, making the cancellation visible to future commands.
Can I cancel a job from a different Claude session?
No. By default, the plugin scopes cancellation to the current session via SESSION_ID_ENV. If you need to cancel a job from another session, you must explicitly provide the job ID as an argument to the cancel command, which bypasses the session filter in filterJobsForCurrentSession().
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →