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
- Increase options.timeoutMs toward the 120000 ceiling (via --timeout-seconds 120 on the CLI).
- Reduce the prompt size to speed up the round trip.
- Retry transiently; if it persists, run `codex` manually with the same packet to see where time is spent.
- 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
- Use the largest safe timeout (120s) for batch reviews to absorb network jitter.
- Keep prompts well under 64 KiB so the round trip is fast.
- Monitor codex latency out-of-band so you notice a degrading API before timeouts cascade.
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
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- timeout is outside the 10-120 second safety range
- Codex review failed${detail ? `: ${detail}` : ''}
- Codex returned no final response: ${error.message}
- fetch %s: %w
- git push failed: {stderr}
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/c7a2d2a3ddae8d95.
Report an issue: GitHub.