AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

ansible-core 2.19 Templating Changes: Fix Broken Conditionals & Jinja Errors

By Luca Berton · Published 2024-01-01 · Category: installation

Deep dive into ansible-core 2.19 templating changes. Fix broken conditionals, multi-pass templating errors, Jinja2 order of operations bugs.

ansible-core 2.19 includes a complete overhaul of the templating engine and introduces Data Tagging. This changes how Ansible evaluates conditionals, templates, and expressions — and it will break playbooks that relied on loose truthy evaluation.

This article covers every templating change with before/after code examples so you can fix your playbooks fast.

The Big Picture

The templating overhaul has three effects: Broken conditionals are now errorswhen and assert must return actual booleans Multi-pass templating is removed — no more {{ }} inside expressions Expression syntax errors are caught — previously silent bugs now surface

See also: Ansible 12 Upgrade Guide: Breaking Changes, Data Tagging & What to Test First

Broken Conditionals: The Full List

1. Implicit Boolean Conversion

The most common issue. Variables used directly as conditionals return their value (a string, dict, list) — not a boolean.

# ❌ BEFORE (worked on 2.18, errors on 2.19)
- name: Check if variable has value
  ansible.builtin.debug:
    msg: "exists"
  when: my_variable

# Error: Conditional result was 'some_value' of type 'str', # which evaluates to True. Conditionals must have a boolean result.

# ✅ AFTER — explicit boolean test - name: Check if variable has value ansible.builtin.debug: msg: "exists" when: my_variable | length > 0

# ✅ Or use is defined / is truthy when: my_variable is defined and my_variable is truthy

2. Registered Variable Checks

# ❌ BEFORE
- ansible.builtin.command: cat /etc/hostname
  register: result

- ansible.builtin.debug: msg: "{{ result.stdout }}" when: result.stdout

# ✅ AFTER — check for non-empty when: result.stdout | length > 0

# ✅ Or check return code when: result.rc == 0

3. Quoted Expressions in assert

# ❌ BEFORE — second part is a quoted string (always truthy)
- ansible.builtin.assert:
    that:
      - inventory_hostname is defined and 'inventory_hostname | length > 0'

# Error: Conditional result was 'inventory_hostname | length > 0' # of type 'str', which evaluates to True.

# ✅ AFTER — remove quotes - ansible.builtin.assert: that: - inventory_hostname is defined and inventory_hostname | length > 0

4. Dictionary as Conditional (YAML Trap)

This one is subtle. In YAML, key: value (colon + space) creates a mapping, not a string.

# ❌ BEFORE — YAML parser sees a dict, not a string
- ansible.builtin.assert:
    that:
      - result.msg == "some_key: some_value"

# Error: Conditional expressions must be strings.

# ✅ AFTER — quote the entire expression - ansible.builtin.assert: that: - 'result.msg == "some_key: some_value"'

5. Jinja2 Operator Precedence

The ~ concatenation operator has lower precedence than tests like contains.

# ❌ BEFORE — evaluates as: (hostname is contains "local") ~ "host"
#             result is "Truehost" (truthy string, not boolean)
- ansible.builtin.assert:
    that: inventory_hostname is contains "local" ~ "host"

# ✅ AFTER — parentheses fix precedence - ansible.builtin.assert: that: inventory_hostname is contains("local" ~ "host")

6. Expression Syntax Errors

# ❌ BEFORE — trailing comma silently accepted
- ansible.builtin.assert:
    that: 1 == 2,

# Error: Syntax error in expression: chunk after expression

# ✅ AFTER — remove trailing comma - ansible.builtin.assert: that: 1 == 2

Multi-Pass Templating Removal

Template Delimiters in Expressions

# ❌ BEFORE — {{ }} inside when/assert
- ansible.builtin.debug:
    msg: "match"
  when: "{{ my_var }}" == "expected"

# Error: Template delimiters are not supported in expressions

# ✅ AFTER — use variable directly when: my_var == "expected"

Nested Template Strings

# ❌ BEFORE — template within template
- ansible.builtin.debug:
    msg: "{{ '{{ nested_var }}' }}"

# ✅ AFTER — direct reference - ansible.builtin.debug: msg: "{{ nested_var }}"

Template in Arithmetic

# ❌ BEFORE
- ansible.builtin.assert:
    that: 1 + {{ value }} == 2
  vars:
    value: 1

# ✅ AFTER - ansible.builtin.assert: that: 1 + value == 2 vars: value: 1

