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 errors — when 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 Guide • Ansible Conditionals: when & assert • Ansible Jinja2 Filters Complete Reference • Ansible block/rescue/always Error HandlingCategory: installation