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
- Strip undefined properties before canonicalization: Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== undefined)).
- Delete the key instead of assigning undefined at construction time.
- Use null where a JSON-representable absent value is needed.
- 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
- Prefer deleting keys over assigning undefined when a field is absent.
- When merging partial configs, filter undefined entries explicitly.
- Use a schema validator (zod) that rejects undefined on optional fields.
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
- undefined array member at ${path}[${i}]
- unsupported JSON value at ${path}
- ${label} requires a JSON argument
- ${label} must be valid JSON
- ${toolsJson} must contain a JSON array of {name, description
AI-assisted analysis of ruvnet/ruflo@6b01dc5a68 (2026-08-12).
Data as JSON: /api/errors/7106ba092049bfae.
Report an issue: GitHub.