JuliusBrussee/caveman · error · Error
CAVE_SETUP_TIMEOUT must be a positive integer
Error message
CAVE_SETUP_TIMEOUT must be a positive integer (got ${JSON.stringify(raw)}) What it means
The installer reads the optional CAVE_SETUP_TIMEOUT env var (seconds) and converts it to milliseconds. `timeoutMs` throws when the variable is set but not parseable as a positive integer, since a bogus value would silently break download timeouts.
Solutions
- Set CAVE_SETUP_TIMEOUT to a plain positive integer, e.g. 300
- Remove the variable entirely to use the default of 300 seconds
- Fix the CI/env file so no stray characters (units, spaces, quotes) leak into the value
- If you need sub-second precision, the variable doesn't support it — keep whole seconds
Example fix
// before CAVE_SETUP_TIMEOUT=90s // after CAVE_SETUP_TIMEOUT=90
Defensive patterns
Strategy: validation
Validate before calling
const raw = process.env.CAVE_SETUP_TIMEOUT;
if (raw !== undefined && (!/^[1-9]\d*$/.test(raw.trim()))) {
throw new Error(`CAVE_SETUP_TIMEOUT must be a positive integer, got ${JSON.stringify(raw)}`);
} Try / catch
try {
await setup();
} catch (e) {
if (String(e.message).startsWith("CAVE_SETUP_TIMEOUT must be a positive integer")) {
console.error("Fix CAVE_SETUP_TIMEOUT (plain integer seconds) or unset it for the 300s default");
} else throw e;
} Prevention
- Set whole seconds with no units: CAVE_SETUP_TIMEOUT=600
- Never export the variable empty; unset it to use the default
- Validate env vars in CI before running setup steps
- Document that sub-second timeouts are not supported
When it happens
Trigger: CAVE_SETUP_TIMEOUT set to a non-integer ("12.5", "abc", ""), zero, a negative number, or a value with spaces/units ("300s").
Common situations: Typo in CI config adding a unit suffix; empty string from an unset-but-exported variable; decimal entered by someone expecting sub-second precision; locale-formatted number.
Understand the failure class
Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- caveman-code: model must use provider/model format
- caveman-code: multiple provider credentials found; set…
- : MCP ownership journal failed; native config may already…
- AbortError
- MCP transaction failed and rolled back
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/0233a30a576704b0.
Report an issue: GitHub.
Appendix: source
Thrown at packages/shared/binary-installer/installer.mjs:82
export function targetPlatform(os = process.platform, nodeArch = process.arch) {
const arch = nodeArch === "x64" ? "amd64" : nodeArch;
if (!(os === "darwin" || os === "linux" || os === "win32") ||
!(arch === "arm64" || arch === "amd64")) {
throw new Error(`no prebuilt binary for ${os}/${arch} — supported: darwin/arm64, darwin/amd64, linux/arm64, linux/amd64, win32/arm64, win32/amd64`);
}
return { os, arch };
}
export function binaryInstallFilename(name, os = process.platform) {
return os === "win32" ? `${name}.exe` : name;
}
function timeoutMs() {
const raw = process.env.CAVE_SETUP_TIMEOUT ?? "300";
const seconds = Number(raw);
if (!Number.isInteger(seconds) || seconds <= 0) {
throw new Error(`CAVE_SETUP_TIMEOUT must be a positive integer (got ${JSON.stringify(raw)})`);
}
return seconds * 1000;
}
async function asset(url, timeout) {
let response;
try {
response = await fetch(url, { signal: AbortSignal.timeout(timeout) });
} catch (error) {
throw new Error(`binary download failed: ${error.message}`);
}
if (!response.ok) throw new Error(`binary download failed: HTTP ${response.status}`);
return response;
}
function signedDigest(checksums, bundleRaw) {
try {
const bundle = JSON.parse(bundleRaw);View on GitHub (pinned to 3ee70a1026)