nodejs/node · error · TypeError
macro %r was invoked with two values for the special caller
Error message
macro %r was invoked with two values for the special caller argument. This is most likely a bug.
What it means
Raised by Macro._invoke when the macro does not accept catch_kwargs (**kwargs) but the call passed a keyword argument literally named 'caller' while an explicit caller was already bound (self.caller is truthy and the special caller was appended to arguments). The runtime treats 'caller' as reserved for {% call %} blocks; receiving it twice means the template is double-passing the caller, almost always a Jinja2 bug rather than user intent.
Source
Thrown at tools/inspector_protocol/jinja2/runtime.py:564
arguments.append(value)
else:
found_caller = self.explicit_caller
# it's important that the order of these arguments does not change
# if not also changed in the compiler's `function_scoping` method.
# the order is caller, keyword arguments, positional arguments!
if self.caller and not found_caller:
caller = kwargs.pop('caller', None)
if caller is None:
caller = self._environment.undefined('No caller defined',
name='caller')
arguments.append(caller)
if self.catch_kwargs:
arguments.append(kwargs)
elif kwargs:
if 'caller' in kwargs:
raise TypeError('macro %r was invoked with two values for '
'the special caller argument. This is '
'most likely a bug.' % self.name)
raise TypeError('macro %r takes no keyword argument %r' %
(self.name, next(iter(kwargs))))
if self.catch_varargs:
arguments.append(args[self._argument_count:])
elif len(args) > self._argument_count:
raise TypeError('macro %r takes not more than %d argument(s)' %
(self.name, len(self.arguments)))
return self._invoke(arguments, autoescape)
def _invoke(self, arguments, autoescape):
"""This method is being swapped out by the async implementation."""
rv = self._func(*arguments)
if autoescape:
rv = Markup(rv)
return rvView on GitHub (pinned to 1b2de5e052)
Solutions
- Stop forwarding 'caller' as a keyword argument: drop caller=... from the inner macro invocation.
- If the macro legitimately accepts catch_kwargs, filter out 'caller' before forwarding: pass **{k:v for k,v in kwargs.items() if k!='caller'}.
- Restructure so only the {% call %} block supplies caller; inner macros receive their own call blocks.
- Audit macro signatures and call sites for accidental double-supply of caller.
Example fix
// before
{% macro wrap(fn) %}{{ fn(caller=caller) }}{% endmacro %}
// after
{% macro wrap(fn) %}{{ fn() }}{% endmacro %}
{# let the inner {% call %} block define its own caller #} Defensive patterns
Strategy: validation
Validate before calling
# Static scan: a {% call %} block whose body forwards caller=... into a macro
# is the common trigger. Flag forwarding of caller kwarg.
import re, pathlib
for p in pathlib.Path('templates').rglob('*.html'):
src = p.read_text()
if re.search(r'\{%\s*call[^%]*%\}.*caller\s*=', src, re.S):
print(f'{p}: possible double-supply of caller') Type guard
def forwards_caller_kwarg(macro_call_src: str) -> bool:
return 'caller=' in macro_call_src Try / catch
# runtime TypeError is a bug; not recoverable. Fix the template.
Prevention
- Never pass caller= explicitly; let {% call %} supply it.
- When forwarding kwargs through a catch_kwargs macro, drop 'caller' first.
- Add a template test that renders call blocks against sample data.
When it happens
Trigger: A {% call %} block wraps a macro and the macro body itself forwards caller=... as a keyword argument to another invocation of the same macro. Manually invoking a macro with caller=something while it is already being called via {% call %}. Programmatic macro.call(..., caller=...) when the macro already received a caller from a {% call %} wrapper.
Common situations: Macro composition where a wrapper macro forwards kwargs blindly (catch_kwargs=True) into an inner macro that also uses {% call %}. Refactoring call blocks into helper macros and accidentally passing caller through. Async/await macro shims that re-dispatch arguments.
Related errors
- macro %r takes no keyword argument %r
- macro %r takes not more than %d argument(s)
- no items for cycling given
- Tried to call non recursive loop. Maybe you forgot the 'rec
- at least one item has to be provided
AI-assisted analysis of nodejs/node@1b2de5e052 (2026-08-13).
Data as JSON: /api/errors/35327c4138f2cec0.
Report an issue: GitHub.