nodejs/node · error · TypeError
Tried to call non recursive loop. Maybe you forgot the 'rec
Error message
Tried to call non recursive loop. Maybe you forgot the 'recursive' modifier.
What it means
Raised by LoopContext.loop(iterable) when self._recurse is None, i.e. the LoopContext was not created in a recursive loop. The loop() method is the callable injected into templates to recurse; calling it makes sense only inside {% for x in seq recursive %...{% endfor %}. Calling loop(...) in a non-recursive for-loop is almost always a template bug.
Source
Thrown at tools/inspector_protocol/jinja2/runtime.py:404
@property
def previtem(self):
if self._before is _first_iteration:
return self._undefined('there is no previous item')
return self._before
@property
def nextitem(self):
if self._after is _last_iteration:
return self._undefined('there is no next item')
return self._after
def __len__(self):
return self.length
@internalcode
def loop(self, iterable):
if self._recurse is None:
raise TypeError('Tried to call non recursive loop. Maybe you '
"forgot the 'recursive' modifier.")
return self._recurse(iterable, self._recurse, self.depth0 + 1)
# a nifty trick to enhance the error message if someone tried to call
# the the loop without or with too many arguments.
__call__ = loop
del loop
def __repr__(self):
return '<%s %r/%r>' % (
self.__class__.__name__,
self.index,
self.length
)
class LoopContext(LoopContextBase):
View on GitHub (pinned to 1b2de5e052)
Solutions
- Add the 'recursive' modifier: {% for node in tree recursive %}...{{ loop(node.children) }}...{% endfor %}.
- If recursion is not intended, replace loop(...) with a macro call or a separate {% include %}.
- Verify the variable named 'loop' has not been shadowed by a local variable inside the loop body.
- Move recursive rendering into a macro that takes the sequence, and call the macro instead of loop().
Example fix
// before
{% for node in tree %}
{{ node.name }}
{{ loop(node.children) }}
{% endfor %}
// after
{% for node in tree recursive %}
{{ node.name }}
{{ loop(node.children) }}
{% endfor %} Defensive patterns
Strategy: validation
Validate before calling
# Static validation: scan templates for loop(...) calls and ensure the
# enclosing {% for %} has the 'recursive' modifier.
import re, pathlib
for p in pathlib.Path('templates').rglob('*.html'):
src = p.read_text()
for m in re.finditer(r'\{%\s*for\s+([^{]+?)\s+in\s+([^{]+?)( recursive)?\s*%\}(.*?)\{%\s*endfor\s*%\}', src, re.S):
if 'loop(' in m.group(4) and not m.group(3):
print(f'{p}: loop() used without recursive modifier') Type guard
def loop_is_recursive(for_tag_text: str) -> bool:
return 'recursive' in for_tag_text Try / catch
# not recoverable at runtime; fix the template # a syntax/lint check at build time is the right layer
Prevention
- Run a template linter that flags loop() calls outside recursive {% for %} blocks.
- Keep recursive tree rendering in dedicated macros to avoid copy-paste into flat loops.
- Avoid shadowing the 'loop' variable inside loops.
When it happens
Trigger: A template uses {{ loop(item.children) }} inside a normal {% for %} block that lacks the 'recursive' modifier. Copy-pasting a recursive macro body into a non-recursive loop. Renaming a variable that shadowed 'loop' so the inner call now hits the outer (non-recursive) LoopContext.
Common situations: Refactoring recursive tree rendering into a flat loop and forgetting to remove loop() calls. Helpers/macros originally written for recursive use and reused in flat contexts. Confusion between loop.index and loop() (calling instead of indexing).
Related errors
- no items for cycling given
- Loop length for some iterators cannot be lazily calculated i
- at least one item has to be provided
- Template module attribute is unavailable in async mode
- extended multiple times
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/753d589e06ebb6ae.
Report an issue: GitHub.