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

  1. Flatten redundant nesting: {'and':[{'and':[...]}]} collapses to {'and':[...]}.
  2. Raise the cap only if justified: set `complex_filter_max_depth = 8` on the view.
  3. 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

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.