Dynamic when Clauses

# ❌ BEFORE — dynamic conditional construction
- ansible.builtin.debug:
    msg: "dynamic"
  when: "{{ condition_var }}"

# ✅ AFTER when: condition_var

See also: Ansible Jinja2 Join Filter: Add Commas Between List Elements

Bulk Fix Script

Use this script to find and categorize issues in your codebase:

#!/bin/bash
# audit-ansible12.sh — Find ansible-core 2.19 compatibility issues

echo "=== Broken Conditionals ===" echo "--- Implicit truthy (when: variable) ---" grep -rn 'when:.*[^=!<>]$' roles/ playbooks/ 2>/dev/null | \ grep -v "is defined\|is not\|==\|!=\|>\|<\|in \|bool\|true\|false\|length\|match\|search"

echo "" echo "--- Template delimiters in expressions ---" grep -rn 'when: "{{ \|when: {{ \|that:.*{{ ' roles/ playbooks/ 2>/dev/null

echo "" echo "--- Quoted sub-expressions in assert ---" grep -rn "that:.*and '" roles/ playbooks/ 2>/dev/null

echo "" echo "--- YAML colon-space in assert ---" grep -rn 'that:' roles/ playbooks/ 2>/dev/null | grep ': ' | grep -v "^.*that:" | grep -v "^#"

echo "" echo "=== Summary ===" echo "Run playbooks with ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true to get warnings instead of errors"

Migration Strategy

Phase 1: Discover (Week 1)

# Run audit script
bash audit-ansible12.sh > audit-results.txt

# Test with warning mode ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true \ ansible-playbook site.yml --check -vv 2>&1 | \ grep "WARNING" > warnings.txt

wc -l warnings.txt

Phase 2: Fix (Weeks 2-3)

Fix in priority order: Production playbooks — anything in your CI/CD pipeline Shared roles — used by multiple teams One-off playbooks — ad hoc automation

Phase 3: Validate (Week 4)

# Run WITHOUT the escape hatch
ansible-playbook site.yml --check -vv

# Zero errors = ready for production

Phase 4: Upgrade Production

pip install ansible==12.0.0
ansible --version  # Verify ansible-core 2.19.x
ansible-playbook smoke-test.yml

See also: Generate Clean YAML Output from Ansible Facts

Common Fix Patterns Reference

| Before (2.18) | After (2.19) | Issue | |---|---|---| | when: my_var | when: my_var \| length > 0 | Truthy string | | when: result.stdout | when: result.stdout \| length > 0 | Truthy string | | when: my_list | when: my_list \| length > 0 | Truthy list | | when: my_dict | when: my_dict \| length > 0 | Truthy dict | | when: "{{ var }}" | when: var | Template in expr | | that: '...' (with and '...') | Remove inner quotes | Quoted sub-expr | | that: x == "a: b" | that: 'x == "a: b"' | YAML colon trap | | x is contains "a" ~ "b" | x is contains("a" ~ "b") | Operator precedence |

FAQ

What is the ALLOW_BROKEN_CONDITIONALS setting?

A temporary configuration option in ansible-core 2.19 that changes broken conditional errors to warnings. Set allow_broken_conditionals = true in ansible.cfg under [defaults] or export ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true. Use it during migration only — it will be removed in a future release.

Why did ansible-core 2.19 change the templating engine?

The overhaul introduces Data Tagging, which tracks metadata about template data for improved security (prevents untrusted template execution), performance, and user experience (catches real bugs that were silently ignored for years).

How do I fix "Conditional result was X of type str"?

Add an explicit boolean test. If checking a string, use | length > 0 or is truthy. If checking existence, use is defined. If comparing values, use == or !=. The key rule: every when and assert expression must evaluate to True or False, not a truthy value.

Will these templating changes be reverted?

No. The broken conditional enforcement and multi-pass templating removal are permanent security and correctness improvements. The ALLOW_BROKEN_CONDITIONALS option is a temporary migration aid only.

Conclusion

ansible-core 2.19's templating changes are the most significant in Ansible's history. They catch real bugs and prevent security issues — but they require updating existing playbooks. Use the audit script, fix in phases, and test thoroughly before upgrading production.

Related Articles

Ansible 12 Upgrade GuideAnsible Conditionals: when & assertAnsible Jinja2 Filters Complete ReferenceAnsible block/rescue/always Error Handling

Category: installation

Browse all Ansible tutorials · AnsiblePilot Home