BerriAI/litellm · error · ValueError
JSON merge patch nesting exceeds the maximum depth of {_MAX_
Error message
JSON merge patch nesting exceeds the maximum depth of {_MAX_MERGE_DEPTH} What it means
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.
Source
Thrown at litellm/proxy/common_utils/json_merge_patch.py:26
# realistic team-metadata shape but well below Python's stack limit, so a
# pathologically deep patch is rejected instead of overflowing the stack.
_MAX_MERGE_DEPTH: Final = 64
def apply_json_merge_patch(target: JsonValue, patch: JsonValue, _depth: int = 0) -> JsonValue:
"""Apply an RFC 7386 JSON Merge Patch to ``target`` and return the result.
- a key absent from ``patch`` keeps its value in ``target``
- a key mapped to ``null`` in ``patch`` is removed from the result
- any other value overwrites, recursing into nested objects
``target`` is never mutated; a new value is returned. Raises ``ValueError``
if ``patch`` nests deeper than ``_MAX_MERGE_DEPTH``.
"""
if not isinstance(patch, dict):
return patch
if _depth >= _MAX_MERGE_DEPTH:
raise ValueError(f"JSON merge patch nesting exceeds the maximum depth of {_MAX_MERGE_DEPTH}")
base: Final = target if isinstance(target, dict) else {}
preserved: Final = {key: value for key, value in base.items() if key not in patch}
applied: Final = {
key: apply_json_merge_patch(base.get(key), value, _depth + 1)
for key, value in patch.items()
if value is not None
}
return {**preserved, **applied}
View on GitHub (pinned to 77b7c6c40c)
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.
Example fix
# before — nested 100 levels deep
metadata = cur
for _ in range(100):
cur["child"] = {}
cur = cur["child"]
patch_team(team_id, metadata)
# after — store as string
patch_team(team_id, {"policy_blob": json.dumps(metadata)}) Defensive patterns
Strategy: validation
Validate before calling
def json_depth(value) -> int:
if isinstance(value, dict):
return 1 + max((json_depth(v) for v in value.values()), default=0)
if isinstance(value, list):
return 1 + max((json_depth(v) for v in value), default=0)
return 0
MAX_DEPTH = 64
patch = {"metadata": deep_doc}
assert json_depth(patch) < MAX_DEPTH, f"depth {json_depth(patch)} >= {MAX_DEPTH}: flatten first" Type guard
def is_merge_patch_depth_ok(patch: dict, max_depth: int = 64) -> bool:
def d(v, cur):
if cur >= max_depth:
return False
if isinstance(v, dict):
return all(d(x, cur + 1) for x in v.values())
if isinstance(v, list):
return all(d(x, cur + 1) for x in v)
return True
return d(patch, 0) Try / catch
try:
resp = client.patch(f"/team/{team_id}", json={"metadata": deep_doc})
resp.raise_for_status()
except HTTPError as e:
if e.response.status_code == 500 and "maximum depth" in e.response.text:
flat = {"doc_blob": json.dumps(deep_doc)}
resp = client.patch(f"/team/{team_id}", json={"metadata": flat})
raise Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Error parsing BFL response: {e}
- Error parsing initial response: {e}
- Cisco AI Defense {surface} API returned a non-JSON response
- You must be a LiteLLM Enterprise user to use this feature. I
- custom_ui_sso_sign_in_handler is not configured. Please set
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/4fc822c294300a01.
Report an issue: GitHub.