BerriAI/litellm · error · HTTPException
Content blocked: {context_label} arguments exceed the maximu
Error message
Content blocked: {context_label} arguments exceed the maximum nesting depth What it means
The content filter masks MCP tool-call arguments via recursive descent in _filter_argument_value, bounded by DEFAULT_MAX_RECURSE_DEPTH (default 100, overridable with the DEFAULT_MAX_RECURSE_DEPTH env var). Arguments nested deeper than the limit are rejected with HTTP 400 rather than scanned, so recursion cost stays bounded.
Source
Thrown at litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py:1777
if self._filter_single_text(text, detections=detections) != text:
raise HTTPException(
status_code=400,
detail={
"error": (
f"Content blocked: {context_label} argument matched a masking rule on a non-rewritable field"
)
},
)
def _filter_argument_value(
self,
value: object,
detections: list[ContentFilterDetection],
context_label: str,
depth: int = 0,
) -> object:
if depth > DEFAULT_MAX_RECURSE_DEPTH:
raise HTTPException(
status_code=400,
detail={"error": f"Content blocked: {context_label} arguments exceed the maximum nesting depth"},
)
if isinstance(value, str):
return self._filter_single_text(value, detections=detections)
if isinstance(value, (int, float)) and not isinstance(value, bool):
self._assert_argument_label_clean(str(value), detections, context_label)
return value
if isinstance(value, dict):
for key in value:
if isinstance(key, str):
self._assert_argument_label_clean(key, detections, context_label)
return {
key: self._filter_argument_value(item, detections, context_label, depth + 1)
for key, item in value.items()
}
if isinstance(value, list):
return [self._filter_argument_value(item, detections, context_label, depth + 1) for item in value]View on GitHub (pinned to 77b7c6c40c)
Solutions
- Flatten or simplify the tool arguments payload so nesting stays well under the limit (default 100)
- Fix the client bug generating runaway nesting - look for accidental self-reference or wrappers added in a loop
- If genuinely deeper payloads are required, raise the DEFAULT_MAX_RECURSE_DEPTH environment variable on the proxy process and restart it
Example fix
# before - loop adds a wrapper dict per attempt: depth grows until 400
args = payload
for attempt in retries:
args = {"wrapper": args}
# after - send the payload once, keep it flat
args = payload Defensive patterns
Strategy: validation
Validate before calling
MAX_DEPTH = 100
def max_nesting_depth(value, depth=0) -> int:
if isinstance(value, dict):
return max((max_nesting_depth(v, depth + 1) for v in value.values()), default=depth)
if isinstance(value, (list, tuple)):
return max((max_nesting_depth(v, depth + 1) for v in value), default=depth)
return depth
if max_nesting_depth(tool_arguments) > MAX_DEPTH:
raise ValueError("flatten arguments before sending: nesting exceeds guardrail limit") Prevention
- Serialize tool arguments once, never re-wrap payloads in loops or retries
- Break reference cycles (json.dumps with default=...) before building arguments
- Set an explicit depth budget in your client and fail fast locally instead of round-tripping to the proxy
When it happens
Trigger: An MCP tool-call arguments payload with more than 100 levels of nested dicts/lists - typically a serialization bug (self-referencing structure), a client loop that adds wrappers each iteration, or a deliberately pathological payload.
Common situations: Cyclic object graphs accidentally serialized into arguments; wrapper-in-a-loop bugs that add one nesting level per retry; machine-generated JSON from recursive data structures; payloads crafted to probe the gateway's limits.
Related errors
- Content blocked: {context_label} argument matched a masking
- guardrail_violation
- Content blocked: competitor comparison or ranking intent det
- MCP Security: request references unregistered MCP server(s):
- invalid_request
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/16b4e561c4afa1e7.
Report an issue: GitHub.