← All TILs · ansible

Forcing types: extra vars arrive as strings, and ansible-core 2.19 stopped guessing

ansible - 2026-10-02

Ninth entry in the Shaping data in Ansible series. Extra vars are variables given on the command line with -e (--extra-vars). They override every other variable, which makes them the usual way to flip a switch for one run. But -e name=value always gives a string, whatever the value looks like. A switch that reads false is the five-character string "false", and the number 200 is "200".

ansible-core 2.19 changed what happens next. A when: that gets a string instead of a boolean now fails instead of guessing. This entry shows the failure and the conversion filters that fix it: bool, int, float and string. Everything below ran with ansible-core 2.21.4.

The play and its two switches

A maintenance play for the PostgreSQL server from part 3 has two switches, with defaults in the play:

vars:
  restart: true
  max_connections: 100
tasks:
  - name: Restart PostgreSQL, if restart
    ansible.builtin.service:
      name: postgresql
      state: restarted
    when: restart

Someone runs it with -e restart=false -e max_connections=200 to apply a setting without a restart. The type_debug filter, which returns a value's type, showed:

Extra vars restart max_connections
none (defaults) true (bool) 100 (int)
-e restart=false -e max_connections=200 "false" (str) "200" (str)
-e '{"restart": false, "max_connections": 200}' false (bool) 200 (int)

The last form is JSON, and JSON keeps its types: false without quotes is a boolean and 200 a number. -e @vars.yml reads a YAML file and keeps its types the same way.

when: on a string is now an error

With -e restart=false, the task failed:

Conditional result (True) was derived from value of type 'str' at "". Conditionals must have a boolean result.

The message says what happened, and where: the string "false", like any non-empty string, counts as true. Before 2.19 it didn't stop there. ansible-core 2.18.19, run the same way, printed a warning, "The result was interpreted as True. This feature will be removed in version 2.19", and ran the restart task anyway. The 2.19 porting guide calls this a broken conditional: "truthy conditional evaluation often masks serious logic errors in playbooks", and 2.19 reports it as an error by default.

The porting guide also names the way back: the ALLOW_BROKEN_CONDITIONALS setting turns the error into a warning. With ANSIBLE_ALLOW_BROKEN_CONDITIONALS=true and the same -e restart=false, the play printed a deprecation warning, "This feature will be removed from ansible-core version 2.23", and the restart task ran. That's the old behaviour, and exactly the bug the error exists to catch. Turning the setting on to silence the error brings the bug back.

The porting guide's other suggestion, the is truthy test, doesn't help a switch either: 'false' is truthy was true.

bool: the conversion a switch needs

when: restart | bool

With -e restart=false, the task was skipped. With JSON extra vars or the play's default, it behaved the same, because bool passes real booleans through. ansible-core's bool filter, to_bool in plugins/filter/core.py, accepts these strings, in any case:

Becomes true Becomes false
yes, on, true, 1 no, off, false, 0

"On" and "OFF" worked as well as "on" and "off". Anything else becomes false, with a warning. A typo, -e restart=fasle, gave "The bool filter coerced invalid value 'fasle' (str) to False. This feature will be removed from ansible-core version 2.23." Here the typo skipped the restart, which is the safe side. For a switch where false is the dangerous side, check the value with an assert task: that: restart in [true, false, 'true', 'false'].

int and float: numbers that arrive as strings

max_connections > 150 with -e max_connections=200 failed too: "'>' not supported between instances of '_AnsibleTaggedStr' and 'int'". A string can't be compared with a number. max_connections | int > 150 was true.

int doesn't fail on input it can't read; it returns 0:

Expression Result
"200" \| int 200
" 200 " \| int 200
"4GB" \| int 0
"4GB" \| int(-1) -1
"2.5" \| int 2
"2.5" \| float 2.5
"0x1F" \| int 0
"0x1F" \| int(0, 16) 31
200 \| string "200"

Which one

The example repository

The series' companion repository, abdelhousni/ansible-data-shaping-series, runs all of the above on the local machine, changing nothing outside its out/ directory. run.sh runs switches.yml with its defaults, with key=value extra vars, with JSON, with a typo and with ALLOW_BROKEN_CONDITIONALS, and records what each conditional and comparison did; convert.yml records the conversions in the tables above. Its CI runs both on every push and compares the output with the expected one.

Sources

Created 2026-10-02T23:33:38+02:00 · Edit