I ran into a perplexing problem where Ansible was running a task in a role even if the role had a when condition which was resolving to false. Plus the task was failing. To visualize it better, one task (not all) was running in the role _symptom_ in the example playbook below, --- - hosts: all roles: - role: symptom when: - false The code that reproduces the issue is below, ./play.yml --- - hosts: all roles: - role: symptom when: - false ./roles/symptom/tasks/main.yml --- - name: create temp directory file: path: /tmp/blog state: directory - name: create directory structure file: path: "/tmp/blog/{{ item }}" state: directory loop: "{{ ['one', 'two', 'three', 'four'] }}" - name: get directories find: paths: /tmp/blog recurse: yes file_type: directory register: reg_dirs - name: must not run when role is ignored file: path: "{{ item }}/test" state: present loop: "{{ reg_dirs | json_query('file.[*]') }}" ./inventory all: children: localhost: hosts: localhost: ansible_connection: local ./ansible.cfg [defaults] become_allow_same_user = yes inventory = ./inventory In the code above, task "_must not run when role is ignored_" was being executed and failing with message "Invalid data passed to 'loop', it requires a list, got this instead: . Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup." When you look at play.yml, the role has a condition of _false_. How could this be? Hint: the line `loop: "{{ reg_dirs | json_query('file.[*]') }}"` is significant, where variable _reg\_dirs_ is being evaluated and processed. $ ansible-playbook play.yml [WARNING]: Found both group and host with same name: localhost PLAY [all] *********************************************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************************************** [WARNING]: Platform darwin on host localhost is using the discovered Python interpreter at /usr/local/bin/python3.11, but future installation of another Python interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-core/2.15/reference_appendices/interpreter_discovery.html for more information. ok: [localhost] TASK [roles/symptom : create temp directory] ******************************************************************************************************** skipping: [localhost] TASK [roles/symptom : create directory structure] ******************************************************************************************************** skipping: [localhost] => (item=one) skipping: [localhost] => (item=two) skipping: [localhost] => (item=three) skipping: [localhost] => (item=four) skipping: [localhost] TASK [roles/symptom : get directories] ******************************************************************************************************************* skipping: [localhost] TASK [roles/symptom : must not run when role is ignored] ************************************************************************************************* fatal: [localhost]: FAILED! => {"msg": "Invalid data passed to 'loop', it requires a list, got this instead: . Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup."} PLAY RECAP *********************************************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=1 skipped=3 rescued=0 ignored=0 Also notice that all tasks were skipped except the one with `loop:`. This is the symptom reported by someone else, "[Tasks inside a block that use a loop are not skipped even if the block's when condition evaluates to false](https://github.com/ansible/ansible/issues/64883)". The real nugget was a [reply](https://github.com/ansible/ansible/issues/64883#issuecomment-554403674) in the post, "The meaning of that, is that the when statement is evaluated for each iteration of the loop, and not before the loop. As a result, your loop must always evaluate as an iterable, regardless of the when statement." I tried to follow this advice but it wasn't working. I realized I had one more aspect in my Ansible code: the loop was using a variable that was created as part of `register:` from a previous task. It appeared that variables set in the following way did not have an impact in my case, - vars/main.yml - inventory - passed with `--extra-vars` - set_fact But a variable created during a task with `register:` was causing my issue. I speculate that in the former case Ansible knows the value of the variable when it is used in `loop:`. But the combination of a variable where the value is unknown until the task (where it is registered) runs and processing its value in the loop causes this issue. As loop is evaluated before the when condition it kind of makes sense. With the above assumption, I moved the processing of `"{{ reg_dirs | json_query('file.[*]') }}"` before the loop task into a `set_fact`. This modified my assumption that Ansible knows the value of set_fact, because obviously it doesn't. I changed my assumption to that Ansible _does not_ evaluate the value of set_fact like it does for `loop:` until later in the execution. In other words, any evaluations in `loop:` are done earlier in the execution but evaluation in `set_fact:` are much later. Making this change solved my issue. ./play.yml --- - hosts: all roles: - role: fixed when: - false ./roles/fixed/tasks/main.yml --- - name: create temp directory file: path: /tmp/blog state: directory - name: create directory structure file: path: "/tmp/blog/{{ item }}" state: directory loop: "{{ ['one', 'two', 'three', 'four'] }}" - name: get directories find: paths: /tmp/blog recurse: yes file_type: directory register: reg_dirs - name: set fact set_fact: list_dirs: "{{ reg_dirs | json_query('files[*].path') }}" - name: must not run when role is ignored file: path: "{{ item }}/test" state: present loop: "{{ list_dirs }}" Notice that `loop: "{{ reg_dirs | json_query('file.[*]') }}"` in the failing case becomes two parts, `set_fact: list_dirs: "{{ reg_dirs | json_query('files[*].path') }}"` and `loop: "{{ list_dirs }}"`. Problem solved. $ ansible-playbook play.yml [WARNING]: Found both group and host with same name: localhost PLAY [all] *********************************************************************************************************************************************** TASK [Gathering Facts] *********************************************************************************************************************************** [WARNING]: Platform darwin on host localhost is using the discovered Python interpreter at /usr/local/bin/python3.11, but future installation of another Python interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-core/2.15/reference_appendices/interpreter_discovery.html for more information. ok: [localhost] TASK [roles/fixed : create temp directory] ********************************************************************************************************** skipping: [localhost] TASK [roles/fixed : create directory structure] ********************************************************************************************************** skipping: [localhost] => (item=one) skipping: [localhost] => (item=two) skipping: [localhost] => (item=three) skipping: [localhost] => (item=four) skipping: [localhost] TASK [roles/fixed : get directories] ********************************************************************************************************************* skipping: [localhost] TASK [roles/fixed : set fact] **************************************************************************************************************************** skipping: [localhost] TASK [roles/fixed : must not run when role is ignored] *************************************************************************************************** skipping: [localhost] PLAY RECAP *********************************************************************************************************************************************** localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=5 rescued=0 ignored=0