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 undefinedorhas 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
whenexpressions like code: reference only variables you know are defined, and addis defined/is not skippedguards for anything that comes from a conditional task. - Coerce string-y values with
| booland numeric facts with| intso comparisons behave predictably across distros. - Never wrap a
whenvalue in{{ }}— it is already a Jinja expression; the bare form is correct and avoids templating surprises. - Define defaults in
defaults/main.ymlfor any feature-flag variable so awhennever hits an undefined name. - Run
ansible-lintto catch risky conditionals and Jinja-in-whenpatterns 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:
- A variable in the condition is undefined on that host.
- A registered result is referenced after its task was skipped.
- The condition is wrapped in
{{ }}or over-quoted. - A string is treated as a boolean without
| bool. - The wrong attribute is checked on a complex registered object.
- 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.
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.