What Happens When a Task Fails

Scenario: A task fails on web02. The remaining tasks skip on web02 but continue on web01, and a handler that both hosts had notified never runs anywhere.

New words, in plain English

By default a failure removes only that host from the rest of the play. Other hosts carry on. This is usually right for large fleets - one bad machine should not block a fleet-wide fix - and occasionally catastrophic, because a partial rollout can leave an estate in a state nobody designed.

Two controls change that. any_errors_fatal: true stops the entire play at the first failure on any host. max_fail_percentage: 30 allows a proportion of hosts to fail before aborting, which pairs naturally with batched rolling updates.

The handler behaviour surprises people: handlers notified during a play do not run if the play fails before reaching the handler-flush point. That is a deliberate safety default - you usually do not want to restart a service after a half-finished configuration change - but it means a genuinely needed restart can be silently skipped. force_handlers: true overrides it when the restart is the safe option.

Analogy: A failed host is a delivery driver who breaks down. By default the rest of the fleet continues. any_errors_fatal recalls everyone the moment one van fails. max_fail_percentage says 'carry on unless we lose more than a third of the fleet'.

A worked example

- name: Deploy to the web tier
  hosts: webservers
  serial: 1                    # one host at a time
  max_fail_percentage: 0       # any failure aborts the rollout
  force_handlers: true         # but still flush pending handlers
  tasks:
    - name: Push the config
      ansible.builtin.template:
        src: app.conf.j2
        dest: /etc/app/app.conf
      notify: restart app

  handlers:
    - name: restart app
      ansible.builtin.service:
        name: app
        state: restarted

Choose deliberately rather than by default. For a config push across 500 hosts, letting a few fail and reporting them is correct. For a coordinated release where hosts must agree on a schema version, a partial rollout is worse than no rollout, and any_errors_fatal is the honest choice.

Phase 4 covers the finer-grained tool - block/rescue/always - which lets one section of a play fail, be handled, and clean up after itself, without taking the host out of the rest of the run.

Warning: Handlers do not run if the play fails first. A configuration change that landed but never triggered its restart is a genuinely dangerous half-state: the file says one thing, the running process another.
Goal: Put this to work in the ansible-idempotent-modules lab. Open /labs/ansible, pick ansible-idempotent-modules, and fix the real broken project - Ansible really does SSH into four managed hosts and converge them.