Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for Ansible By James Joyner IV · · 9 min read

Ansible Error Guide: 'The conditional check ... failed'

Fix Ansible's The conditional check failed error: diagnose undefined vars in when, wrong types, quoting of Jinja, string-vs-bool comparisons, and bad registered results.

  • #ansible
  • #troubleshooting
  • #errors
  • #conditionals

Overview

This error means Ansible tried to evaluate a when: (or failed_when/changed_when) conditional and the Jinja2 expression itself blew up — most often because a variable in the condition is undefined, or the expression is malformed. It is not the same as the condition simply being false (that just skips the task). A failed conditional means Ansible could not even decide true or false.

You will see it on the task whose when could not be evaluated:

fatal: [web-01]: FAILED! => {"msg": "The conditional check 'app_result.rc == 0' failed. The error was: error while evaluating conditional (app_result.rc == 0): 'app_result' is undefined\n\nThe error appears to be in '/home/deploy/site/roles/app/tasks/main.yml': line 30, column 7"}

The crucial part is after The error was: — it tells you why evaluation failed: 'x' is undefined, 'dict object' has no attribute ..., or a type/syntax error. Fix that underlying expression, not the boolean logic.

Symptoms

  • A task fails (not skips) with The conditional check '...' failed.
  • The trailing reason is is undefined or has no attribute.
  • The condition references a registered result from a task that was skipped or did not run.
  • A condition compares a string to a boolean and behaves unexpectedly.
ansible-playbook -i inventory.ini site.yml --limit web-01
TASK [app : Restart only if changed] *******************************************
fatal: [web-01]: FAILED! => {"msg": "The conditional check 'svc.changed' failed. The error was: 'svc' is undefined"}

Common Root Causes

1. A variable in the condition is undefined

The most common cause: the when references a var that was never set on this host.

- name: Open firewall
  ansible.posix.firewalld:
    port: 8080/tcp
    state: enabled
  when: enable_app_port
The conditional check 'enable_app_port' failed. The error was: 'enable_app_port' is undefined
ansible web-01 -i inventory.ini -m debug -a "var=enable_app_port"

2. Referencing a registered result from a skipped task

A register exists but the task was skipped, so the subkey you check is missing.

- command: /usr/bin/app --check
  register: appcheck
  when: deploy_app | bool

- debug: msg="ok"
  when: appcheck.rc == 0   # appcheck has no rc if the first task was skipped
The conditional check 'appcheck.rc == 0' failed. The error was: 'dict object' has no attribute 'rc'

3. Wrapping the condition in Jinja braces / extra quotes

when is already a Jinja expression; adding {{ }} or over-quoting changes how it is parsed.

- debug: msg="hi"
  when: "{{ ansible_os_family == 'Debian' }}"

This produces a deprecation/templating issue; the bare form is correct:

when: ansible_os_family == "Debian"

4. String-vs-boolean comparison

A variable that is the string "false" is truthy in Jinja unless coerced. Comparing or relying on it directly gives the wrong branch or a confusing failure.

ansible web-01 -i inventory.ini -m debug -a "var=feature_flag"
"feature_flag": "false"
when: feature_flag | bool    # coerce the string to a real boolean

5. Wrong attribute on a complex registered object

Checking result.results on a non-loop task, or result.stdout on a module that returns no stdout.

- ansible.builtin.stat:
    path: /etc/app.conf
  register: st

- debug: msg="exists"
  when: st.stdout == ""    # stat has no stdout; use st.stat.exists
The conditional check 'st.stdout == ""' failed. The error was: 'dict object' has no attribute 'stdout'

6. Syntax / operator error in the expression

An invalid operator, unbalanced parenthesis, or mixing and/, incorrectly.

when: ansible_distribution == "Ubuntu" and ansible_distribution_major_version >= "20"

A numeric comparison against a string can misbehave; cast with | int:

when: ansible_distribution == "Ubuntu" and ansible_distribution_major_version | int >= 20

Diagnostic Workflow

Step 1: Read the reason after “The error was:”

