Introduction
The no-jinja-when rule in Ansible Lint prevents using Jinja2 expressions (double curly braces {{ }}) inside when, failed_when, and changed_when clauses. While wrapping conditions in {{ }} may appear to work, it is an anti-pattern that can cause subtle bugs, double-evaluation issues, and unexpected behavior. This article explains the rule, demonstrates every common scenario, and shows the correct patterns.
Why This Rule Exists
Ansible processes when clauses differently from regular template strings. The when clause is already implicitly a Jinja2 expression — Ansible automatically evaluates it as Jinja2 without needing {{ }}.
When you add {{ }} inside a when clause, you create a nested expression:
# What you write:
when: "{{ my_var == 'hello' }}"
# What Ansible actually evaluates:
# Step 1: Render the Jinja2 template → "True" or "False" (a string!)
# Step 2: Evaluate the string "True" as a when condition
This double-evaluation can cause problems:
- Type coercion issues: The template renders to a string
"True", not a booleanTrue - Undefined variable errors: Behave differently with nested vs direct evaluation
- Performance: Unnecessary template rendering pass
- Readability: Misleading syntax that confuses other developers
The Error
Problematic Code
---
- name: Example playbook
hosts: localhost
tasks:
- name: Shut down Debian systems
ansible.builtin.command: /sbin/shutdown -t now
when: "{{ ansible_facts['os_family'] == 'Debian' }}"
Lint Output
$ ansible-lint playbook.yml
WARNING Listing 3 violation(s) that are fatal
jinja[spacing]: Jinja2 spacing could be improved
no-jinja-when.yml:5 Task/Handler: Shut down Debian systems
no-changed-when: Commands should not change things if nothing needs doing.
no-jinja-when.yml:5 Task/Handler: Shut down Debian systems
no-jinja-when: No Jinja2 in when.
no-jinja-when.yml:5 Task/Handler: Shut down Debian systems
Rule Violation Summary
count tag profile rule associated tags
1 jinja[spacing] basic formatting (warning)
1 no-jinja-when basic deprecations
1 no-changed-when shared command-shell, idempotency
Failed: 2 failure(s), 1 warning(s) on 1 files.
Correct Code
---
- name: Example playbook
hosts: localhost
tasks:
- name: Shut down Debian systems
ansible.builtin.command: /sbin/shutdown -t now
when: ansible_facts['os_family'] == "Debian"
changed_when: false
Common Patterns: Wrong vs Right
Simple Variable Check
# ❌ Wrong — Jinja2 in when
when: "{{ my_var }}"
# ✅ Right — direct reference
when: my_var
Boolean Comparison
# ❌ Wrong
when: "{{ enable_feature == true }}"
# ✅ Right
when: enable_feature
# ✅ Also right (explicit)
when: enable_feature == true
# ✅ Negation
when: not enable_feature
String Comparison
# ❌ Wrong
when: "{{ ansible_distribution == 'Ubuntu' }}"
# ✅ Right
when: ansible_distribution == 'Ubuntu'
Complex Conditions
# ❌ Wrong
when: "{{ ansible_os_family == 'RedHat' and ansible_distribution_major_version | int >= 8 }}"
# ✅ Right
when:
- ansible_os_family == 'RedHat'
- ansible_distribution_major_version | int >= 8
Variable is Defined
# ❌ Wrong
when: "{{ my_var is defined }}"
# ✅ Right
when: my_var is defined
Register and Check
# ❌ Wrong
- ansible.builtin.command: which nginx
register: nginx_check
ignore_errors: true
- name: Install nginx
ansible.builtin.package:
name: nginx
when: "{{ nginx_check.rc != 0 }}"
# ✅ Right
- ansible.builtin.command: which nginx
register: nginx_check
ignore_errors: true
changed_when: false
- name: Install nginx
ansible.builtin.package:
name: nginx
when: nginx_check.rc != 0
List Membership
# ❌ Wrong
when: "{{ inventory_hostname in groups['webservers'] }}"
# ✅ Right
when: inventory_hostname in groups['webservers']
Combining Conditions with or
# ❌ Wrong
when: "{{ ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian' }}"
# ✅ Right
when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
# ✅ Also right — using 'in'
when: ansible_distribution in ['Ubuntu', 'Debian']
The Same Rule Applies to failed_when and changed_when
# ❌ Wrong
- ansible.builtin.command: check_status.sh
register: result
failed_when: "{{ result.rc > 1 }}"
changed_when: "{{ 'updated' in result.stdout }}"
# ✅ Right
- ansible.builtin.command: check_status.sh
register: result
failed_when: result.rc > 1
changed_when: "'updated' in result.stdout"
When You DO Need Jinja2 in when
There are rare edge cases where you need Jinja2 filters inside when. In these cases, use the filter directly without {{ }}:
# Using filters is fine — no {{ }} needed
when: my_list | length > 0
when: my_var | default('') != ''
when: my_string | regex_search('pattern')
when: my_number | int > 10
The key point: filters work inside when without curly braces because the entire when clause is already evaluated as Jinja2.
What Happens When You Ignore This Rule
Double-Evaluation Bug
# This can cause unexpected behavior:
vars:
my_condition: "{{ some_var == 'value' }}"
tasks:
- name: Conditional task
ansible.builtin.debug:
msg: "Running"
when: "{{ my_condition }}"
# Double evaluation: first renders my_condition, then evaluates the result
# If my_condition is the STRING "True", it works by accident
# If my_condition is the STRING "False", it ALSO evaluates as truthy!
The string "False" is a non-empty string, which is truthy in Python — so when: "{{ my_condition }}" would evaluate as True even when the variable holds the string "False".
Undefined Variable Inconsistency
# With {{ }}: renders to an empty string or error depending on Jinja2 undefined settings
when: "{{ undefined_var == 'test' }}"
# Without {{ }}: raises a clear AnsibleUndefinedVariable error
when: undefined_var == 'test'
Migrating Existing Playbooks
If you have playbooks with {{ }} in when clauses, fix them in bulk:
# Find all occurrences
grep -rn 'when:.*{{' roles/ playbooks/
# Use ansible-lint to find and fix
ansible-lint --fix playbooks/ roles/
Manual Fix Pattern
For each occurrence, simply remove the {{ }} and the surrounding quotes:
# Before
when: "{{ condition }}"
# After
when: condition
Best Practices
- Never use
{{ }}inwhen,failed_when, orchanged_when— the clause is already Jinja2 - Use list format for multiple conditions — cleaner than long
andchains - Use filters directly —
when: my_list | length > 0(no braces needed) - Run
ansible-lintin CI to catch violations automatically - Use explicit comparisons —
when: my_var == trueis clearer thanwhen: my_var - Test with
--check— verify conditional logic before applying changes
Related Articles
- Ansible changed_when failed_when Guide
- Ansible Jinja2 Templates Guide
- Ansible Lint Guide
- Ansible when Conditional Guide
Conclusion
The no-jinja-when rule enforces correct Ansible conditional syntax. The when clause is already a Jinja2 expression — adding {{ }} creates double evaluation that can cause type coercion bugs, unexpected truthy behavior, and undefined variable inconsistencies. The fix is simple: remove the curly braces and quotes. Run ansible-lint --fix to auto-correct existing playbooks.