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' }
      break

View on GitHub (pinned to adc986b362)

Solutions

  1. Break the cycle on the encode side: encode one direction of the reference only, or represent back-references as ids rather than nested nodes.
  2. Validate the arena is a DAG before decoding (DFS with the same visiting-set logic).
  3. If producing arenas with the library's own encoder, report/upgrade — self-produced cyclic arenas should not be encodable.
  4. 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

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


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/e464a66e33659e63. Report an issue: GitHub.