ruvnet/ruflo · error · Error

canonical JSON does not support ${typeof item}

Error message

canonical JSON does not support ${typeof item}

What it means

This branch catches any remaining non-object value that is not null/boolean/string/number/undefined — in practice symbol, bigint, and function — and embeds its typeof in the message (e.g. 'canonical JSON does not support bigint'). JSON has no representation for these types, and silently stringifying them would produce non-canonical output, so they are rejected with the type named.

Source

Thrown at v3/@claude-flow/codex/src/harness/repository-state.ts:129

  const encode = (item: unknown): string => {
    if (item === null || typeof item === 'boolean') return JSON.stringify(item);
    if (typeof item === 'string') {
      assertUnicodeScalarString(item);
      return JSON.stringify(item);
    }
    if (typeof item === 'number') {
      if (
        !Number.isFinite(item)
        || Object.is(item, -0)
        || (Number.isInteger(item) && !Number.isSafeInteger(item))
      ) {
        throw new Error('canonical JSON requires finite, safe, non-negative-zero numbers');
      }
      return JSON.stringify(item);
    }
    if (item === undefined) throw new Error('canonical JSON does not support undefined');
    if (typeof item !== 'object') {
      throw new Error(`canonical JSON does not support ${typeof item}`);
    }
    if (ancestors.has(item)) throw new Error('canonical JSON does not support cycles');
    ancestors.add(item);
    try {
      if (Array.isArray(item)) return `[${item.map(encode).join(',')}]`;
      const prototype = Object.getPrototypeOf(item);
      if (prototype !== Object.prototype && prototype !== null) {
        throw new Error('canonical JSON supports only arrays and plain objects');
      }
      const entries = Object.entries(item as Record<string, unknown>)
        .sort(([left], [right]) => codeUnitCompare(left, right));
      return `{${entries.map(([key, child]) => {
        assertUnicodeScalarString(key);
        return `${JSON.stringify(key)}:${encode(child)}`;
      }).join(',')}}`;
    } finally {
      ancestors.delete(item);
    }

View on GitHub (pinned to fa13ee4ad6)

Solutions

  1. Convert bigint leaves to strings while building the payload (String(n) or n.toString())
  2. Pass evidence through a JSON.stringify replacer pre-pass that stringifies bigint and drops symbols/functions
  3. Type payloads as a JsonValue structure so the compiler rejects these types at the boundary

Example fix

// before
canonicalJson({ fileId: BigInt('9007199254740993') });

// after
canonicalJson({ fileId: '9007199254740993' });
Defensive patterns

Strategy: type-guard

Validate before calling

function toCanonicalValue(value: unknown): unknown {
  if (typeof value === 'bigint') return value.toString();
  if (typeof value === 'symbol' || typeof value === 'function') return undefined; // drop
  if (Array.isArray(value)) return value.map(toCanonicalValue);
  if (typeof value === 'object' && value !== null) {
    return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, toCanonicalValue(v)]));
  }
  return value;
}

Type guard

type CanonicalJsonValue = null | boolean | number | string | CanonicalJsonValue[] | { [key: string]: CanonicalJsonValue };
function isCanonicalJsonValue(value: unknown): value is CanonicalJsonValue {
  if (value === null || ['boolean', 'number', 'string'].includes(typeof value)) return true;
  if (Array.isArray(value)) return value.every(isCanonicalJsonValue);
  if (typeof value === 'object') {
    return Object.getPrototypeOf(value) === Object.prototype
      && Object.values(value).every(isCanonicalJsonValue);
  }
  return false;
}

Try / catch

try {
  return canonicalJson(payload);
} catch (error) {
  if (error instanceof Error && error.message.startsWith('canonical JSON does not support ')
      && /bigint|symbol|function/.test(error.message)) {
    return canonicalJson(toCanonicalValue(payload));
  }
  throw error;
}

Prevention

When it happens

Trigger: Evidence containing BigInt hashes or 64-bit integer IDs from crypto/database clients (postgres int64, BSON Long); Symbol values or symbol-keyed data leaking through spreads; class methods enumerated into the payload.

Common situations: node:crypto or driver APIs returning BigInt; schemas parsed with bigint support; passing rich objects whose enumerable properties include bound functions.

Related errors


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