{"record":{"id":"0233a30a576704b0","repo":"JuliusBrussee/caveman","slug":"cave-setup-timeout-must-be-a-positive-integer-got-0233a3","errorCode":null,"errorMessage":"CAVE_SETUP_TIMEOUT must be a positive integer (got ${JSON.stringify(raw)})","messagePattern":"CAVE_SETUP_TIMEOUT must be a positive integer \\(got (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/shared/binary-installer/installer.mjs","lineNumber":82,"sourceCode":"\nexport function targetPlatform(os = process.platform, nodeArch = process.arch) {\n  const arch = nodeArch === \"x64\" ? \"amd64\" : nodeArch;\n  if (!(os === \"darwin\" || os === \"linux\" || os === \"win32\") ||\n      !(arch === \"arm64\" || arch === \"amd64\")) {\n    throw new Error(`no prebuilt binary for ${os}/${arch} — supported: darwin/arm64, darwin/amd64, linux/arm64, linux/amd64, win32/arm64, win32/amd64`);\n  }\n  return { os, arch };\n}\n\nexport function binaryInstallFilename(name, os = process.platform) {\n  return os === \"win32\" ? `${name}.exe` : name;\n}\n\nfunction timeoutMs() {\n  const raw = process.env.CAVE_SETUP_TIMEOUT ?? \"300\";\n  const seconds = Number(raw);\n  if (!Number.isInteger(seconds) || seconds <= 0) {\n    throw new Error(`CAVE_SETUP_TIMEOUT must be a positive integer (got ${JSON.stringify(raw)})`);\n  }\n  return seconds * 1000;\n}\n\nasync function asset(url, timeout) {\n  let response;\n  try {\n    response = await fetch(url, { signal: AbortSignal.timeout(timeout) });\n  } catch (error) {\n    throw new Error(`binary download failed: ${error.message}`);\n  }\n  if (!response.ok) throw new Error(`binary download failed: HTTP ${response.status}`);\n  return response;\n}\n\nfunction signedDigest(checksums, bundleRaw) {\n  try {\n    const bundle = JSON.parse(bundleRaw);","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/shared/binary-installer/installer.mjs#L64-L100","documentation":"timeoutMs() parses the CAVE_SETUP_TIMEOUT environment variable (default 300 seconds) and throws unless Number() of it is a positive integer. This guards the AbortSignal.timeout used for every installer fetch, so a garbage value fails before any download starts rather than mid-install.","triggerScenarios":"Setting CAVE_SETUP_TIMEOUT to an empty string, \"abc\", \"30s\", \"0\", \"-5\", \"1.5\", or values with trailing whitespace — Number() yields NaN, 0, negative, or non-integer and the check rejects it.","commonSituations":"Copy-pasting units into env vars (\"300s\", \"5m\"); Docker ENV or CI secret matrices injecting empty strings; shell quoting mishaps; assuming milliseconds.","solutions":["Set the value to a plain positive integer of seconds: CAVE_SETUP_TIMEOUT=300","Unset the variable entirely to accept the 300-second default","Check for stray whitespace or units where the variable is defined (.env, compose file, CI settings)"],"exampleFix":"# before\nCAVE_SETUP_TIMEOUT=300s   # or =0, =1.5\n\n# after\nCAVE_SETUP_TIMEOUT=300    # seconds, integer > 0 — or omit entirely","handlingStrategy":"validation","validationCode":"if (process.env.CAVE_SETUP_TIMEOUT !== undefined) {\n  const n = Number(process.env.CAVE_SETUP_TIMEOUT);\n  if (!Number.isInteger(n) || n <= 0) delete process.env.CAVE_SETUP_TIMEOUT; // fall back to 300\n}","typeGuard":"function isValidTimeout(v) {\n  if (v === undefined) return true; // the default applies\n  const n = Number(v);\n  return Number.isInteger(n) && n > 0;\n}","tryCatchPattern":null,"preventionTips":["Document env values as bare seconds in .env templates","Lint CI env matrices for empty strings leaking into numeric settings"],"tags":["configuration","env-var","installer"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}