ansible/ansible · error · NotALoopError
The current task is not a loop.
Error message
The current task is not a loop.
What it means
NotALoopError raised by build_loop_result when task.is_loop is False. The method assembles an aggregate UnifiedTaskResult from _raw_loop_results, which only exist for loop tasks; calling it on a loop-free task is a programming error in the caller, so it fails fast instead of fabricating an empty loop result.
Source
Thrown at lib/ansible/_internal/_task.py:360
if item_label_template := self.task.loop_control.label:
try:
item_label = self.task_templar.template(item_label_template)
except AnsibleTemplateError as ex:
display.error_as_warning('Failed to template loop_control label.', ex, obj=item_label_template)
if item_label is ...:
if self._loop_var is None:
# _loop_var will be None if start_loop was not called due to a field attribute error (even if the error is for another field)
item_label = ''
else:
item_label = self.task_templar.resolve_variable_expression(self._loop_var)
utr.loop_item_label = item_label
def build_loop_result(self, preview: bool = False) -> UnifiedTaskResult:
if not self.is_loop:
raise NotALoopError()
if not preview and (self._item_index is None or len(self._raw_loop_results) not in (self._item_index, self._item_index + 1)):
# RPFIX-9: FUTURE: can we ditch preview while retaining this safety check?
# Loop results can be queried before or after loop results are recorded, so we need to accept a range of results.
raise RuntimeError(f"Mismatch between item index {self._item_index} and loop result count {len(self._raw_loop_results)}.")
# create the overall result item
utr = UnifiedTaskResult.from_action_result_dict()
utr.loop_results = self._raw_loop_results
# RPFIX-5: IMPL: all the fields set in this loop could be converted to properties
for item in self._raw_loop_results:
if item.no_log:
utr.no_log = True # ensure no_log processing recognizes at least one item needs to be censored
utr._extend_warnings(item.warnings)
utr._extend_deprecations(item.deprecations)
View on GitHub (pinned to 9cf16a4aca)
Solutions
- Branch on task.is_loop (or the loop keyword) before calling build_loop_result.
- For non-loop tasks use the ordinary single-result path (latest_result / recorded result dict).
- If the task was supposed to loop, fix the templating error on the loop field that prevented loop setup.
- Preview mode also requires is_loop — do not use preview to bypass the check.
Example fix
# before
utr = task.build_loop_result() # NotALoopError for normal tasks
# after
if task.is_loop:
utr = task.build_loop_result()
else:
utr = task.latest_result Defensive patterns
Strategy: type-guard
Validate before calling
if task.is_loop:
utr = task_ext.build_loop_result() Type guard
def is_loop_task(task) -> bool:
return bool(task.loop) Try / catch
try:
utr = task_ext.build_loop_result()
except NotALoopError:
utr = task_ext.latest_result # single-result path Prevention
- Branch on task.loop before loop-only APIs
- Fix loop-field templating errors that disable loop mode
- Do not copy loop handling into non-loop code paths
When it happens
Trigger: Plugin/strategy code calling build_loop_result() unconditionally for every task; handling a task whose `loop` keyword failed to template so is_loop ended up False even though loop semantics were expected; copying loop-specific handling code to a non-loop path.
Common situations: Custom callbacks or result processors branching incorrectly; tasks where the loop field had a templating error (the error path noted in _record_result's comment means start_loop never ran).
Related errors
- No task result has been recorded.
- Task args must resolve to a {native_type_name(dict)!r} not {
- Register projection {var_name!r} failed.
- The `loop` value must resolve to a 'list', not {native_type_
- Failed to duplicate token
AI-assisted analysis of ansible/ansible@9cf16a4aca (2026-08-15).
Data as JSON: /api/errors/875bbd7fe86eacb9.
Report an issue: GitHub.