{"record":{"id":"875bbd7fe86eacb9","repo":"ansible/ansible","slug":"the-current-task-is-not-a-loop","errorCode":null,"errorMessage":"The current task is not a loop.","messagePattern":"The current task is not a loop\\.","errorType":"exception","errorClass":"NotALoopError","httpStatus":null,"severity":"error","filePath":"lib/ansible/_internal/_task.py","lineNumber":360,"sourceCode":"\n        if item_label_template := self.task.loop_control.label:\n            try:\n                item_label = self.task_templar.template(item_label_template)\n            except AnsibleTemplateError as ex:\n                display.error_as_warning('Failed to template loop_control label.', ex, obj=item_label_template)\n\n        if item_label is ...:\n            if self._loop_var is None:\n                # _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)\n                item_label = ''\n            else:\n                item_label = self.task_templar.resolve_variable_expression(self._loop_var)\n\n        utr.loop_item_label = item_label\n\n    def build_loop_result(self, preview: bool = False) -> UnifiedTaskResult:\n        if not self.is_loop:\n            raise NotALoopError()\n\n        if not preview and (self._item_index is None or len(self._raw_loop_results) not in (self._item_index, self._item_index + 1)):\n            # RPFIX-9: FUTURE: can we ditch preview while retaining this safety check?\n            # Loop results can be queried before or after loop results are recorded, so we need to accept a range of results.\n            raise RuntimeError(f\"Mismatch between item index {self._item_index} and loop result count {len(self._raw_loop_results)}.\")\n\n        # create the overall result item\n        utr = UnifiedTaskResult.from_action_result_dict()\n        utr.loop_results = self._raw_loop_results\n\n        # RPFIX-5: IMPL: all the fields set in this loop could be converted to properties\n        for item in self._raw_loop_results:\n            if item.no_log:\n                utr.no_log = True  # ensure no_log processing recognizes at least one item needs to be censored\n\n            utr._extend_warnings(item.warnings)\n            utr._extend_deprecations(item.deprecations)\n","sourceCodeStart":342,"sourceCodeEnd":378,"githubUrl":"https://github.com/ansible/ansible/blob/9cf16a4aca7898481c257f1e17ad28d0b67b1f85/lib/ansible/_internal/_task.py#L342-L378","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"# before\nutr = task.build_loop_result()  # NotALoopError for normal tasks\n\n# after\nif task.is_loop:\n    utr = task.build_loop_result()\nelse:\n    utr = task.latest_result","handlingStrategy":"type-guard","validationCode":"if task.is_loop:\n    utr = task_ext.build_loop_result()","typeGuard":"def is_loop_task(task) -> bool:\n    return bool(task.loop)","tryCatchPattern":"try:\n    utr = task_ext.build_loop_result()\nexcept NotALoopError:\n    utr = task_ext.latest_result  # single-result path","preventionTips":["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"],"tags":["ansible","task","loop","plugin-api","misuse"],"backgroundTag":null,"analyzedSha":"9cf16a4aca7898481c257f1e17ad28d0b67b1f85","analyzedAt":"2026-08-15T00:15:47.100Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}