ruvnet/ruflo · error

build input bytes must be a non-negative safe integer

Error message

build input bytes must be a non-negative safe integer

What it means

Each DeclaredBuildInput.bytes value must satisfy Number.isSafeInteger(bytes) && bytes >= 0. Negative values, fractional values, NaN, Infinity, and integers beyond Number.MAX_SAFE_INTEGER (2^53-1) all throw when createBuildEvidence validates the folded input list, because the evidence contract must hash to identical bytes on every platform.

Source

Thrown at v3/@claude-flow/codex/src/harness/build-evidence.ts:120

export function createBuildEvidence(
  sourceState: ExactSourceState,
  buildInputs: readonly DeclaredBuildInput[],
  toolchains: readonly DeclaredToolchain[],
): BuildEvidence {
  const inputs = buildInputs.map((input) => ({
    name: requireText(input.name, 'build input name'),
    path: normalizePath(input.path),
    digest: requireDigest(input.digest, 'build input digest'),
    bytes: input.bytes,
  })).sort((left, right) => compare(left.path, right.path) || compare(left.name, right.name));
  const tools = toolchains.map((toolchain) => ({
    name: requireText(toolchain.name, 'toolchain name'),
    version: requireText(toolchain.version, 'toolchain version'),
    digest: requireDigest(toolchain.digest, 'toolchain digest'),
  })).sort((left, right) => compare(left.name, right.name) || compare(left.version, right.version));

  if (inputs.some(({ bytes }) => !Number.isSafeInteger(bytes) || bytes < 0)) {
    throw new Error('build input bytes must be a non-negative safe integer');
  }
  const inputKeys = inputs.map(({ name, path }) => `${name}\0${path}`);
  if (new Set(inputKeys).size !== inputKeys.length) throw new Error('duplicate declared build input');
  const foldedPaths = inputs.map(({ path }) => portableCaseFold(path));
  if (new Set(foldedPaths).size !== foldedPaths.length) {
    throw new Error('case-fold collision in declared build inputs');
  }
  const toolKeys = tools.map(({ name, version }) => `${name}\0${version}`);
  if (new Set(toolKeys).size !== toolKeys.length) throw new Error('duplicate declared toolchain');

  const body = {
    contractVersion: 1 as const,
    assurance: 'declared-unsigned' as const,
    sourceStateId: requireDigest(sourceState.sourceStateId, 'source state id'),
    buildInputs: inputs,
    toolchains: tools,
  };
  return { ...body, evidenceDigest: sha256(canonicalJson(body)) };

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Audit every buildInputs[].bytes value — all must be whole numbers ≥ 0 and ≤ 2^53-1
  2. Fix the measurement source: use integer byte counts from fs stat (stat.size), not derived floats
  3. If a genuine input exceeds 2^53-1 bytes, split it or declare a manifest of it — the contract cannot represent that size

Example fix

// before
bytes: stats.size / 1024, // fractional KB value
bytes: -1,

// after
bytes: stats.size, // integer byte length from fs.Stats
Defensive patterns

Strategy: validation

Validate before calling

function isValidByteCount(bytes: unknown): bytes is number {
  return typeof bytes === 'number' && Number.isSafeInteger(bytes) && bytes >= 0;
}

Type guard

function isValidByteCount(bytes: unknown): bytes is number {
  return typeof bytes === 'number' && Number.isSafeInteger(bytes) && bytes >= 0;
}

Prevention

When it happens

Trigger: bytes set to -1; bytes computed as a float (averages, unit conversions, sizes divided by 1024.0); NaN propagated from parsing; a byte count larger than 2^53-1.

Common situations: Sizes measured by external tools and parsed from strings; BigInt file sizes converted with Number() losing precision; uninitialized size fields defaulting to NaN in generated declarations.

Related errors


AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18). Data as JSON: /api/errors/0de45ddf4fa11b36. Report an issue: GitHub.