{"record":{"id":"197944b7700fae52","repo":"pydantic/monty","slug":"max-input-depth-exceeded","errorCode":null,"errorMessage":"Max input depth exceeded","messagePattern":"Max input depth exceeded","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/classInstance.ts","lineNumber":506,"sourceCode":" * Outbound walk over a host value heading into the sandbox: replaces\n * [`ClassInstance`] wrappers with their wire marker (registering them in\n * `store`, eager attrs prepared recursively), recurses into arrays / Maps /\n * Sets / plain objects, and rejects any other non-plain object with a\n * `TypeError` telling the caller to wrap it.\n */\nexport function prepare(value: unknown, store: InstanceStore): unknown {\n  return prepareInner(value, store, 0)\n}\n\n/** Recursion guard for the outbound walk itself, so a too-deep value fails\n *  with a catchable error instead of a `RangeError` mid-recursion. Not the\n *  authoritative wire budget: the native layer re-checks every value with\n *  exact per-shape accounting (`exceeds_max_value_depth`) before encoding. */\nconst MAX_INPUT_DEPTH = 48\n\nfunction prepareInner(value: unknown, store: InstanceStore, depth: number): unknown {\n  if (depth > MAX_INPUT_DEPTH) {\n    throw new TypeError('Max input depth exceeded')\n  }\n  if (typeof value !== 'object' || value === null) {\n    return value\n  }\n  const walk = (item: unknown) => prepareInner(item, store, depth + 1)\n  // `ClassType` and `ClassInstance` are sibling `BaseWrapper`s; the class check simply comes first.\n  if (value instanceof ClassType) {\n    return classTypeToMarker(value, store, depth)\n  }\n  if (value instanceof ClassInstance) {\n    return wrapperToMarker(value, store, depth)\n  }\n  if (value instanceof MontyClassProxy) {\n    return value.toMarker(store, depth)\n  }\n  if (Array.isArray(value)) {\n    return walkArray(value, walk)\n  }","sourceCodeStart":488,"sourceCodeEnd":524,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/classInstance.ts#L488-L524","documentation":"Values passed into the Monty sandbox (inputs, external function results, class instance attrs) are recursively prepared before crossing the wire, and nesting deeper than MAX_INPUT_DEPTH (48) is rejected with this TypeError. The depth cap keeps the JS-side walk from stack overflow and mirrors the authoritative per-shape depth budget the native layer re-checks during encoding.","triggerScenarios":"Calling session.feedRun with an `inputs` object, returning a value from an externalLookup/external function callback, or wrapping an object in ClassInstance(...) whose attribute tree nests more than 48 levels (e.g. self-referential-looking deep linked lists, generated nested JSON).","commonSituations":"Passing deeply recursive data structures (ASTs, linked lists built from nested objects), accidental self-referencing objects serialized by a custom wrapper, or frameworks that produce very deeply nested config trees.","solutions":["Flatten or restructure the value so nesting stays under 48 levels before passing it to the sandbox","Break deeply linked structures into separate shallow pieces (e.g. pass an array of nodes with references by id) and link them on the Python side","If the depth is legitimately needed, do the computation on the host and hand the sandbox only the final shallow result"],"exampleFix":"// before\nsession.feedRun('run(data)', { inputs: { data: deeplyNested } });\n// after\nconst flattened = flattenToIdRefs(deeplyNested); // array of shallow nodes\nsession.feedRun('run(data)', { inputs: { data: flattened } });","handlingStrategy":"validation","validationCode":"function depthOf(v, d = 0) {\n  if (d > 48) return d;\n  if (typeof v !== 'object' || v === null) return d;\n  return Math.max(0, ...Object.values(v).map((x) => depthOf(x, d + 1)));\n}\nif (depthOf(inputs) > 48) throw new Error('inputs exceed 48-level depth');","typeGuard":null,"tryCatchPattern":"try {\n  await session.feedRun(code, { inputs });\n} catch (e) {\n  if (e instanceof TypeError && e.message === 'Max input depth exceeded') {\n    inputs = flattenToIdRefs(inputs);\n    await session.feedRun(code, { inputs });\n  } else throw e;\n}","preventionTips":["Keep values passed to the sandbox shallow; pass id-referenced collections instead of nested trees","Write a depth-checking helper in CI tests for every payload shape you feed to Monty","Avoid passing recursive or self-referential object graphs directly"],"tags":["typescript","input-depth","recursion-limit","validation"],"backgroundTag":"value-out-of-range","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"}