makeplane/plane · warning · DRFValidationError
max_depth_exceeded
max_depth_exceeded
Error message
Filter nesting is too deep (max {max_depth}); found depth {current_depth} What it means
Raised by _validate_structure when current_depth exceeds max_depth (default 5, overridable per-view via complex_filter_max_depth). The recursion counts every nested or/and/not operator level, so deeply composed boolean trees are capped to bound CPU and prevent stack blow-ups during validation.
Source
Thrown at apps/api/plane/utils/filters/filter_backend.py:327
return value_int
except Exception:
return self.default_max_depth
def _validate_structure(self, node, max_depth, current_depth):
"""Validate JSON structure and enforce nesting depth.
Rules:
- Each object may contain only one logical operator:
or/and/not (case-insensitive)
- Logical operator objects cannot contain field keys alongside the
operator
- or/and values must be non-empty lists of dicts
- not value must be a dict
- Leaf objects must only contain field keys and acceptable values
- Depth must not exceed max_depth
"""
if current_depth > max_depth:
raise DRFValidationError(
{
"message": (f"Filter nesting is too deep (max {max_depth}); found depth {current_depth}"),
"code": "max_depth_exceeded",
}
)
if not isinstance(node, dict):
raise DRFValidationError(
{
"message": "Each filter node must be a JSON object",
"code": "invalid_filter_node",
}
)
if not node:
raise DRFValidationError(
{
"message": "Filter objects must not be empty",View on GitHub (pinned to 1c8a60f858)
Solutions
- Flatten redundant nesting: {'and':[{'and':[...]}]} collapses to {'and':[...]}.
- Raise the cap only if justified: set `complex_filter_max_depth = 8` on the view.
- Audit the client's filter-tree builder to stop wrapping single-child groups.
Example fix
// before
?filters={"and":[{"and":[{"and":[{"and":[{"and":[{"state":"open"}]}]}]}]}]}
// after
?filters={"state":"open"} Defensive patterns
Strategy: validation
Validate before calling
MAX_DEPTH = getattr(view, 'complex_filter_max_depth', 5)
def depth(node, d=1):
if not isinstance(node, dict):
return d
op_keys = [k for k in node if isinstance(k, str) and k.lower() in ('or', 'and', 'not')]
if not op_keys:
return d
children = node[op_keys[0]]
kids = children if isinstance(children, list) else [children]
return max((depth(c, d + 1) for c in kids), default=d)
if depth(filter_data) > MAX_DEPTH:
raise ValueError(f'filter tree too deep (max {MAX_DEPTH})') Prevention
- Flatten single-child and/or groups in the client before sending.
- If the cap is too low for legitimate use, raise complex_filter_max_depth on the view with a justification.
When it happens
Trigger: A filter with six or more levels of nested {'and':[{'or':[{'and':[...]}]}]}; deeply recursive 'not' chains like {'not':{'not':{'not':{'not':{'not':{'not':{...}}}}}}}; a programmatic client that wraps every condition in an extra and-group.
Common situations: Auto-generated filter trees (e.g. translating a UI rule builder) that add a redundant wrapper per clause; adversarial input; migrating from a less strict filter backend that allowed arbitrary depth.
Related errors
AI-assisted analysis of makeplane/plane@1c8a60f858 (2026-08-12).
Data as JSON: /api/errors/76d5644161aaac4f.
Report an issue: GitHub.