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:

  1. Type coercion issues: The template renders to a string "True", not a boolean True
  2. Undefined variable errors: Behave differently with nested vs direct evaluation
  3. Performance: Unnecessary template rendering pass
  4. 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

  1. Never use {{ }} in when, failed_when, or changed_when — the clause is already Jinja2
  2. Use list format for multiple conditions — cleaner than long and chains
  3. Use filters directly — when: my_list | length > 0 (no braces needed)
  4. Run ansible-lint in CI to catch violations automatically
  5. Use explicit comparisons — when: my_var == true is clearer than when: my_var
  6. Test with --check — verify conditional logic before applying changes

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.