TL;DR โ Quick Fix
Nine times out of ten, this error comes down to one of two things:
- A string value contains a colon
:that isn't quoted - Tabs crept into your indentation โ YAML only accepts spaces
Three steps to clear it:
- Wrap any value containing
:in double quotes:msg: "Error: connection failed" - Replace every tab with 2-space indentation
- Run
ansible-playbook --syntax-check playbook.ymlto confirm
The Full Error
ERROR! We were unable to read either as JSON nor YAML, these are the errors we got from each:
JSON: Expecting value: line 1 column 1 (char 0)
YAML: mapping values are not allowed in this context
in "<unicode string>", line 12, column 18
That line and column number is the fastest clue you have. Jump straight there before anything else โ don't scroll around guessing.
Root Causes
1. Unquoted colon in a string value (most common)
YAML treats : as a key-value separator. Drop one inside a string without quotes and the parser reads it as a new key โ which breaks everything after it.
# BROKEN โ YAML sees "Error" as a key
- name: Show error
debug:
msg: Error: connection timed out
# FIXED โ wrap in quotes
- name: Show error
debug:
msg: "Error: connection timed out"
URLs hit the same trap:
# BROKEN
url: https://example.com/api
# FIXED
url: "https://example.com/api"
2. Mixed tabs and spaces
YAML strictly forbids tabs for indentation. One stray tab โ say, from pasting code out of Slack or a PDF โ and the parser fails. Worse, it often points to the wrong line number.
# Spot invisible characters in vim
:set list
# Or use cat -A โ tabs show up as ^I
cat -A playbook.yml | grep "\^I"
3. Wrong indentation level
Ansible expects consistent 2-space indentation throughout. Mix 2-space and 4-space blocks in the same file and this error follows.
# BROKEN โ inconsistent indentation
- name: Install nginx
apt:
name: nginx
state: present # extra 2 spaces breaks it
# FIXED
- name: Install nginx
apt:
name: nginx
state: present
4. Special characters in unquoted values
Not just colons. Any of these characters can trigger the error when left unquoted: : { } [ ] , & * # ? | - < > = ! % @ \
# BROKEN
shell: echo hello && exit 0
# FIXED โ double-quote it
shell: "echo hello && exit 0"
# Or use a literal block scalar
shell: |
echo hello && exit 0
How to Find and Fix the Problem
Step 1 โ Run syntax check first
ansible-playbook --syntax-check playbook.yml
This outputs the exact line number. Use it. Don't eyeball the file hoping to spot the issue.
Step 2 โ Get detailed feedback with yamllint
# Install
pip install yamllint
# Check the file
yamllint playbook.yml
# Ansible-friendly config with relaxed line length
yamllint -d "{extends: default, rules: {line-length: {max: 120}}}" playbook.yml
A single yamllint run flags tabs, bad indentation, and unquoted special chars all at once. It's faster than fixing one error at a time.
Step 3 โ Hunt down and remove tabs
# Find tabs โ grep -Pn uses Perl regex for \t
grep -Pn "\t" playbook.yml
# Replace tabs with 2 spaces in-place
sed -i 's/\t/ /g' playbook.yml
# In vim: convert tabs on the fly
:set expandtab tabstop=2
:%retab
Step 4 โ Confirm the fix
ansible-playbook --syntax-check playbook.yml
# Clean output looks like this:
playbook: playbook.yml
Common Patterns That Break YAML
Pattern 1 โ Colon in when conditions
# BROKEN โ the trailing ": true" is invalid here
when: ansible_os_family == "Debian": true
# FIXED โ the condition evaluates to true on its own
when: ansible_os_family == "Debian"
Pattern 2 โ Multi-line strings with colons
# BROKEN โ colon on the continuation line confuses the parser
msg: This is a message
with: a colon midway
# FIXED โ use a literal block scalar
msg: |
This is a message
with: a colon midway
Pattern 3 โ Wrong syntax in Jinja2 default filter
# BROKEN โ default filter does not use a colon
msg: "{{ my_var | default: 'fallback' }}"
# FIXED โ use = or pass the value as an argument
msg: "{{ my_var | default('fallback') }}"
Tips
When I'm debugging a complex playbook that keeps throwing YAML errors, I paste the broken section into the YAML โ JSON Converter at ToolCraft. It converts YAML to JSON in real time, so you can instantly see where the parser fails โ no need to run Ansible repeatedly. Everything stays in the browser, nothing gets uploaded.
It also works in reverse: if someone sends you a JSON config and you need to put it in a playbook, convert it to YAML and the indentation is already correct.
Prevention
- Editor settings: Set indent to 2 spaces, turn on "show invisibles" to catch tabs before they cause trouble, and enable YAML language support
- VS Code: Install the "YAML" extension by Red Hat โ it validates playbooks on save and underlines problems inline
- Add yamllint to CI: One line in your pipeline (
yamllint .) catches syntax issues before any Ansible run happens - Commit a
.yamllintconfig to the repo root so every team member is checked against the same rules
Quick Reference
# Always quote these patterns:
msg: "Error: something failed" # colon in value
url: "https://example.com" # URL
cmd: "echo foo && bar" # special shell chars
regexp: "^\\d+: " # regex with colons
# Use block scalars for multi-line content:
script: |
#!/bin/bash
echo "Running: $1"
exit 0

