when With import_tasks and include_tasks
Here’s the sharpest, most practical consequence of the static/dynamic split: when:, attached to import_tasks versus include_tasks, behaves in two genuinely different ways — and the difference can silently change which tasks actually run.
when: On import_tasks: Applied To Every Expanded Task Individually
Since import_tasks splices the imported file’s tasks directly into the play at parse time, a when: attached to it gets copied onto every single task that came from that file — as if you’d manually added the same condition to each one separately. Each copy is evaluated independently, at the moment that specific task actually runs.
when: On include_tasks: Evaluated Once, For The Whole Include
include_tasks, being a genuine runtime node in the task list, evaluates its when: exactly once — deciding whether the entire include happens at all. If true, every task inside runs, as a unit, with no further rechecking of that specific condition per task. If false, none of them run.
Why This Distinction Matters In Practice
Here’s where it actually bites: what happens when a task inside the included file changes the very variable the when: condition depends on?
- name: Task A — sets should_continue to false
ansible.builtin.set_fact:
should_continue: false
- name: Task B — depends on should_continue
ansible.builtin.debug:
msg: "Task B ran"With import_tasks:
- name: Play
hosts: fedora
vars:
should_continue: true
tasks:
- name: Import with condition
ansible.builtin.import_tasks: conditional_tasks.yaml
when: should_continueok: [ubuntu] => (Task A ran, should_continue is now false)
skipping: [ubuntu] (Task B — its own copy of the when: is rechecked, now false)Task A runs, since should_continue was true at that point — and sets it to false. Task B has its own independent copy of when: should_continue, rechecked fresh at the moment it runs — and by then, the condition is false, so Task B is skipped.
With include_tasks, the identical scenario behaves completely differently:
- name: Play
hosts: fedora
vars:
should_continue: true
tasks:
- name: Import with condition
ansible.builtin.include_tasks: conditional_tasks.yaml
when: should_continueok: [ubuntu] => (Task A ran, should_continue is now false)
ok: [ubuntu] => (Task B ran anyway)should_continue was checked once, when Ansible reached the include_tasks line — it was true then, so the decision “run this whole file” was made once and for all. Task B runs regardless of Task A changing the variable, because the include itself was never rechecked — only the individual tasks inside import_tasks’s expanded form get that per-task treatment.
Visibility: --list-tasks And --list-tags
A related consequence: ansible-playbook site.yaml --list-tasks previews every task that would run, without actually running anything. Since import_tasks (and import_playbook, import_role) is fully resolved at parse time, every individual task it brings in shows up in that listing, by name. include_tasks (and include_role) hasn’t been resolved yet at that point — it shows up as just the include statement itself, with none of its actual contents visible until the playbook genuinely runs and reaches that point.
A Combined Example
Given everything above, the choice isn’t just stylistic:
- name: "Import — full visibility, but when: applies per-task"
ansible.builtin.import_tasks: setup.yaml
when: should_run_setup
- name: "Include — single all-or-nothing gate, but hidden from --list-tasks"
ansible.builtin.include_tasks: setup.yaml
when: should_run_setupSame file, same condition, meaningfully different behavior and meaningfully different visibility, depending purely on which keyword you chose.
Best Practices
- Remember
when:onimport_tasksrechecks per expanded task — a task inside the imported content can genuinely change the outcome for later tasks in that same file. - Remember
when:oninclude_tasksis a single all-or-nothing gate, decided once, for the whole file. - Use
--list-tasksto preview a playbook before running it, but remember dynamic includes won’t reveal their contents there — reach forimport_tasksspecifically when full preview visibility matters to you. - Choose based on the actual behavior you want, not just which keyword happens to be shorter to type — the two aren’t interchangeable the moment a
when:or a variable-dependent filename is involved.