{"record":{"id":"4fc822c294300a01","repo":"BerriAI/litellm","slug":"json-merge-patch-nesting-exceeds-the-maximum-depth","errorCode":null,"errorMessage":"JSON merge patch nesting exceeds the maximum depth of {_MAX_MERGE_DEPTH}","messagePattern":"JSON merge patch nesting exceeds the maximum depth of (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/proxy/common_utils/json_merge_patch.py","lineNumber":26,"sourceCode":"# realistic team-metadata shape but well below Python's stack limit, so a\n# pathologically deep patch is rejected instead of overflowing the stack.\n_MAX_MERGE_DEPTH: Final = 64\n\n\ndef apply_json_merge_patch(target: JsonValue, patch: JsonValue, _depth: int = 0) -> JsonValue:\n    \"\"\"Apply an RFC 7386 JSON Merge Patch to ``target`` and return the result.\n\n    - a key absent from ``patch`` keeps its value in ``target``\n    - a key mapped to ``null`` in ``patch`` is removed from the result\n    - any other value overwrites, recursing into nested objects\n\n    ``target`` is never mutated; a new value is returned. Raises ``ValueError``\n    if ``patch`` nests deeper than ``_MAX_MERGE_DEPTH``.\n    \"\"\"\n    if not isinstance(patch, dict):\n        return patch\n    if _depth >= _MAX_MERGE_DEPTH:\n        raise ValueError(f\"JSON merge patch nesting exceeds the maximum depth of {_MAX_MERGE_DEPTH}\")\n\n    base: Final = target if isinstance(target, dict) else {}\n    preserved: Final = {key: value for key, value in base.items() if key not in patch}\n    applied: Final = {\n        key: apply_json_merge_patch(base.get(key), value, _depth + 1)\n        for key, value in patch.items()\n        if value is not None\n    }\n    return {**preserved, **applied}\n","sourceCodeStart":8,"sourceCodeEnd":36,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/common_utils/json_merge_patch.py#L8-L36","documentation":"apply_json_merge_patch implements RFC 7386 merge for nested objects and enforces a hard recursion ceiling of _MAX_MERGE_DEPTH = 64. When a patch nests dicts more than 64 levels deep, it raises ValueError at the depth check. The proxy uses this for PATCH /team/{team_id} metadata merging, so overly nested team metadata hits this error.","triggerScenarios":"PATCH /team/{team_id} with a metadata object nested deeper than 64 levels (e.g. machine-generated nested configs or recursive structures serialized into metadata). Depth is counted per nested dict level in the patch.","commonSituations":"Storing a deeply nested JSON document (IAM policy trees, recursive UI state) inside team metadata. Two systems that embed each other's payloads, growing depth over time.","solutions":["Flatten the metadata before sending: store deep documents as a JSON string field or by dotted keys.","Compute the nesting depth client-side and cap it below 64.","If you control both sides, move large structured data out of team metadata into dedicated storage."],"exampleFix":"# before — nested 100 levels deep\nmetadata = cur\nfor _ in range(100):\n    cur[\"child\"] = {}\n    cur = cur[\"child\"]\npatch_team(team_id, metadata)\n\n# after — store as string\npatch_team(team_id, {\"policy_blob\": json.dumps(metadata)})","handlingStrategy":"validation","validationCode":"def json_depth(value) -> int:\n    if isinstance(value, dict):\n        return 1 + max((json_depth(v) for v in value.values()), default=0)\n    if isinstance(value, list):\n        return 1 + max((json_depth(v) for v in value), default=0)\n    return 0\n\nMAX_DEPTH = 64\npatch = {\"metadata\": deep_doc}\nassert json_depth(patch) < MAX_DEPTH, f\"depth {json_depth(patch)} >= {MAX_DEPTH}: flatten first\"","typeGuard":"def is_merge_patch_depth_ok(patch: dict, max_depth: int = 64) -> bool:\n    def d(v, cur):\n        if cur >= max_depth:\n            return False\n        if isinstance(v, dict):\n            return all(d(x, cur + 1) for x in v.values())\n        if isinstance(v, list):\n            return all(d(x, cur + 1) for x in v)\n        return True\n    return d(patch, 0)","tryCatchPattern":"try:\n    resp = client.patch(f\"/team/{team_id}\", json={\"metadata\": deep_doc})\n    resp.raise_for_status()\nexcept HTTPError as e:\n    if e.response.status_code == 500 and \"maximum depth\" in e.response.text:\n        flat = {\"doc_blob\": json.dumps(deep_doc)}\n        resp = client.patch(f\"/team/{team_id}\", json={\"metadata\": flat})\n    raise","preventionTips":["Never store recursive or machine-generated trees inside team metadata; use a blob string.","Compute nesting depth before every PATCH that touches metadata.","Watch for depth growth when two systems embed each other's payloads in metadata."],"tags":["litellm","json","merge-patch","team-metadata","depth-limit","rfc-7386"],"backgroundTag":"json-nesting-depth-exceeded","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}