The Error
You're looping over a list or using a variable in a when condition and Ansible throws:
The conditional check 'item > 5' failed. The error was: error while evaluating conditional (item > 5): 'item' is a string and cannot be compared to an integer
The playbook halts. Nothing runs. The maddening part? The value looks like a number โ it's just that Jinja2 disagrees.
Why This Happens
Ansible variables are loosely typed โ and that flexibility bites you here. Define a list in a vars file, inventory, or from a register result, and values often arrive as strings even when they look numeric:
vars:
port_list:
- "8080"
- "443"
- "3000"
Jinja2 sees "8080" and 5 as incompatible types. Python 3 โ which Ansible runs on โ refuses to silently coerce strings into numbers during comparisons. Python 2 let that slide; Python 3 does not.
Common sources of string-typed numbers:
- YAML values wrapped in quotes:
port: "8080" - Environment variables from
lookup('env', ...) - Command output captured via
register+stdout - Variables passed via
--extra-varson the CLI (always strings, no exceptions) - Remote facts like
ansible_processor_count, depending on the host
Diagnosing the Problem
First, confirm what type Ansible actually sees. Drop a debug task in front of the failing condition:
- name: Debug item type
debug:
msg: "Value: {{ item }} | Type: {{ item | type_debug }}"
loop: "{{ port_list }}"
If the output shows Type: str instead of Type: int, that's your culprit.
Checking a single variable works the same way:
- name: Check variable type
debug:
msg: "{{ my_count | type_debug }}"
Fix 1: Cast with the int Filter (Recommended)
Use the int filter โ it converts the string to an integer right inside the when expression, no restructuring needed:
- name: Only process ports above 1024
debug:
msg: "High port: {{ item }}"
loop: "{{ port_list }}"
when: item | int > 1024
The cast happens before the comparison runs. If a value can't be converted โ say, an empty string โ the filter defaults to 0, which prevents a crash rather than blowing up the run.
Need a different fallback? Set it explicitly:
when: item | int(default=0) > 1024
Fix 2: Remove Quotes from the Variable Definition
Got direct control over the variable definition? Drop the quotes:
# Before (strings)
port_list:
- "8080"
- "443"
# After (integers)
port_list:
- 8080
- 443
Unquoted numbers in YAML are always parsed as integers. Cleaner source data means simpler playbooks โ no filter juggling required downstream.
Fix 3: Cast at the Point of Registration
register results always come back as strings. Cast early โ ideally right in the when condition where you use it:
- name: Get number of running containers
command: docker ps -q | wc -l
register: container_count
- name: Warn if too many containers
debug:
msg: "Container count is high!"
when: container_count.stdout | int > 10
If you'll reuse the value in multiple places, store the cast result in a fact:
- name: Set integer fact
set_fact:
container_count_int: "{{ container_count.stdout | int }}"
- name: Use integer fact in condition
debug:
msg: "High count: {{ container_count_int }}"
when: container_count_int > 10
Fix 4: Extra Vars from the CLI
--extra-vars strings are always strings โ no matter what they look like. Cast them inside the playbook:
ansible-playbook site.yml --extra-vars "max_retries=5"
- name: Retry check
debug:
msg: "Too many retries"
when: max_retries | int > 3
Alternatively, pass the value as JSON to preserve the type at the source:
ansible-playbook site.yml --extra-vars '{"max_retries": 5}'
Verifying the Fix
Run with -v and watch the task output:
ansible-playbook site.yml -v
A working fix looks like this:
TASK [Only process ports above 1024]
skipping: [localhost] => (item=443)
ok: [localhost] => (item=8080)
No more 'item' is a string. The condition skips 443 and runs on 8080, exactly as intended.
Still unsure the cast landed? Print the type explicitly after the filter:
- name: Confirm type after cast
debug:
msg: "Casted type: {{ item | int | type_debug }}"
loop: "{{ port_list }}"
Expected output: Casted type: int
Quick Reference
{{ item | int }}โ cast string to integer (defaults to 0 on failure){{ item | float }}โ cast to float if needed{{ item | string }}โ cast integer back to string{{ item | type_debug }}โ print the actual Python type- Unquoted YAML numbers (
8080) are integers; quoted ones ("8080") are strings
Lessons Learned
Nine times out of ten, the culprit is a quoted number in YAML or a value piped in from a shell command. Both arrive as strings. The int filter handles either case in-place โ one small addition, no playbook restructuring.
One habit worth building: stop quoting numbers in vars files. YAML treats 8080 as an integer automatically. Let it do that work, and half these type errors disappear before they start.

