#85605·ansible

Ansible 2.19.0 breaks `loop`-templates because of jinja native types.

Author: mricherzhagenCreated Jul 31, 2025Updated Sep 15, 2026
Labelsbug

Summary

After upgrading to Ansible 2.19.0 some of our loop: jinja templates don't work any more and report an Provide a list of items/templates, or a template resolving to a list. error.

Issue Type

Bug Report

Component Name

ansible, lib/ansible/executor/task_executor, TemplateEngine

Ansible Version

$ ansible --version
ansible-playbook [core 2.19.0]
  config file = /.../ansible.cfg
  configured module search path = ['/.../.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.13/site-packages/ansible
  ansible collection location = /.../.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible-playbook
  python version = 3.13.5 (main, Jun 21 2025, 09:35:00) [GCC 15.1.1 20250425] (/usr/bin/python)
  jinja version = 3.1.6
  pyyaml version = 6.0.2 (with libyaml v0.2.5)

Configuration

# if using a version older than ansible-core 2.12 you should omit the '-t all'
$ ansible-config dump --only-changed -t all
CONFIG_FILE() = /.../ansible.cfg
DEFAULT_LOG_PATH(/.../ansible.cfg) = /.../ansible.log
EDITOR(env: EDITOR) = vim
PAGER(env: PAGER) = less

GALAXY_SERVERS:

OS / Environment

$ uname -a
Linux hostname 6.15.7-arch1-1 #1 SMP PREEMPT_DYNAMIC Thu, 17 Jul 2025 21:05:29 +0000 x86_64 GNU/Linux

Steps to Reproduce

This playbook works as expected in previous ansible versions, but fails in 2.19.0 claiming the loop template does not resolve to a list.

---
- name: Test loop jinja template
  hosts: localhost
  vars:
    somevar:
      somefile1:
        typea:
          - mail1
          - mail2
        typeb:
          - mail3
  tasks:
  - debug:
      msg: "File: {{ item.file }} Type: {{ item.type }} mails: {{ item.mails }}"
    loop: |
      [
        {% for file, types in somevar.items() %}
          {% for type, mails in types.items() %}
            {"file": "{{ file }}", "type": "{{ type }}", "mails": {{ mails }} },
          {% endfor %}
        {% endfor %}
      ]

But the provided template does resolve to a list, even when using a jinja2 NativeEnvironemnt:

import jinja2.nativetypes

T = """[
  {% for file, types in somevar.items() %}
    {% for type, mails in types.items() %}
      {"file": "{{ file }}", "type": "{{ type }}", "mails": {{ mails }} },
    {% endfor %}
  {% endfor %}
]"""


env = jinja2.nativetypes.NativeEnvironment()

data = {
  "somefile1": {
    "typea": [
      "mail1",
      "mail2",
    ], 
    "typeb": [
      "mail3",
    ]
  },
}

result = env.from_string(T).render(somevar=data)
print(type(result)) #Prints:` <class 'list'>
print(result)

I suspect that at some place in the ansible pipeline some whitespace sneaks into the template, which makes it resolve to a str and not a list.

Even flatting the whole loop template to a single line and wrapping it in ' still reproduces the error, though.

Expected Results

The playbook should complete successfully looping over the values.

Actual Results

ansible-playbook [core 2.19.0]
  config file = /.../ansible.cfg
  configured module search path = ['/.../.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.13/site-packages/ansible
  ansible collection location = /.../.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible-playbook
  python version = 3.13.5 (main, Jun 21 2025, 09:35:00) [GCC 15.1.1 20250425] (/usr/bin/python)
  jinja version = 3.1.6
  pyyaml version = 6.0.2 (with libyaml v0.2.5)
Using /.../ansible.cfg as config file
host_list declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
Skipping due to inventory source not existing or not being readable by the current user
script declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
auto declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
Skipping due to inventory source not existing or not being readable by the current user
yaml declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
Skipping due to inventory source not existing or not being readable by the current user
ini declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
Skipping due to inventory source not existing or not being readable by the current user
toml declined parsing /etc/ansible/hosts as it did not pass its verify_file() method
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'
Skipping callback 'minimal', as we already have a stdout callback.
Skipping callback 'oneline', as we already have a stdout callback.

PLAYBOOK: test_loop_template.yml ***************************************************************************************************************************
1 plays in test_loop_template.yml

PLAY [Test loop jinja template] ****************************************************************************************************************************

TASK [Gathering Facts] *************************************************************************************************************************************
task path: /.../test_loop_template.yml:2
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: user
<127.0.0.1> EXEC /bin/sh -c 'echo ~user && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /.../.ansible/tmp `"&& mkdir "` echo /.../.ansible/tmp/ansible-tmp-1753963312.4276824-225544-267776214024951 `" && echo ansible-tmp-1753963312.4276824-225544-267776214024951="` echo /.../.ansible/tmp/ansible-tmp-1753963312.4276824-225544-267776214024951 `" ) && sleep 0'
Using module file /usr/lib/python3.13/site-packages/ansible/modules/setup.py
<127.0.0.1> PUT /.../.ansible/tmp/ansible-local-225541klf5vx7c/tmpodvhhgt1 TO /.../.ansible/tmp/ansible-tmp-1753963312.4276824-225544-267776214024951/AnsiballZ_setup.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+rwx /.../.ansible/tmp/ansible-tmp-1753963312.4276824-225544-267776214024951/ /.../.ansible/tmp/ansible-tmp-1753963312.4276824-225544-267776214024951/AnsiballZ_setup.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python /.../.ansible/tmp/ansible-tmp-1753963312.4276824-225544-267776214024951/AnsiballZ_setup.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /.../.ansible/tmp/ansible-tmp-1753963312.4276824-225544-267776214024951/ > /dev/null 2>&1 && sleep 0'
ok: [localhost]

TASK [debug] ***********************************************************************************************************************************************
task path: /.../test_loop_template.yml:18
[ERROR]: The `loop` value must resolve to a 'list', not 'str'.
Origin: /.../test_loop_template.yml:20:11

18   - debug:
19       msg: "File: {{ item.file }} Type: {{ item.type }} mails: {{ item.mails }}"
20     loop: |
             ^ column 11

Provide a list of items/templates, or a template resolving to a list.

fatal: [localhost]: FAILED! => {
    "msg": "The `loop` value must resolve to a 'list', not 'str'."
}

PLAY RECAP *************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

Code of Conduct

  • I agree to follow the Ansible Code of Conduct