pydantic/monty · error · Error
component value arena contains a cycle
Error message
component value arena contains a cycle
What it means
While reading a class-type's component value nodes, the decoder keeps a `visiting` set of in-flight indexes; re-entering an index that is already being read means the arena graph contains a cycle (e.g. a class type whose attribute value loops back to the class type itself). This Error aborts decoding instead of recursing forever. Note readValueNode leaves the index marked as visiting, so parent-level cycles are caught too.
Source
Thrown at crates/monty-js/ts/worker/value.ts:270
return {
tag: 'file-handle',
val: { path: object.path, mode: canonicalFileMode(object.mode), position: BigInt(position) },
}
}
/** Appends key/value pairs while preserving their insertion order. */
function pushPairs(pairs: [unknown, unknown][], nodes: ValueNode[]): NodePair[] {
return pairs.map(([key, value]) => ({ key: pushValue(key, nodes), value: pushValue(value, nodes) }))
}
/** Fetches a raw arena node by index with the same bounds/cycle checks as
* `readValue`, for callers that must inspect the node's tag. The index stays
* marked as visiting, so a parent cycle in class-type nodes throws instead
* of recursing forever. */
function readValueNode(index: number, nodes: ValueNode[], visiting: Set<number>): ValueNode {
const node = nodes[index]
if (node === undefined) throw new Error(`component value node index ${index} is out of bounds`)
if (visiting.has(index)) throw new Error('component value arena contains a cycle')
visiting.add(index)
return node
}
/** Reads one arena node recursively, rejecting malformed indexes and cycles. */
function readValue(index: number, nodes: ValueNode[], visiting: Set<number>): unknown {
const node = nodes[index]
if (node === undefined) throw new Error(`component value node index ${index} is out of bounds`)
if (visiting.has(index)) throw new Error('component value arena contains a cycle')
visiting.add(index)
let value: unknown
switch (node.tag) {
case 'ellipsis':
value = { [TYPE_MARKER]: 'Ellipsis' }
break
case 'not-implemented':
value = { [TYPE_MARKER]: 'NotImplemented' }
breakView on GitHub (pinned to adc986b362)
Solutions
- Break the cycle on the encode side: encode one direction of the reference only, or represent back-references as ids rather than nested nodes.
- Validate the arena is a DAG before decoding (DFS with the same visiting-set logic).
- If producing arenas with the library's own encoder, report/upgrade — self-produced cyclic arenas should not be encodable.
- Pin matching versions of the monty binary and the JS package so both sides agree the format is acyclic.
Example fix
// before // classNode.attrs = [['parent', <index of classNode itself>]] // after // classNode.attrs = [['name', <index of a leaf value node>]]
Defensive patterns
Strategy: validation
Validate before calling
function assertAcyclic(nodes) {
const visiting = new Set();
function dfs(i) {
if (visiting.has(i)) throw new Error(`cycle at node ${i}`);
visiting.add(i);
for (const ref of referencedIndexes(nodes[i])) dfs(ref);
visiting.delete(i);
}
dfs(0);
} Try / catch
try {
const value = decodeValue(nodes);
} catch (e) {
if (e instanceof Error && e.message === 'component value arena contains a cycle') {
throw new Error('encoder produced a cyclic arena; back-references must use identity ids');
} else throw e;
} Prevention
- Never encode recursive JS objects directly — break cycles before building the arena
- Represent back-references with uuid identity nodes, not repeated indexes
- Run an acyclicity check (DFS with visiting set) on any hand-built arena
- Keep encoder and decoder versions aligned so both agree the format is acyclic
When it happens
Trigger: Decoding an arena where a class-type node's attr value index chain returns to an ancestor node; hand-built arenas that model cyclic structures (which the flat format does not allow); a producer bug that emits self-referencing nodes.
Common situations: Custom encoders attempting to serialize recursive JS objects (e.g. `obj.self = obj`) into the arena without cycle detection on the encode side; a mismatch between the Rust encoder and the TS decoder versions.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- component value node index ${index} is out of bounds
- Unknown Monty marker type: ${String(object[TYPE_MARKER])}
- ClassType attrs entries must be [name, value] pairs
- ClassType attr name must be a string
- ClassType attr value missing
AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13).
Data as JSON: /api/errors/e464a66e33659e63.
Report an issue: GitHub.