{"record":{"id":"48e7dfaafaec28ea","repo":"pydantic/monty","slug":"component-value-node-index-index-is-out-of-bounds","errorCode":null,"errorMessage":"component value node index ${index} is out of bounds","messagePattern":"component value node index (.+?) is out of bounds","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/worker/value.ts","lineNumber":269,"sourceCode":"  validateFilePosition(position)\n  return {\n    tag: 'file-handle',\n    val: { path: object.path, mode: canonicalFileMode(object.mode), position: BigInt(position) },\n  }\n}\n\n/** Appends key/value pairs while preserving their insertion order. */\nfunction pushPairs(pairs: [unknown, unknown][], nodes: ValueNode[]): NodePair[] {\n  return pairs.map(([key, value]) => ({ key: pushValue(key, nodes), value: pushValue(value, nodes) }))\n}\n\n/** Fetches a raw arena node by index with the same bounds/cycle checks as\n *  `readValue`, for callers that must inspect the node's tag. The index stays\n *  marked as visiting, so a parent cycle in class-type nodes throws instead\n *  of recursing forever. */\nfunction readValueNode(index: number, nodes: ValueNode[], visiting: Set<number>): ValueNode {\n  const node = nodes[index]\n  if (node === undefined) throw new Error(`component value node index ${index} is out of bounds`)\n  if (visiting.has(index)) throw new Error('component value arena contains a cycle')\n  visiting.add(index)\n  return node\n}\n\n/** Reads one arena node recursively, rejecting malformed indexes and cycles. */\nfunction readValue(index: number, nodes: ValueNode[], visiting: Set<number>): unknown {\n  const node = nodes[index]\n  if (node === undefined) throw new Error(`component value node index ${index} is out of bounds`)\n  if (visiting.has(index)) throw new Error('component value arena contains a cycle')\n  visiting.add(index)\n  let value: unknown\n  switch (node.tag) {\n    case 'ellipsis':\n      value = { [TYPE_MARKER]: 'Ellipsis' }\n      break\n    case 'not-implemented':\n      value = { [TYPE_MARKER]: 'NotImplemented' }","sourceCodeStart":251,"sourceCodeEnd":287,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/worker/value.ts#L251-L287","documentation":"readValueNode fetches a raw node from the flat value arena by index when reading class-type component values, and throws this Error when the index refers to a slot that does not exist (undefined). This means the arena sent from the sandbox (or hand-built by a caller) contains an index outside 0..nodes.length-1 — the encoded value graph is malformed.","triggerScenarios":"Decoding a worker response whose arena node (class attrs values, bases) references an index >= nodes.length or negative; hand-constructing a value-node arena with stale indexes after removing nodes; a bug in custom encoder code producing dangling references.","commonSituations":"Third-party or hand-rolled code building arenas and reusing old index constants after edits; truncating the node array without fixing references; corrupted/truncated worker messages.","solutions":["Regenerate the arena from the source data rather than editing indexes by hand — dangling references usually mean the array and its references got out of sync.","Check every reference index satisfies `0 <= i < nodes.length` before dispatching/decoding.","If this comes from a worker response, update/rebuild @pydantic/monty so the Rust and TS codecs agree on the arena format.","Log `nodes.length` and the offending index to find the producer of the bad reference."],"exampleFix":"// before\nnodes: [classNode],  // class node references attr value at index 1\n// after\nnodes: [classNode, valueNode]  // include every node the graph references","handlingStrategy":"validation","validationCode":"function validateArena(nodes) {\n  nodes.forEach((n, i) => {\n    for (const ref of referencedIndexes(n)) {\n      if (!(ref in nodes)) throw new Error(`node ${i} references missing index ${ref}`);\n    }\n  });\n}","typeGuard":"const indexInBounds = (i: number, nodes: ValueNode[]): i is number =>\n  Number.isInteger(i) && i >= 0 && i < nodes.length;","tryCatchPattern":"try {\n  const value = decodeValue(nodes);\n} catch (e) {\n  if (e instanceof Error && e.message.includes('is out of bounds')) {\n    console.error(`arena has ${nodes.length} nodes; bad ref: ${e.message}`);\n  } else throw e;\n}","preventionTips":["Build arenas programmatically, never patch indexes by hand","Re-encode from source data instead of editing serialized arenas","Keep the monty binary and @pydantic/monty versions in lockstep","Log nodes.length alongside the failing index when debugging producers"],"tags":["typescript","wasm","arena","decoding"],"backgroundTag":"index-out-of-bounds","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}