ruvnet/ruflo · error · Error

undefined property at ${path}.${key}

Error message

undefined property at ${path}.${key}

What it means

Thrown by assertJsonValue() when an object property's value is `undefined`. Object.entries() yields keys whose value is undefined (unlike JSON.stringify which silently drops them), so canonicalization must reject them explicitly to keep the hash deterministic across the JSON domain. The ${path}.${key} placeholder names the offending property.

Source

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

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.
 */
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(',')}}`;

View on GitHub (pinned to 6b01dc5a68)

Solutions

  1. Strip undefined properties before canonicalization: Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)).
  2. Delete the key instead of assigning undefined at construction time.
  3. Use null where a JSON-representable absent value is needed.
  4. Validate the policy object with a helper that rejects undefined values recursively.

Example fix

// before
const policy = { ...base, alpha: opts.alpha };
// opts.alpha is undefined → triggers on canonicalizeJcs
// after
const policy = { ...base };
if (opts.alpha !== undefined) policy.alpha = opts.alpha;
Defensive patterns

Strategy: validation

Validate before calling

function stripUndefinedProps<T extends Record<string, unknown>>(obj: T): Partial<T> {
  return Object.fromEntries(
    Object.entries(obj).filter(([, v]) => v !== undefined)
  ) as Partial<T>;
}
const cleanPolicy = stripUndefinedProps(candidatePolicy);

Type guard

function hasNoUndefinedProps(value: unknown): boolean {
  if (value && typeof value === 'object') {
    return Object.entries(value).every(([k, v]) => v !== undefined && hasNoUndefinedProps(v));
  }
  return value !== undefined;
}

Try / catch

try {
  canonicalizeJcs(payload);
} catch (e) {
  if (e instanceof Error && /undefined property/.test(e.message)) {
    // strip and retry, or surface a precise config error
    payload = JSON.parse(JSON.stringify(payload));
  } else throw e;
}

Prevention

When it happens

Trigger: Passing an object to createFlywheelReceipt()/canonicalizeJcs() where a property was set to undefined (e.g. { a: 1, b: undefined }) rather than deleted. Common with spread/merge of partial configs, optional fields defaulted to undefined, or JSON parsed then re-typed.

Common situations: Candidate policy assembled via { ...defaults, tuningParam: opts.tuningParam } where opts.tuningParam is undefined; resourceEvidence or gate maps with conditionally-undefined entries.

Related errors


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