Skip to main content
RunBook Academy

AnsibleXXXI · Serial Execution and Failure ToleranceFailure Tolerance

Ending a batch or a play deliberately

Advanced⏱ ~22 minansible-playbook

What you'll learn

  • Choose the correct meta verb for a host, a batch, a play or a role
  • Predict which hosts continue after each of end_batch, end_host and end_play
  • Explain why a meta stop exits zero and what to do about it
  • State what clear_host_errors restores and why that is dangerous

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

Not yet marked complete on this device.

The failure keywords stop a run when Ansible notices something wrong. Sometimes you notice something wrong — a precondition that is not met, a state that means proceeding is pointless, a maintenance window that has closed — and there is no failure for a keyword to react to.

ansible.builtin.meta provides four verbs for stopping on purpose, each with a different reach.

VerbStopsEverything else
end_hostthis host, for the rest of the playcontinues
end_batchthe current batchlater batches still run
end_playthe whole play, all batcheslater plays still run
end_rolethe current rolethe rest of the play continues

end_batch and end_role are the two that need a version note: end_batch requires ansible-core 2.12 or later, and end_role 2.18 or later. This course targets 2.21, so both are available.

The reach of each verb, observed

One playbook, six hosts, serial: 2, with the stop conditioned on h03 — which is the first host of the second batch. Only the verb changes.

Read-only / Safethe test
- name: Which hosts survive the stop
hosts: batchlab
gather_facts: false
serial: 2
tasks:
  - name: Stop deliberately
    ansible.builtin.meta: end_batch
    when: inventory_hostname == 'h03'

  - name: After
    ansible.builtin.debug:
      msg: 'AFTER {{ inventory_hostname }}'
Read-only / Safeexecuted on ansible-core 2.21.3
$ ansible-playbook -i inv6.ini meta.yml
meta: end_batch   AFTER reached by:  h01 h02        h05 h06
meta: end_play    AFTER reached by:  h01 h02
meta: end_host    AFTER reached by:  h01 h02    h04 h05 h06

Read each line against the batch boundaries:

end_batch ended batch two. h04 — the innocent batch-mate — was stopped as well, and then batch three ran normally. This is the verb whose name is most likely to mislead: it does not stop the rollout, it skips the rest of this batch and carries on.

end_play ended everything. h04 stopped, and batches three onwards never ran at all.

end_host stopped only h03. h04 continued to the end of the play, and every later batch ran. This is the surgical one.

Where each one belongs

end_host — this host is not a candidate.

The common case: a host that does not need the change, or cannot take it, and where that is a normal condition rather than an error.

Read-only / Safeskipping a host that is already at the target version
    - name: Read the currently deployed version
    ansible.builtin.slurp:
      src: /etc/app/deployed-version
    register: deployed
    failed_when: false

  - name: Nothing to do on this host
    ansible.builtin.debug:
      msg: 'Already at {{ release_version }}, leaving the play'
    when: (deployed.content | default('') | b64decode | trim) == release_version

  - name: Leave the play for hosts already at the target version
    ansible.builtin.meta: end_host
    when: (deployed.content | default('') | b64decode | trim) == release_version

The alternative — putting the same when: on all fifteen subsequent tasks — is what end_host exists to replace. It is also strictly worse, because the fifteenth when: is the one somebody will forget.

end_play — the run should not continue.

A precondition that applies to the whole change. The maintenance window closed. A dependency is unavailable. A canary batch reported something that means the remaining batches must not proceed.

Read-only / Safestopping the rollout when the window closes
    - name: Stop the rollout if the maintenance window has closed
    ansible.builtin.debug:
      msg: >-
        Maintenance window closed at {{ window_end }}. Stopping before
        batch {{ ansible_play_batch | join(', ') }}. Hosts not yet
        deployed remain on the previous version.
    run_once: true
    when: (ansible_date_time.iso8601 > window_end) | default(false)

  - name: End the play at the window boundary
    ansible.builtin.meta: end_play
    when: (ansible_date_time.iso8601 > window_end) | default(false)

Because a batched play re-executes per batch, a check like this at the top of the task list runs before every batch — so it stops the rollout at a batch boundary rather than mid-batch, which is the cheap place to stop.

ansible_date_time comes from fact gathering, so this needs gather_facts: true or an explicit setup task.

end_role — this role has nothing further to do.

Available from 2.18. It ends the current role for the host and continues with the rest of the play. Useful in a role that establishes early that it is not applicable, without the role having to guard every task.

The problem with all of them

A play ended with meta is a successful run.

Read-only / Safethe exit code of a deliberate stop
$ ansible-playbook -i inv6.ini endplay.yml; echo EXIT=$?
PLAY RECAP *********************************************************************
h01                        : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
h02                        : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

EXIT=0

Nothing in that output says the rollout stopped early. failed=0, exit 0, and four of the six hosts have no recap line at all — not even h03, the host that triggered the stop, or h04 beside it. The recap reports hosts that completed work, so a run that stopped after batch one is textually identical to a successful run against a two-host fleet.

clear_host_errors

The counterpart verb: instead of removing hosts, it puts them back.

Upstream names it as the way to reactivate hosts that were marked unreachable — it restores failed and unreachable hosts to the active list so that subsequent tasks run against them again.

There is one defensible use. A play that deliberately disconnects hosts — a network reconfiguration, a reboot performed outside the reboot module — expects hosts to drop out and expects them back. Clearing the errors after they return is repair.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A play with serial: 2 over six hosts hits meta: end_batch on h03, the first host of batch two. Which hosts run the tasks that follow?

  2. Q2. Which meta verb stops one host while letting its batch-mates and all later batches continue?

  3. Q3. A rolling play ends early via meta: end_play. What is true of the run? Select all that apply.

  4. Q4. Adding meta: clear_host_errors after a batch of failures is a reasonable way to let a rolling deploy finish covering the fleet.

Passing score: 75%. Answers are checked in this browser.