mongodb/laravel-mongodb · error · InvalidArgumentException
Aggregation nesting exceeds the maximum of
Error message
Aggregation nesting exceeds the maximum of %d levels.
What it means
The aggregate pipeline nesting (recursion through $facet, $lookup and $unionWith sub-pipelines) went deeper than 100 levels, the library's guard mirroring BSON's 100-level nesting limit. The check runs recursively in ensureNoNestedWriteInAggregation, incrementing `$level` for each nested sub-pipeline.
Solutions
- Flatten the pipeline: reduce nesting of $facet/$lookup/$unionWith sub-pipelines to under 100 levels.
- Restructure recursive lookups as iterative queries or use $graphLookup for recursive traversal instead of nested $lookup stages.
- Validate pipeline depth client-side before invoking the tool.
Example fix
// before: 101 nested stages, each [['$lookup' => ['from' => 'x', 'pipeline' => <next>]]]
// after
{"aggregate": "users", "pipeline": [{"$lookup": {"from": "orders", "localField": "_id", "foreignField": "user_id", "as": "orders"}}, {"$limit": 10}]} Defensive patterns
Strategy: validation
Validate before calling
function pipelineDepth(array $pipeline): int {
$max = 1;
foreach ($pipeline as $stage) {
$name = str_replace('$', '', (string) array_key_first($stage));
$body = $stage[array_key_first($stage)];
if ($name === 'facet' && is_array($body)) {
foreach ($body as $sub) { $max = max($max, 1 + pipelineDepth($sub)); }
} elseif (in_array($name, ['lookup', 'unionWith'], true) && is_array($body) && isset($body['pipeline'])) {
$max = max($max, 1 + pipelineDepth($body['pipeline']));
}
}
return $max;
}
if (pipelineDepth($pipeline) > 100) { throw new \InvalidArgumentException('pipeline too deeply nested'); } Try / catch
try {
$result = $tool->handle($request);
} catch (\InvalidArgumentException $e) {
if (str_contains($e->getMessage(), 'nesting exceeds')) {
// flatten or restructure the pipeline, then retry
}
} Prevention
- Prefer $graphLookup over recursive $lookup chains.
- Cap generated $facet/$lookup/$unionWith nesting well below 100 levels.
- Review machine-generated pipelines for pathological recursion before sending.
When it happens
Trigger: An aggregate command whose sub-pipelines inside $facet/$lookup/$unionWith recurse or stack more than 100 levels deep, e.g. 101 nested $lookup.pipeline levels or a $facet whose facet pipelines each nest further past the limit.
Common situations: Machine- or LLM-generated pipelines with pathological nesting; programmatically built recursive facet/lookup trees; deeply generated JSON from another tool pasted in as the command.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- A pipeline exceeds the maximum of
- Only read aggregation stages are allowed. Found
- Please pass a valid MongoDB command
- Only read commands are allowed
- Database is not properly configured.
AI-assisted analysis of mongodb/laravel-mongodb@0634653039 (2026-09-15).
Data as JSON: /api/errors/d6fb3f6c4740c886.
Report an issue: GitHub.
Appendix: source
Thrown at src/Tools/DatabaseQuery.php:133
}
if ($operation === 'aggregate') {
// Check nested write ops recursively with conservative allow list
$this->ensureNoNestedWriteInAggregation($command['pipeline'] ?? []);
}
return $connection->getDatabase()->command($command)->toArray();
}
/**
* @param array<int, array<string, mixed>> $pipeline
*
* @throws InvalidArgumentException
*/
private function ensureNoNestedWriteInAggregation(array $pipeline, int $level = 1): void
{
if ($level > self::MAX_NESTING_LEVEL) {
throw new InvalidArgumentException(sprintf('Aggregation nesting exceeds the maximum of %d levels.', self::MAX_NESTING_LEVEL));
}
if (count($pipeline) > self::MAX_STAGES_PER_PIPELINE) {
throw new InvalidArgumentException(sprintf('A pipeline exceeds the maximum of %d stages.', self::MAX_STAGES_PER_PIPELINE));
}
// These aggregation stages may contain nested aggregation pipelines
// and must be checked for write ops recursively.
$supportsNestedWrites = ['facet', 'lookup', 'unionWith'];
// Every known read-only aggregation stage. Kept as an allow list (rather than
// denying $merge/$out) so that write stages added in the future are rejected by
// default. See https://www.mongodb.com/docs/manual/reference/mql/aggregation-stages
$allowList = array_merge($supportsNestedWrites, [
'addFields',
'bucket',
'bucketAuto',
'changeStream',View on GitHub (pinned to 0634653039)