What Happens When a Codex Turn Is Interrupted Mid-Execution: Inside the OpenAI Codex Plugin

When a Codex turn is interrupted mid-execution, the client sends a turn/interrupt RPC to the app-server, which aborts pending work by clearing the turn from the interruptibleTurns map and returning a turn/completed message with the status set to interrupted.

Interrupting a running AI task requires precise coordination between client and server to prevent resource leaks and ensure state consistency. In the openai/codex-plugin-cc repository, the Codex runtime implements a cooperative interruption protocol that allows clients to abort specific turns programmatically or via CLI commands. This mechanism relies on thread and turn identifiers to precisely target running operations without affecting other concurrent tasks.

The Client-Side Interrupt Request

The interruption flow begins in plugins/codex/scripts/lib/codex.mjs, where the interruptAppServerTurn function handles the outgoing request. This helper first validates that both a threadId and turnId are provided and that the Codex runtime is available in the current working directory.

If validation passes, the function opens a persistent broker client and issues a turn/interrupt RPC to the running Codex app-server. The method returns an object describing whether the interrupt was attempted and whether it ultimately succeeded, allowing calling code to handle failure cases gracefully.

According to the source code at lines 60-89, the function signature requires the target working directory and an options object containing the thread and turn identifiers:

// From plugins/codex/scripts/lib/codex.mjs
async function interruptAppServerTurn(cwd, { threadId, turnId }) {
  // Validation and RPC logic
}

App-Server Processing and Turn Abort

When the app-server receives the turn/interrupt method, it processes the request by looking up the turn in an internal interruptibleTurns map. As implemented in tests/fake-codex-fixture.mjs (lines 11-31), the handler verifies that the turn exists and is eligible for interruption.

If the turn is found, the server immediately clears any pending timeout associated with that turn, removes the entry from the interruptibleTurns map to prevent duplicate processing, and dispatches a turn/completed message back to the client. This response explicitly marks the turn as interrupted, distinguishing it from natural completion or error states.

The test fixture demonstrates this logic at lines 24-30:

// Simplified from tests/fake-codex-fixture.mjs
if (turn) {
  clearTimeout(turn.timeout);
  interruptibleTurns.delete(turnId);
  send({ method: 'turn/completed', turn: { ...turn, status: 'interrupted' } });
}

Result Propagation and User Feedback

Once the app-server responds, the client code evaluates the result. The interruptAppServerTurn function returns an object containing attempted and interrupted boolean flags. If the RPC succeeded, both values are true, confirming that the server acknowledged and processed the interruption.

In plugins/codex/scripts/codex-companion.mjs (lines 976-982), the companion script consumes this result to provide user-facing feedback. Upon successful interruption, it logs a message such as "Requested Codex turn interrupt for <turnId> on <threadId>," ensuring operators can track which operations were manually terminated.

CLI Integration: The Cancel Command

End users typically trigger interruptions through the codex cancel command, documented in plugins/codex/commands/cancel.md. This CLI wrapper ultimately invokes the same interruptAppServerTurn function, translating command-line arguments for thread and turn IDs into the RPC mechanism described above.

The integration is verified in tests/runtime.test.mjs (lines 1740-1744), where test cases confirm that issuing a cancel command sends the interrupt request before terminating the brokered task. This ensures that the interruption signal propagates to the server even when the client process is shutting down.

Programmatic Implementation Examples

Developers can integrate turn interruption into custom workflows using the Codex plugin library.

Programmatic Interrupt

To interrupt a turn from JavaScript, import interruptAppServerTurn and await the result:

import { interruptAppServerTurn } from './plugins/codex/scripts/lib/codex.mjs';

const cwd = process.cwd();
const result = await interruptAppServerTurn(cwd, {
  threadId: 'thread-123',
  turnId: 'turn-456'
});

if (result.interrupted) {
  console.log('Turn was successfully interrupted.');
} else if (result.attempted) {
  console.warn('Interrupt was attempted but failed:', result.detail);
} else {
  console.warn('Interrupt not attempted:', result.detail);
}

Using the CLI Cancel Command

For command-line usage, specify the thread and turn IDs as arguments:

codex cancel --thread-id thread-123 --turn-id turn-456

# → Requested Codex turn interrupt for turn-456 on thread-123.

Handling Interrupts in Client Scripts

When building interactive applications, you can capture a turn reference and interrupt it later based on user input:

import { startThread, captureTurn } from './plugins/codex/scripts/lib/codex.mjs';

async function runAndPossiblyInterrupt() {
  const client = await CodexAppServerClient.connect(cwd, { reuseExistingBroker: true });
  const thread = await startThread(client, cwd, { model: 'gpt-4', sandbox: 'read-only' });
  const turn = await captureTurn(client, thread.thread.id, () => client.request('some/long-running-task'));
  
  // If user cancels:
  await interruptAppServerTurn(cwd, { 
    threadId: thread.thread.id, 
    turnId: turn.turnId 
  });
}

Summary

  • Cooperative Protocol: Interrupting a Codex turn requires the client to send a turn/interrupt RPC and the server to acknowledge it by removing the turn from the interruptibleTurns map.
  • Validation Required: The interruptAppServerTurn function in plugins/codex/scripts/lib/codex.mjs validates threadId and turnId before attempting the RPC.
  • Server-Side Cleanup: The app-server clears pending timeouts and sends a turn/completed message with status: 'interrupted' to confirm the operation.
  • CLI and Programmatic Access: Both the codex cancel command and direct JavaScript API calls use the same underlying interruption mechanism.
  • Graceful Degradation: If a turn is not found in interruptibleTurns, the interrupt request is ignored and the turn continues to completion.

Frequently Asked Questions

What happens if I try to interrupt a Codex turn that has already completed?

If the turn is not present in the app-server's interruptibleTurns map—either because it finished naturally or was already interrupted—the server ignores the request. The client receives a response indicating the interrupt was attempted but the turn continues to completion without interruption.

Can I interrupt a Codex turn from a different process than the one that started it?

Yes, provided the interrupting process has access to the same Codex runtime broker and knows the correct threadId and turnId. The interruptAppServerTurn function opens a persistent broker client to send the RPC, allowing cross-process interruption as long as the broker is accessible.

What is the difference between attempted and interrupted in the return value?

The attempted field indicates whether the client successfully sent the turn/interrupt RPC to the app-server, while interrupted confirms that the server actually found and terminated the turn. If attempted is true but interrupted is false, the server received the request but the turn was not in an interruptible state.

How does the codex cancel command relate to the interrupt API?

The codex cancel command documented in plugins/codex/commands/cancel.md is a CLI wrapper that calls interruptAppServerTurn internally. It translates command-line arguments into the RPC mechanism, making the programmatic interruption API accessible from the command line without requiring custom JavaScript code.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →