AnsibleXLIV · Failure Modes and Partial Fleet FailureFailure Modes and Partial Fleet Failure
Is it safe to run it again?
What you'll learn
- Audit a playbook task by task for rerun safety before retrying anything
- Build a rerun target list from the run artefact without relying on retry files
- Explain what a rerun does to a host interrupted part-way through a task
- Use check mode and diff to establish what a rerun believes it must do
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
“Just run it again” is the most common response to a partial fleet failure and it is right often enough to be dangerous.
It is right when every task is idempotent, when the target list is correct, and when nothing was interrupted mid-transaction. Those three conditions are checkable in about fifteen minutes. Running without checking them is not a decision, it is a reflex, and the cases where the reflex is wrong are the expensive ones.
Rerun safety is not a property of the run
It is a property of each task, decided when the task was written.
That is worth being blunt about, because it means the answer at 03:00 is already determined. You are not deciding whether the rerun is safe; you are discovering whether the person who wrote the play made it safe. The only thing you can do at 03:00 is find out, and the only place to find out is the playbook.
Note the state: latest row. It is idempotent in the sense that it
converges, and it is not repeatable in the sense that matters here: a
rerun four hours later may pick up a package published in between, so
the 30 retried hosts end up on a version the original 220 do not have.
That is not a failure — it is a new inconsistency, created by the fix.
Building the rerun target list
You need a --limit file. There is a trap in how people expect to get
one.
$ ansible-config list | grep -A 3 '^RETRY_FILES_ENABLED'RETRY_FILES_ENABLED:
default: false
description: This controls whether a failed Ansible playbook should create a .retry
file.RETRY_FILES_ENABLED defaults to false. The --limit @site.retry
idiom that appears in older material and in a great deal of
Stack Overflow does not work out of the box on 2.21, because the file
is not written.
You can turn it on. It is worth knowing why the default moved: a
.retry file is written next to the playbook, silently, containing a
host list from a previous run. Two runs later it is stale, and
--limit @site.retry then targets a set of hosts chosen by an
incident that happened last Tuesday. Several estates have run a change
against the wrong hosts exactly that way.
log=/var/log/ansible/patch-2026-08-11.log
awk '/failed=[1-9]/ {print $1}' "$log" | sort -u > retarget.txt
awk '/unreachable=1/ {print $1}' "$log" | sort -u >> retarget.txt
sort -u -o retarget.txt retarget.txt
wc -l retarget.txt # expect 80
# Prove the limit resolves to exactly those hosts, before running anything
ansible-playbook -i inventory/prod patch.yml \
--limit @retarget.txt --list-hostsThe --list-hosts step is not optional ceremony. A limit file
containing a hostname the inventory does not know produces a warning and
a smaller host list than you expect, and at 03:00 nobody reads warnings.
Count the hosts it prints and compare to the number you wrote down.
What a rerun does to an interrupted host
This is the case the audit table cannot cover, because the question is not “is this task idempotent” but “is this task idempotent when applied to a host on which it was already half-executed”.
Three shapes recur:
The half-finished package transaction. The rerun does not get as far
as your task; apt or dnf refuses to do anything until the previous
transaction is resolved. This fails loudly and safely, which is the good
outcome — you get an error naming the real problem.
The partially written file. Modules that write via a temp file and
rename (copy, template, assemble) leave either the old file or the
new one, never a half-file, so the rerun simply completes the job. A
shell task using a redirect does not have this property and can leave
a truncated file that looks valid.
The service that was mid-restart. The rerun evaluates a service that
is in a transitional state. Depending on timing, the service module
may report ok for a service that is failing to start, because it asked
at the moment systemd still considered it starting.
$ ansible-playbook -i inventory/prod patch.yml --limit @retarget.txt --check --diffPLAY RECAP *********************************************************************
app-047 : ok=12 changed=0 unreachable=0 failed=0 # already converged
app-112 : ok=8 changed=4 unreachable=0 failed=0 # stopped part-way
app-203 : ok=6 changed=9 unreachable=0 failed=0 # never startedIllustrative output
Three hosts, three changed counts, three different situations — and
none of them were distinguishable in the original recap.
The decision, condensed
- Audit the play, task by task. Any task you cannot classify is a blocker.
- Build the target list explicitly from the run artefact. Verify it
with
--list-hosts. - Establish per-host state with
--check --diffagainst that list. - Resolve stuck resources — package transactions, locks, stale PID files — on the hosts that have them, before the rerun.
- Rerun with
serialand a small first batch, even though the play already ran once. The conditions changed; treat it as a new change.
Step 5 is the one people skip because the play “already worked on 220 hosts”. It worked at 02:06 on a fleet in a state that no longer exists.
Knowledge check
Knowledge check · 4 questions
Q1. What determines whether a failed run is safe to repeat?
Q2. After a failed run on ansible-core 2.21.3, --limit @site.retry is the built-in way to retarget just the hosts that failed.
Q3. You have an 80-host retarget list containing both failed and unreachable hosts. What should you run before the real rerun?
Q4. Which tasks should be flagged as unsafe to repeat during a rerun-safety audit? Select all that apply.
Passing score: 75%. Answers are checked in this browser.