ruvnet/ruflo · error · Error

${label} must be a canonical unsigned decimal integer

Error message

${label} must be a canonical unsigned decimal integer

What it means

parseCanonicalUnsigned() accepts exactly one spelling of an unsigned integer: /^(?:0|[1-9][0-9]*)$/. It rejects leading zeros, '+', '0x' hex, underscores, surrounding whitespace, empty strings, and negatives. It is used for protocol fields such as inbox message sequence/cursor and fenced-lease version, where non-canonical spellings would break equality and ordering comparisons.

Source

Thrown at v3/@claude-flow/codex/src/harness/unsigned-integer.ts:7

const CANONICAL_UNSIGNED = /^(?:0|[1-9][0-9]*)$/;
const MAX_UNSIGNED_64 = (1n << 64n) - 1n;

/** Parse a decimal unsigned integer without accepting aliases such as +1, 01, hex, or whitespace. */
export function parseCanonicalUnsigned(value: string, label: string): bigint {
  if (!CANONICAL_UNSIGNED.test(value)) {
    throw new Error(`${label} must be a canonical unsigned decimal integer`);
  }
  const parsed = BigInt(value);
  if (parsed > MAX_UNSIGNED_64) {
    throw new Error(`${label} exceeds unsigned 64-bit range`);
  }
  return parsed;
}

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Check the label in the message ('message sequence', 'inbox cursor', 'lease version') to find the offending field
  2. Emit plain decimal via BigInt.prototype.toString() or String(Number) and never pad or sign values
  3. Round-trip test your serializer against /^(?:0|[1-9][0-9]*)$/ before sending

Example fix

// before
cursor: String(seq).padStart(4, '0'); // '0042' -> throws
// after
cursor: BigInt(seq).toString(); // '42'
Defensive patterns

Strategy: type-guard

Validate before calling

const CANONICAL = /^(?:0|[1-9][0-9]*)$/;
function isCanonicalUnsigned(v: string): boolean { return CANONICAL.test(v); }

Type guard

const CANONICAL_UNSIGNED = /^(?:0|[1-9][0-9]*)$/;
function isCanonicalUnsigned(v: unknown): v is string {
  return typeof v === 'string' && CANONICAL_UNSIGNED.test(v);
}

Try / catch

try { parseCanonicalUnsigned(cursor, 'inbox cursor'); } catch (e) { if (/canonical unsigned/.test(String(e))) return rejectPeer('non-canonical integer field'); throw e; }

Prevention

When it happens

Trigger: Passing '01', '+1', ' 1', '0x10', '1_000', '-5', '' or 1e21-derived strings as message.sequence, inbox cursor, or lease version; JSON codecs that reformat numbers (padding, exponent form) feed these fields.

Common situations: A peer implementation serializes integers with a different formatter; a database or JSON store returns '01'; hand-crafted test fixtures use padded strings; values were concatenated instead of parsed then re-stringified.

Related errors


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