{"record":{"id":"3b4b1caffd6af6e0","repo":"stablyai/orca","slug":"hourly-build-number-must-be-a-positive-integer","errorCode":null,"errorMessage":"Hourly build number must be a positive integer: ${buildNumber}","messagePattern":"Hourly build number must be a positive integer: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"config/scripts/hourly-build-version.mjs","lineNumber":68,"sourceCode":"  const prefix = `${baseVersion} • `\n  const highest = releaseNames.reduce((max, entry) => {\n    const name = String(entry ?? '')\n    if (!name.startsWith(prefix)) {\n      return max\n    }\n    const match = /^(\\d+) • /.exec(name.slice(prefix.length))\n    return match ? Math.max(max, Number(match[1])) : max\n  }, 0)\n  return highest + 1\n}\n\n/**\n * `1.4.163 • 01 • Jul 31, 1:54PM • e698241` — the human-facing release title,\n * shown verbatim in both the GitHub releases list and the in-app build picker.\n */\nexport function formatHourlyReleaseName(version, buildNumber, commit, date) {\n  if (!Number.isInteger(buildNumber) || buildNumber < 1) {\n    throw new Error(`Hourly build number must be a positive integer: ${buildNumber}`)\n  }\n  return [\n    version.split('-')[0],\n    String(buildNumber).padStart(2, '0'),\n    formatReleaseTitleTimestamp(date),\n    commit.slice(0, 7)\n  ].join(' • ')\n}\n\n// Why the number is derived here rather than passed in: it counts builds of the\n// base version, and the base is only known once the published tags have been\n// resolved just above. Computing it outside meant numbering against whatever\n// version the caller guessed.\nexport function getHourlyBuildIdentity(now = new Date(), { publishedVersions, releaseNames } = {}) {\n  const packageJson = JSON.parse(readFileSync(resolve('package.json'), 'utf8'))\n  const commit = execFileSync('git', ['rev-parse', '--short=12', 'HEAD'], {\n    encoding: 'utf8'\n  }).trim()","sourceCodeStart":50,"sourceCodeEnd":86,"githubUrl":"https://github.com/stablyai/orca/blob/1136503c6a231a16dce8f921f6fadb63d181e8db/config/scripts/hourly-build-version.mjs#L50-L86","documentation":"formatHourlyReleaseName assembles the human-facing hourly release title (e.g. '1.4.163 • 01 • ... • e698241'), shown verbatim in the GitHub releases list and the in-app build picker. It throws defensively when buildNumber is not a positive integer because the title zero-pads the number with padStart(2,'0') and a non-integer would corrupt the format. The normal path derives buildNumber via nextHourlyBuildNumber (always a positive integer starting at 1), so this guard mainly catches direct or test callers that bypass that derivation.","triggerScenarios":"Calling formatHourlyReleaseName(version, buildNumber, commit, date) with buildNumber of 0, a negative number, a float, NaN, or a non-number. A caller that hand-computes the number or passes a value parsed from an unexpected source can hit it.","commonSituations":"Unit tests invoking formatHourlyReleaseName directly with a fixture buildNumber of 0 or undefined; a refactor that stops routing through nextHourlyBuildNumber/getHourlyBuildIdentity.","solutions":["Route buildNumber through nextHourlyBuildNumber (or getHourlyBuildIdentity), which always yields a positive integer.","If calling formatHourlyReleaseName directly, validate/coerce buildNumber with Math.max(1, Math.floor(buildNumber)) before passing it.","Check the stack to confirm the value did not come from a malformed ORCA_HOURLY_RELEASE_NAMES entry feeding nextHourlyBuildNumber."],"exampleFix":"// before\nformatHourlyReleaseName(version, buildNumber, commit, date)\n\n// after\nconst buildNumber = nextHourlyBuildNumber(base, releaseNames)\nformatHourlyReleaseName(version, buildNumber, commit, date)","handlingStrategy":"type-guard","validationCode":"function isPositiveIntegerBuildNumber(value) {\n  return Number.isInteger(value) && value >= 1\n}\nif (!isPositiveIntegerBuildNumber(buildNumber)) {\n  buildNumber = nextHourlyBuildNumber(base, releaseNames)\n}","typeGuard":"function isPositiveBuildNumber(value: unknown): value is number {\n  return typeof value === 'number' && Number.isInteger(value) && value >= 1\n}","tryCatchPattern":null,"preventionTips":["Always derive buildNumber from nextHourlyBuildNumber or getHourlyBuildIdentity rather than computing it ad hoc.","In tests, pass explicit positive integers to formatHourlyReleaseName instead of 0 or undefined."],"tags":["validation","release","build-versioning"],"backgroundTag":null,"analyzedSha":"1136503c6a231a16dce8f921f6fadb63d181e8db","analyzedAt":"2026-08-12T23:15:58.167Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}