ruvnet/ruflo · error · Error

unsupported JSON value at ${path}

Error message

unsupported JSON value at ${path}

What it means

Thrown by assertJsonValue() when a value is not null, string, boolean, number, array, or plain object — i.e. it falls outside the canonical JSON domain. This catches Symbol, Function, BigInt, class instances, and any other non-JSON-serializable type that would break deterministic hashing.

Source

Thrown at v3/@claude-flow/cli/src/services/flywheel-receipt.ts:169

      throw new Error(`non-canonical number at ${path}`);
    }
    return;
  }
  if (Array.isArray(value)) {
    value.forEach((v, i) => {
      if (v === undefined) throw new Error(`undefined array member at ${path}[${i}]`);
      assertJsonValue(v, `${path}[${i}]`);
    });
    return;
  }
  if (typeof value === 'object') {
    for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
      if (child === undefined) throw new Error(`undefined property at ${path}.${key}`);
      assertJsonValue(child, `${path}.${key}`);
    }
    return;
  }
  throw new Error(`unsupported JSON value at ${path}`);
}

/**
 * RFC-8785-compatible for the JSON domain accepted above: ECMAScript primitive
 * serialization plus recursively sorted UTF-16 property names.
 */
export function canonicalizeJcs(value: unknown): string {
  assertJsonValue(value);
  const encode = (v: unknown): string => {
    if (v === null || typeof v !== 'object') return JSON.stringify(v);
    if (Array.isArray(v)) return `[${v.map(encode).join(',')}]`;
    const obj = v as Record<string, unknown>;
    return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${encode(obj[k])}`).join(',')}}`;
  };
  return encode(value);
}

export function sha256Ref(value: string | Buffer): string {

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Convert BigInt fields to string or number before passing (e.g. ts.toString()).
  2. Serialize class instances to plain objects (JSON.parse(JSON.stringify(obj)) or a manual mapper).
  3. Strip Symbol/Function values from the object tree before canonicalization.
  4. Ensure only plain Record<string, unknown> / JSON-native values reach canonicalizeJcs().

Example fix

// before
const policy = { ts: 1700000000n, weight: 0.5 };
policyCandidateId(policy);
// after
const policy = { ts: '1700000000', weight: 0.5 };
policyCandidateId(policy);
Defensive patterns

Strategy: type-guard

Validate before calling

function assertJsonSafe(value: unknown, path = '$'): void {
  if (value === null || typeof value === 'string' || typeof value === 'boolean') return;
  if (typeof value === 'number') return;
  if (Array.isArray(value)) { value.forEach((v, i) => assertJsonSafe(v, `${path}[${i}]`)); return; }
  if (value && typeof value === 'object' && Object.getPrototypeOf(value) === Object.prototype || value && typeof value === 'object' && Object.getPrototypeOf(value) === null) {
    for (const [k, v] of Object.entries(value)) assertJsonSafe(v, `${path}.${k}`);
    return;
  }
  throw new TypeError(`non-JSON value at ${path}: ${typeof value}`);
}
assertJsonSafe(candidatePolicy);

Type guard

function isPlainJsonValue(value: unknown): value is null | string | boolean | number | unknown[] | Record<string, unknown> {
  if (value === null || ['string', 'boolean', 'number'].includes(typeof value)) return true;
  if (Array.isArray(value)) return value.every(isPlainJsonValue);
  if (value && typeof value === 'object') {
    const proto = Object.getPrototypeOf(value);
    if (proto !== Object.prototype && proto !== null) return false;
    return Object.values(value).every(isPlainJsonValue);
  }
  return false;
}

Try / catch

try {
  policyCandidateId(policy);
} catch (e) {
  if (e instanceof Error && /unsupported JSON value/.test(e.message)) {
    policy = JSON.parse(JSON.stringify(policy, (_k, v) => typeof v === 'bigint' ? v.toString() : v));
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a value containing a BigInt (e.g. a timestamp as BigInt), a Symbol-keyed or Symbol-valued entry, a Function reference, a class instance (not a plain object literal), or a Map/Set to canonicalizeJcs(), createFlywheelReceipt(), or policyCandidateId().

Common situations: Candidate policy that includes a BigInt metric (common in crypto/timestamp code); a policy object that is a class instance rather than a plain record; values produced by libraries that return custom types.

Related errors


AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12). Data as JSON: /api/errors/92a81b1722720415. Report an issue: GitHub.