affaan-m/ECC · error · Error

Codex review timed out

Error message

Codex review timed out

What it means

runReview spawns codex with timeout: options.timeoutMs. When Node's spawnSync kills the subprocess for exceeding that timeout, result.error.code is 'ETIMEDOUT', which this branch re-throws as 'Codex review timed out'. The finally block still removes the temp directory. The default budget is 60s, max 120s.

Source

Thrown at skills/council-multi-model/scripts/review-with-codex.js:213

  const makeTemp = dependencies.mkdtempSync || fs.mkdtempSync;
  const readFile = dependencies.readFileSync || fs.readFileSync;
  const remove = dependencies.rmSync || fs.rmSync;
  const tempDir = makeTemp(path.join(os.tmpdir(), 'ecc-council-review-'));
  const outputFile = path.join(tempDir, 'last-message.txt');

  try {
    const result = spawn('codex', buildCodexArgs(tempDir, outputFile), {
      cwd: tempDir,
      env: environment,
      input: prompt,
      encoding: 'utf8',
      timeout: options.timeoutMs,
      maxBuffer: 1024 * 1024,
      windowsHide: true,
    });

    if (result.error) {
      if (result.error.code === 'ETIMEDOUT') throw new Error('Codex review timed out');
      if (result.error.code === 'ENOENT') throw new Error('Codex CLI is not installed');
      throw new Error(`Codex invocation failed: ${result.error.message}`);
    }
    if (result.status !== 0) {
      const detail = (result.stderr || '').trim().split('\n').slice(-1)[0];
      throw new Error(`Codex review failed${detail ? `: ${detail}` : ''}`);
    }

    let text;
    try {
      text = readFile(outputFile, 'utf8').trim();
    } catch (error) {
      throw new Error(`Codex returned no final response: ${error.message}`);
    }
    if (!text) throw new Error('Codex returned an empty final response');
    return `${providerLabel(options.hostProvider)}\n${text}`;
  } finally {
    remove(tempDir, { recursive: true, force: true });

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Increase options.timeoutMs toward the 120000 ceiling (via --timeout-seconds 120 on the CLI).
  2. Reduce the prompt size to speed up the round trip.
  3. Retry transiently; if it persists, run `codex` manually with the same packet to see where time is spent.
  4. Check network/proxy connectivity to OpenAI.

Example fix

// before
runReview(packet, { consent: true, timeoutMs: 30_000, hostProvider: 'openai' });

// after
runReview(packet, { consent: true, timeoutMs: 120_000, hostProvider: 'openai' });
Defensive patterns

Strategy: retry

Validate before calling

const MAX_TIMEOUT_MS = 120_000;
// choose the largest safe budget so transient slowness does not trip the timeout
options.timeoutMs = Math.min(desiredTimeoutMs ?? 60_000, MAX_TIMEOUT_MS);

Try / catch

try {
  return await runReview(packet, options);
} catch (error) {
  if (error.message === 'Codex review timed out' && attempt < MAX_RETRIES) {
    await backoff(attempt);
    return runReview(packet, { ...options, timeoutMs: Math.min(options.timeoutMs * 1.5, 120_000) });
  }
  throw error;
}

Prevention

When it happens

Trigger: The codex subprocess runs longer than options.timeoutMs and is killed by Node, yielding result.error.code === 'ETIMEDOUT'.

Common situations: Slow or rate-limited network to OpenAI; cold start of the Codex CLI; a large prompt near the 64 KiB ceiling; overloaded API during peak hours; DNS/proxy latency.

Understand the failure class

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/c7a2d2a3ddae8d95. Report an issue: GitHub.