The conditional check 'svc.changed' failed. The error was: 'svc' is undefined

is undefined / has no attribute points you straight at the variable to inspect.

Step 2: Dump the variables used in the condition

ansible web-01 -i inventory.ini -m debug -a "var=svc"
web-01 | SUCCESS => {
    "svc": "VARIABLE IS NOT DEFINED!"
}

If it is a registered result, dump it to see whether it was skipped and which keys exist.

Step 3: Check whether a prior task was skipped

ansible-playbook -i inventory.ini site.yml --limit web-01 | grep -E 'skipping|TASK'

A skipping: line on the task that does the register explains the missing attribute downstream.

Step 4: Test the expression in isolation

ansible web-01 -i inventory.ini -m debug -a "msg={{ (feature_flag | default('false')) | bool }}"

This proves how the expression evaluates with a safe default before you put it back in when.

Step 5: Guard the condition and re-run

when:
  - appcheck is defined
  - appcheck.rc is defined
  - appcheck.rc == 0
ansible-playbook -i inventory.ini site.yml --limit web-01

Example Root Cause Analysis

A deploy role fails intermittently — only on hosts where a feature is disabled:

fatal: [web-04]: FAILED! => {"msg": "The conditional check 'migrate_result.rc == 0' failed. The error was: 'dict object' has no attribute 'rc'"}

The condition guards a “reload after migration” task. Dumping the registered var on the failing host:

ansible web-04 -i inventory.ini -m debug -a "var=migrate_result"
web-04 | SUCCESS => {
    "migrate_result": {
        "changed": false,
        "skipped": true,
        "skip_reason": "Conditional result was False"
    }
}

The migration task itself has when: run_migrations | bool, which is false on web-04. The task is skipped, so migrate_result exists but has no rc. The downstream when: migrate_result.rc == 0 then fails because it assumes the migration always ran. The unconditional reference to .rc on a sometimes-skipped result is the root cause.

Fix: gate the downstream check on the result being a real run, not a skip:

when:
  - migrate_result is not skipped
  - migrate_result.rc | default(1) == 0

The reload task now skips cleanly on web-04 instead of erroring.

Prevention Best Practices

  • Treat when expressions like code: reference only variables you know are defined, and add is defined / is not skipped guards for anything that comes from a conditional task.
  • Coerce string-y values with | bool and numeric facts with | int so comparisons behave predictably across distros.
  • Never wrap a when value in {{ }} — it is already a Jinja expression; the bare form is correct and avoids templating surprises.
  • Define defaults in defaults/main.yml for any feature-flag variable so a when never hits an undefined name.
  • Run ansible-lint to catch risky conditionals and Jinja-in-when patterns before they reach a host.
  • When a single skipped task cascades into conditional failures, the free incident assistant can trace the chain back to the originating skip. See more in the Ansible guides.

Quick Command Reference

# Read the real reason
# -> "The error was: 'x' is undefined" / "has no attribute"

# Dump the variables used in the condition
ansible <host> -i inventory.ini -m debug -a "var=<name>"

# Did the registering task get skipped?
ansible-playbook -i inventory.ini site.yml --limit <host> | grep -E 'skipping|TASK'

# Test the expression with a safe default
ansible <host> -i inventory.ini -m debug -a "msg={{ (flag | default('false')) | bool }}"

# Safe conditional patterns (YAML):
# when:
#   - result is defined
#   - result is not skipped
#   - result.rc | default(1) == 0

Conclusion

The conditional check '...' failed means the when expression could not be evaluated — distinct from a condition merely being false. The reason after The error was: is the key. The usual root causes:

  1. A variable in the condition is undefined on that host.
  2. A registered result is referenced after its task was skipped.
  3. The condition is wrapped in {{ }} or over-quoted.
  4. A string is treated as a boolean without | bool.
  5. The wrong attribute is checked on a complex registered object.
  6. A syntax or type error (e.g. string-vs-number comparison) in the expression.

Read the trailing reason, dump the variables, and add is defined / is not skipped / | default() guards — that turns a hard failure into a reliable, evaluable condition.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.