What Happens When a Codex Turn Times Out in openai/codex-plugin-cc

When a Codex turn exceeds its execution window, the process module detects the timeout via Promise.race, aborts the underlying request, updates the shared state to timed_out, triggers any configured rescue agent, and returns a status object containing partial output to the caller.

In the openai/codex-plugin-cc repository, a turn represents a single interaction between a user and the Codex engine. Understanding how the system handles timeouts is critical for building resilient AI-powered workflows. This article examines the timeout lifecycle implementation across the core process and state modules.

The Turn Timeout Lifecycle

When a Codex turn exceeds its allotted execution window, the system executes a five-phase recovery sequence designed to release resources and preserve diagnostic context.

1. Timeout Detection via Promise.race

The process module (plugins/codex/scripts/lib/process.mjs) wraps each Codex request in a Promise.race against a setTimeout timer. If the timer fires before the request completes, the turn is flagged for termination.

2. Graceful Cancellation and Abort

Upon detection, the plugin invokes the abort controller exposed by the Codex library (plugins/codex/scripts/lib/codex.mjs) to cancel the in-flight request. This ensures that underlying network connections and compute resources are released promptly rather than hanging indefinitely.

3. State Persistence in the State Module

The shared state module (plugins/codex/scripts/lib/state.mjs) records the timeout event, updating the turn's status to timed_out and preserving any metadata required for subsequent hooks. This centralized state ensures that all components observe a consistent view of the turn's lifecycle.

4. Rescue Agent Activation

If a rescue command is defined (see plugins/codex/agents/codex-rescue.md), the system automatically triggers this agent to perform cleanup operations, emit diagnostic information, and optionally retry the turn. The rescue agent operates as a safety net for automated recovery.

5. Client Response with Partial Output

The final response sent to the caller includes a status: "timeout" field and any partial output accumulated before the abort occurred. This allows client applications to handle degraded responses gracefully while maintaining visibility into execution progress.

Key Source Files and Responsibilities

The timeout handling logic is distributed across several specialized modules:

  • plugins/codex/scripts/lib/process.mjs: Core orchestration of Codex turns; implements the Promise.race timeout guard and runTurn function.
  • plugins/codex/scripts/lib/state.mjs: Central state store providing getState and persistence for turn status, including timed_out states.
  • plugins/codex/scripts/lib/codex.mjs: Wrapper around the Codex API providing abortable request objects and the underlying abort controller.
  • plugins/codex/agents/codex-rescue.md: Definition and configuration of the rescue agent triggered automatically on timeout.
  • plugins/codex/scripts/lib/session-lifecycle-hook.mjs: Hooks that tie turn lifecycle events (start, finish, timeout) to broader session management.

Handling Timeouts in Your Code

The following examples demonstrate how to interact with the timeout lifecycle programmatically.

Running a Turn with Explicit Timeout Handling

Use the runTurn function from the process module to execute a Codex interaction with custom timeout logic:

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

const TURN_TIMEOUT_MS = 30_000; // 30 seconds

try {
  const result = await runTurn({
    prompt: 'Explain the bubble sort algorithm.',
    timeoutMs: TURN_TIMEOUT_MS,
  });
  console.log('Turn completed:', result);
} catch (err) {
  if (err.name === 'TimeoutError') {
    console.error('The turn timed out after', TURN_TIMEOUT_MS, 'ms');
    // Optional: invoke rescue logic manually
    // await invokeRescueAgent();
  } else {
    console.error('Turn failed:', err);
  }
}

Inspecting Turn State After Timeout

After a timeout occurs, inspect the shared state to retrieve diagnostic information and partial outputs:

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

const state = await getState(turnId);
if (state.status === 'timed_out') {
  console.log('Turn timed out. Reason:', state.reason);
  // Access any partial output generated before abort
  console.log('Partial output:', state.partialOutput);
}

Summary

  • Timeout Detection: The process module uses Promise.race with a setTimeout to detect when a turn exceeds its execution window.
  • Graceful Abort: The system cancels the underlying Codex request via an abort controller to release resources immediately.
  • State Management: The state module updates the turn status to timed_out and preserves metadata for debugging.
  • Rescue Activation: Configured rescue agents in codex-rescue.md automatically execute cleanup and diagnostic routines.
  • Client Visibility: Timeouts return a status: "timeout" response including any partial output accumulated before cancellation.

Frequently Asked Questions

How is the timeout duration configured for a Codex turn?

The timeout duration is passed as the timeoutMs parameter to the runTurn function in plugins/codex/scripts/lib/process.mjs. If the Codex request does not complete within this millisecond window, the Promise.race resolves with the timeout trigger, initiating the abort sequence.

What happens to partial output when a turn times out?

Partial output generated before the abort signal is captured and preserved in the shared state managed by plugins/codex/scripts/lib/state.mjs. The getState function returns a state object containing a partialOutput field, which is included in the final client response to provide visibility into execution progress.

Can the rescue agent behavior be customized per turn?

Yes. The rescue agent is defined in plugins/codex/agents/codex-rescue.md and can be configured to execute specific cleanup logic, emit diagnostic information, or retry the turn. While the system triggers rescue automatically on timeout, you can also invoke rescue logic manually by catching the TimeoutError and calling the rescue functions directly.

How does the timeout mechanism interact with the session lifecycle?

The session-lifecycle-hook.mjs module subscribes to turn events including timeouts through the state management system. When a turn status changes to timed_out, these hooks execute to update session-level tracking, ensuring that resource accounting and logging reflect the terminated turn correctly.

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 →