affaan-m/ECC · error · Error
timeout is outside the 10-120 second safety range
Error message
timeout is outside the 10-120 second safety range
What it means
runReview validates the requested Codex subprocess timeout before spawning the CLI. Node spawnSync accepts any value, but this library deliberately confines timeouts to 10-120 seconds so a review cannot hang forever or kill Codex before it starts producing output. If options.timeoutMs is below 10000 or above MAX_TIMEOUT_MS (120000), the call is rejected before any process is started.
Solutions
- Set timeoutMs to a value between 10000 and 120000 milliseconds (e.g. 60000).
- Clamp user-supplied values: timeoutMs = Math.min(Math.max(Number(timeoutMs) || 60000, 10000), 120000).
- If a review needs more than 120s, split the prompt into smaller review packets instead of raising the timeout.
- Ensure timeoutMs is a number, not a string from CLI args (use Number() before calling).
Example fix
// before
await runReview({ prompt, consent: true, timeoutMs: 5000 });
// after
await runReview({ prompt, consent: true, timeoutMs: 60_000 }); // within 10s-120s Defensive patterns
Strategy: validation
Validate before calling
function isValidTimeout(ms) {
return typeof ms === 'number' && Number.isFinite(ms) && ms >= 10_000 && ms <= 120_000;
}
if (!isValidTimeout(opts.timeoutMs)) throw new RangeError('timeoutMs must be 10000-120000 ms'); Type guard
const isTimeoutMs = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 10_000 && v <= 120_000;
Try / catch
try {
await runReview(options);
} catch (e) {
if (e.message.includes('safety range')) {
options.timeoutMs = 60_000;
await runReview(options);
} else throw e;
} Prevention
- Define a single TIMEOUT_MS constant (60000) and derive all timeouts from it.
- Clamp CLI/env-supplied timeouts with Math.min/Math.max before calling runReview.
- Always convert string inputs with Number() before numeric range checks.
When it happens
Trigger: Calling runReview({ ..., timeoutMs: 5000 }) or runReview({ ..., timeoutMs: 300000 }) — any value outside the inclusive 10000..120000 ms window, including undefined coerced to NaN by the comparison.
Common situations: Developer sets a 5-second timeout expecting a fast check; developer reuses a general-purpose 10-minute timeout constant from another tool; timeoutMs passed as a string or omitted so the numeric comparison fails.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- --timeout-seconds must be an integer from 10 to 120
- --all-harnesses and --harness are mutually exclusive.
- all overlays must be readable local files
- all takes must be readable local files
- Arguments must not contain NUL bytes.
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/a96f334bd756a5a1.
Report an issue: GitHub.
Appendix: source
Thrown at skills/council-multi-model/scripts/review-with-codex.js:188
function buildEnvironment(sourceEnv = process.env) {
const allowed = [
'PATH', 'HOME', 'USERPROFILE', 'CODEX_HOME',
'TMPDIR', 'TMP', 'TEMP', 'SystemRoot', 'ComSpec', 'PATHEXT',
];
return Object.fromEntries(
allowed.filter((name) => sourceEnv[name]).map((name) => [name, sourceEnv[name]])
);
}
function runReview(prompt, options, dependencies = {}) {
if (!prompt.trim()) throw new Error('review packet is empty');
if (Buffer.byteLength(prompt, 'utf8') > MAX_PROMPT_BYTES) {
throw new Error(`review packet exceeds ${MAX_PROMPT_BYTES} bytes`);
}
if (!options.consent) throw new Error('OpenAI transfer consent is required');
if (options.timeoutMs < 10_000 || options.timeoutMs > MAX_TIMEOUT_MS) {
throw new Error('timeout is outside the 10-120 second safety range');
}
const spawn = dependencies.spawnSync || spawnSync;
const environment = buildEnvironment(dependencies.env || process.env);
const verifySupport = dependencies.verifyToollessSupport || verifyToollessSupport;
verifySupport({ spawnSync: spawn, env: environment });
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',View on GitHub (pinned to 8321021c54)