stablyai/orca · error · Error

Hourly build number must be a positive integer: ${buildNumbe

Error message

Hourly build number must be a positive integer: ${buildNumber}

What it means

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.

Source

Thrown at config/scripts/hourly-build-version.mjs:68

  const prefix = `${baseVersion} • `
  const highest = releaseNames.reduce((max, entry) => {
    const name = String(entry ?? '')
    if (!name.startsWith(prefix)) {
      return max
    }
    const match = /^(\d+) • /.exec(name.slice(prefix.length))
    return match ? Math.max(max, Number(match[1])) : max
  }, 0)
  return highest + 1
}

/**
 * `1.4.163 • 01 • Jul 31, 1:54PM • e698241` — the human-facing release title,
 * shown verbatim in both the GitHub releases list and the in-app build picker.
 */
export function formatHourlyReleaseName(version, buildNumber, commit, date) {
  if (!Number.isInteger(buildNumber) || buildNumber < 1) {
    throw new Error(`Hourly build number must be a positive integer: ${buildNumber}`)
  }
  return [
    version.split('-')[0],
    String(buildNumber).padStart(2, '0'),
    formatReleaseTitleTimestamp(date),
    commit.slice(0, 7)
  ].join(' • ')
}

// Why the number is derived here rather than passed in: it counts builds of the
// base version, and the base is only known once the published tags have been
// resolved just above. Computing it outside meant numbering against whatever
// version the caller guessed.
export function getHourlyBuildIdentity(now = new Date(), { publishedVersions, releaseNames } = {}) {
  const packageJson = JSON.parse(readFileSync(resolve('package.json'), 'utf8'))
  const commit = execFileSync('git', ['rev-parse', '--short=12', 'HEAD'], {
    encoding: 'utf8'
  }).trim()

View on GitHub (pinned to 1136503c6a)

Solutions

  1. Route buildNumber through nextHourlyBuildNumber (or getHourlyBuildIdentity), which always yields a positive integer.
  2. If calling formatHourlyReleaseName directly, validate/coerce buildNumber with Math.max(1, Math.floor(buildNumber)) before passing it.
  3. Check the stack to confirm the value did not come from a malformed ORCA_HOURLY_RELEASE_NAMES entry feeding nextHourlyBuildNumber.

Example fix

// before
formatHourlyReleaseName(version, buildNumber, commit, date)

// after
const buildNumber = nextHourlyBuildNumber(base, releaseNames)
formatHourlyReleaseName(version, buildNumber, commit, date)
Defensive patterns

Strategy: type-guard

Validate before calling

function isPositiveIntegerBuildNumber(value) {
  return Number.isInteger(value) && value >= 1
}
if (!isPositiveIntegerBuildNumber(buildNumber)) {
  buildNumber = nextHourlyBuildNumber(base, releaseNames)
}

Type guard

function isPositiveBuildNumber(value: unknown): value is number {
  return typeof value === 'number' && Number.isInteger(value) && value >= 1
}

Prevention

When it happens

Trigger: 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.

Common situations: Unit tests invoking formatHourlyReleaseName directly with a fixture buildNumber of 0 or undefined; a refactor that stops routing through nextHourlyBuildNumber/getHourlyBuildIdentity.

Related errors


AI-assisted analysis of stablyai/orca@1136503c6a (2026-08-12). Data as JSON: /api/errors/3b4b1caffd6af6e0. Report an issue: GitHub.