AnsibleXI · PlaybooksPlaybooks
The pre-flight options, and what each one proves
What you'll learn
- Run the pre-flight ladder in order and state what each rung establishes
- Name the specific claim each option does not make
- Recognise which tasks check mode silently skips rather than predicts
- Use --start-at-task and --step for recovery and inspection
Prerequisites
Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11
The Linux course’s linux-safe-cm-rollout teaches the habit: never run
configuration management against production without a ladder of
increasingly expensive checks first. This lesson gives that ladder its
precise Ansible form — and, more usefully, states what each rung does
not prove.
An option that answers a narrower question than you think it does is worse than no option at all, because it produces confidence.
The ladder
| Rung | Option | Proves | Does not prove |
|---|---|---|---|
| 1 | --syntax-check | The playbook and its statically included files parse | That any module, host, variable or path exists |
| 2 | --list-hosts | The set of hosts each play resolves to, and its size | That those hosts are reachable, or in what order they run |
| 3 | --list-tasks | The resolved execution order of the tasks you wrote | That the tasks will succeed, or what dynamic includes will add |
| 4 | --list-tags | Which tags exist, so --tags will select what you expect | That a tagged subset is a coherent unit of work |
| 5 | --check | A prediction of what would change, per host | Anything about tasks that do not support check mode |
| 6 | --check --diff | The predicted content of file changes | That the rendered content is correct, only that it differs |
| 7 | --step / --start-at-task | Interactive or partial execution for recovery | That skipping earlier tasks was safe |
Climb it in order. Each rung is cheaper than the one below it and rules out a class of mistake that would otherwise waste the next rung’s time.
1. --syntax-check — it parses
$ ansible-playbook -i inventory.ini site.yml --syntax-checkplaybook: site.ymlThat single line and exit code 0 are the whole result. It confirms the
YAML is valid, the play keys are recognised, and everything reachable
through import_playbook and import_tasks also parses.
It does not open a connection, does not check that
ansible.builtin.tempalte is a real module name, and does not evaluate
a single Jinja expression. A playbook that passes --syntax-check can
still fail on its first task on every host.
Run it in CI on every commit. It is fast, it catches the indentation mistakes that YAML makes easy, and it costs nothing.
2. --list-hosts — this is who
Covered in the previous lesson. The number in hosts (n) is your blast
radius. Read it for every play in the file, not just the one you
changed.
3. --list-tasks — this is what, and in what order
$ ansible-playbook -i inventory.ini site.yml --list-tasksplaybook: site.yml
play #1 (lb): Configure the load balancer TAGS: [lb]
tasks:
Render the backend pool TAGS: [config, lb]
play #2 (web): Deploy the web tier TAGS: []
tasks:
Take the host out of the load balancer pool TAGS: []
webserver : R1 role task, notifies H-role TAGS: []
Apply the site-specific tuning TAGS: []
Return the host to the load balancer pool TAGS: []
play #3 (db): Configure the database tier TAGS: [db]
tasks:
Check replication lag TAGS: [db, health]This is the rung that catches structural mistakes: a role you forgot you
had included, tasks in an order you did not intend, a pre_tasks step
that is not where you thought it was.
Its limits are specific and worth memorising:
Gathering Factsis absent. It is injected by the play, not written by you.- Handlers are absent. They are conditional and have no fixed position.
include_tasksandinclude_rolecontribute nothing. Dynamic includes are resolved at execution time, so their contents cannot appear in a static listing.import_tasksandimport_roleare static and do appear.
The last one is the trap. A playbook that is mostly include_role can
produce a short, reassuring --list-tasks output for a run that will
execute two hundred tasks.
4. --list-tags — what --tags will select
$ ansible-playbook -i inventory.ini site.yml --list-tagsplaybook: site.yml
play #1 (lb): Configure the load balancer TAGS: [lb]
TASK TAGS: [config, lb]
play #2 (web): Deploy the web tier TAGS: []
TASK TAGS: []
play #3 (db): Configure the database tier TAGS: [db]
TASK TAGS: [db, health]The load-balancer play declares tags: ['lb'] at play level and its one
task declares tags: ['config']. The task’s effective tags are both.
Play-level tags are inherited, which is how --tags lb selects the
whole play.
What this does not prove is that --tags config selects a coherent
subset. Tag selection is per task, and a tag that pulls in the task that
writes a config file but not the handler that reloads the service
produces a half-applied change. Tags are a filter, not a unit of work,
and the error-handling part returns to this.
5. --check — a prediction, with holes in it
Check mode asks every module to report what it would do without doing
it. Where a module implements check mode properly, the result is a
genuine prediction, and the recap’s changed count is the number of
tasks that would have changed something.
The hole is that not every module implements it, and the ones that do not are skipped:
$ ansible-playbook -i inv2.ini checkmode.yml --checkTASK [A debug task] ************************************************************
ok: [localhost] => {
"msg": "hello"
}
TASK [A command task with no check-mode support] *******************************
skipping: [localhost]
TASK [A stat probe] ************************************************************
ok: [localhost]
TASK [Report the probe] ********************************************************
ok: [localhost] => {
"msg": "exists=True"
}
PLAY RECAP *********************************************************************
localhost : ok=3 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0skipped=1. Not changed=1, not a warning, not an error — a skip that
looks exactly like a when: condition evaluating false.
Check mode also cannot predict second-order effects. A task that templates a config file can tell you the file would change; it cannot tell you the service will fail to start with the new content.
6. --check --diff — the predicted content
Adding --diff makes file-writing modules print a unified diff of the
change they would make. It converts “this file would change” into “these
three lines would change”, which is the difference between a prediction
you can review and one you can only count.
7. --start-at-task and --step — recovery and inspection
--start-at-task skips forward to the first task whose name matches and
begins there:
$ ansible-playbook -i inv2.ini checkmode.yml --start-at-task='A stat probe'TASK [A stat probe] ************************************************************
ok: [localhost]
TASK [Report the probe] ********************************************************
ok: [localhost] => {
"msg": "exists=True"
}
PLAY RECAP *********************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0This is the fastest way to resume a long play that failed at task 40,
and it is also a good way to break one. The skipped tasks did not run,
so anything they registered is undefined and anything they set with
set_fact is missing. If task 12 registered a value that task 45 reads,
starting at task 40 gives you an undefined-variable failure — or worse,
a default that silently differs.
Matching is on the first task whose name matches, which is the second reason task names should be unique.
--step prompts before each task, letting you walk a play by hand. On
2.21.3 the prompt is:
Perform task: TASK: A debug task (N)o/(y)es/(c)ontinue:
Note the capitalised N — the default is no. Pressing return at
every prompt on 2.21.3 executed nothing and produced an empty
PLAY RECAP with no host line at all: a safe default, and also one that
lets you walk past a step you meant to take without noticing. It is an
inspection tool for a playbook you do
not trust and a demonstration tool for a review; it is not something to
use on a hundred hosts, because you will answer the prompt a hundred
times.
Knowledge check
Knowledge check · 4 questions
Q1. A play contains three command tasks. Running it with --check reports changed=0 and skipped=3. What has check mode established about those tasks?
Q2. Which pre-flight option opens SSH connections to every targeted host?
Q3. Which of these will NOT appear in --list-tasks output? Select all that apply.
Q4. Resuming a failed play with --start-at-task is safe as long as you name a task that comes after the one that failed.
Passing score: 75%. Answers are checked in this browser.