Ansible Blocks With Conditionals

3 minute read

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 when is 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