ruvnet/ruflo · error · Error

undefined array member at ${path}[${i}]

Error message

undefined array member at ${path}[${i}]

What it means

Thrown by assertJsonValue() inside canonicalizeJcs() during RFC-8785 JCS-style canonicalization. JSON has no 'undefined' type, so an array element that is literally the JavaScript value `undefined` cannot be serialized canonically and would make the receipt hash non-reproducible. The ${path}[${i}] placeholder points at the exact offending location in the value tree (e.g. $.heldOutDeltas[2]).

Source

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

  termVerification?: TermVerification[];
  now?: number;
  ttlMs?: number;
  privateKeyPem?: string;
  publicKeyPem?: string;
  bootstrapIterations?: number;
}

function assertJsonValue(value: unknown, path = '$'): void {
  if (value === null || typeof value === 'string' || typeof value === 'boolean') return;
  if (typeof value === 'number') {
    if (!Number.isFinite(value) || Object.is(value, -0)) {
      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.
 */

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Sanitize arrays before receipt creation: arr.filter((x) => x !== undefined).
  2. Replace undefined slots with null (a valid JSON type) only if the consuming code treats null semantically.
  3. Delete the property/omit the element at the source so the array never carries undefined.
  4. If the value comes from external/JSON input, parse with a reviver that drops undefined.

Example fix

// before
const policy = { weights: [1, undefined, 3] };
createFlywheelReceipt({ candidatePolicy: policy, /* ... */ });
// after
const policy = { weights: [1, undefined, 3].filter((x) => x !== undefined) };
createFlywheelReceipt({ candidatePolicy: policy, /* ... */ });
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeForCanonical<T>(value: T): T {
  if (Array.isArray(value)) return value.filter((v) => v !== undefined).map(sanitizeForCanonical) as T;
  if (value && typeof value === 'object') {
    return Object.fromEntries(
      Object.entries(value as Record<string, unknown>)
        .filter(([, v]) => v !== undefined)
        .map(([k, v]) => [k, sanitizeForCanonical(v)])
    ) as T;
  }
  return value;
}
// run before createFlywheelReceipt / canonicalizeJcs:
const safe = sanitizeForCanonical(candidatePolicy);

Type guard

function hasNoUndefined(value: unknown): boolean {
  if (value === undefined) return false;
  if (Array.isArray(value)) return value.every(hasNoUndefined);
  if (value && typeof value === 'object')
    return Object.values(value).every(hasNoUndefined);
  return true;
}

Try / catch

try {
  const ref = policyCandidateId(policy);
} catch (e) {
  if (e instanceof Error && /undefined array member/.test(e.message)) {
    throw new Error(`candidatePolicy is not JSON-canonical: ${e.message}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling createFlywheelReceipt(), canonicalizeJcs(), policyCandidateId(), or sha256Ref() with a candidatePolicy / heldOutDeltas / any nested object that contains an array slot holding `undefined` (e.g. arr[2] = undefined, or [1, , 3] sparse-array coercion, or a map/filter that left an undefined element).

Common situations: A candidatePolicy record built from partial config where an array field was conditionally assigned undefined instead of being omitted; heldOutDeltas produced by a pipeline that can yield NaN/undefined slots; objects deserialized then re-extended with undefined defaults.

Related errors


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