Ansible Blocks With Conditionals
Refreshed June 2026: module names updated to FQCN, the docs link fixed, and the examples re-verified against current ansible-core. The behavior described here has not changed since 2019, which is exactly why it is still worth knowing.
Background
I ran into a surprise using Ansible blocks: tasks inside a block were being skipped when I did not expect it. I had no idea this was expected behavior, so I am writing it down in case you hit the same thing.
The playbook that triggered it imported another set of tasks in a loop a fixed number of times. Inside the block I had a when conditional that decided whether the later tasks should run. What I learned is that a block-level condition is not evaluated once when you enter the block. It is re-evaluated for every task in the block. The Ansible documentation says this directly: a when statement on a block is inherited by the tasks within it, and the condition is evaluated before each task runs.
That one detail changes how you order tasks inside a conditional block.
Example 1
This is close to how I had it originally. The block runs when debugging is true, and one of the tasks flips debugging to false partway through.
Playbook:
---
- hosts: localhost
gather_facts: false
connection: local
vars:
debugging: true
tasks:
- block:
- name: First Task
ansible.builtin.debug:
msg: First Task
- name: Set debugging To False
ansible.builtin.set_fact:
debugging: false
- name: Second Task
ansible.builtin.debug:
msg: Second Task
when: debugging | bool
I expected both First Task and Second Task to run, since debugging was true when the block started. That is not what happens.
Execution:
PLAY [localhost] ********************************************************************
TASK [First Task] ******************************************************************
ok: [localhost] => {
"msg": "First Task"
}
TASK [Set debugging To False] ******************************************************
ok: [localhost]
TASK [Second Task] *****************************************************************
skipping: [localhost]
PLAY RECAP *************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
Second Task is skipped. The block-level when is checked again before Second Task, and by then Set debugging To False has already changed the variable. The condition is now false, so the task does not run.
Example 2
Same tasks, reordered so the set_fact runs last. Now everything inside the block executes before the variable changes.
Playbook:
---
- hosts: localhost
gather_facts: false
connection: local
vars:
debugging: true
tasks:
- block:
- name: First Task
ansible.builtin.debug:
msg: First Task
- name: Second Task
ansible.builtin.debug:
msg: Second Task
- name: Set debugging To False
ansible.builtin.set_fact:
debugging: false
when: debugging | bool
Execution:
PLAY [localhost] ********************************************************************
TASK [First Task] ******************************************************************
ok: [localhost] => {
"msg": "First Task"
}
TASK [Second Task] *****************************************************************
ok: [localhost] => {
"msg": "Second Task"
}
TASK [Set debugging To False] ******************************************************
ok: [localhost]
PLAY RECAP *************************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Takeaways
- A block-level
whenis inherited by every task in the block and evaluated before each one. It is not a single check at the top of the block. - If a task inside the block changes the variable the condition depends on, set it as the last task in the block. Otherwise the tasks after it get skipped.
- This is documented and current behavior in ansible-core, not a bug. Worth keeping in mind any time a block both reads and writes the same variable.
Updated Example
Someone reached out asking about using blocks and conditionals inside roles. Here is the playbook we worked through (original gist). The same rule applies: the block-level when is checked before each task in the block, so the condition has to still hold by the time the later roles run.
---
- hosts: rhel6
tasks:
- ansible.builtin.include_role:
name: yum_clean_all
- ansible.builtin.include_role:
name: yum_makecache
- ansible.builtin.include_role:
name: yum_check_update
- ansible.builtin.include_role:
name: yum_download_only
- block:
- ansible.builtin.include_role:
name: yum_update
- ansible.builtin.include_role:
name: yum_clean_all
when:
- yum_download_only is defined
- yum_download_only.stdout.find('No packages marked for updates') == -1
Conclusion
If a conditional block is skipping tasks you expected to run, check whether something inside the block is changing the variable in the when. That was the whole story for me.
And as always, ENJOY!!
Comments