Skip to main content
RunBook Academy

AnsibleXVI · HandlersHandlers

Naming handlers, and role collisions

Advanced⏱ ~19 minansible-playbook

What you'll learn

  • Explain why a handler name cannot contain a host-specific variable
  • Read the warning Ansible emits when a handler name fails to template
  • Predict what happens when two roles define the same handler name
  • Design handler names and topics that survive a role being added to a play

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.

Handler names look like ordinary task names. They are not: they are lookup keys, and they are resolved when the play is assembled — before any host has been contacted and before host-specific variables exist.

That single implementation fact produces two distinct production failures. The first is a handler that vanishes when you add a variable to its name. The second is two roles quietly sharing one handler.

Names are resolved early, so host variables are not available

A play-level variable in a handler name works, because it is known at assembly time:

Read-only / Safea play var in a handler name resolves
$ ansible-playbook -i inv.ini hname.yml
TASK [Notify a templated handler name] *****************************************
changed: [localhost]

RUNNING HANDLER [Restart nginx] ************************************************
ok: [localhost]

Move the same variable into the inventory, so each host has its own value, and the handler stops existing:

Read-only / Safethe inventory that breaks it
[web]
alpha.example.com ansible_connection=local svc=nginx
beta.example.com  ansible_connection=local svc=haproxy
Read-only / Safethe same play against per-host variables
$ ansible-playbook -i inv2.ini hname2.yml
[WARNING]: Handler 'Restart {{ svc }}' is unusable because it has no listen
topics and the name could not be templated (host-specific variables are not
supported in handler names). The error: 'svc' is undefined

[ERROR]: The requested handler 'Restart nginx' was not found in either the main
handlers list nor in the listening handlers list

Read the warning carefully, because it is unusually informative and it explains the whole design:

  • “host-specific variables are not supported in handler names” — stated outright. Resolution happens once, for the play, not once per host.
  • “it has no listen topics” — a handler whose name fails to template is still reachable through a listen topic, because topics are static strings. The handler is not discarded; it is only unreachable by name.
  • “The error: ‘svc’ is undefined” — at assembly time, svc does not exist, because host variables have not been resolved for any host yet.

Note also that the run got as far as printing Restart nginx in the error. The notify on the task was templated, per host, successfully. The handler name was not. The two sides of the lookup are resolved at different times, which is exactly why they fail to meet.

Two roles, one handler name

Handlers from roles are added to the play’s handler list. There is no per-role namespace, no prefixing, and no collision detection.

So a play that includes a webserver role and a monitoring role, both of which define a handler called Restart the service — a plausible name for either — has a problem.

Read-only / Safethe collision, reproduced in one play
- hosts: local
gather_facts: false
tasks:
  - name: Notify the colliding name
    ansible.builtin.debug:
      msg: t
    changed_when: true
    notify: Restart the service

handlers:
  - name: Restart the service
    ansible.builtin.debug:
      msg: FIRST DEFINITION ran

  - name: Restart the service
    ansible.builtin.debug:
      msg: SECOND DEFINITION ran
Read-only / Safeexecuted on ansible-core 2.21.3
$ ansible-playbook -i inv.ini dup.yml
TASK [Notify the colliding name] ***********************************************
changed: [localhost]

RUNNING HANDLER [Restart the service] ******************************************
ok: [localhost] => {
  "msg": "FIRST DEFINITION ran"
}

PLAY RECAP *********************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

One handler ran. The other did not, and nothing said so.

Now map that onto the roles. The monitoring role’s task changes its configuration and notifies Restart the service, intending its own handler. What actually happens depends on which role was listed first in the play. If webserver came first, the monitoring change restarts nginx, and the monitoring agent is never restarted at all.

Naming that survives composition

Three rules, in order of how much they buy you.

1. Name the handler for its own service. Restart the service is a name that collides with every other role in the repository. Restart the monitoring agent does not. This alone prevents most collisions, costs nothing, and makes the run log readable.

2. Publish a listen topic and let external callers use it. A role should be notified through a topic it declares, not through a handler name it happens to have. That is the contract argument from the previous lesson, and it also removes the collision risk entirely, because two roles subscribing to two differently-named topics never meet.

3. Read the assembled handler list when composing a play. For a play built from roles you did not all write, the handler namespace is a property of the play, not of any role. There is no command that prints it directly, but the roles’ handlers/main.yml files are short and reading them is a two-minute job that a composition bug costs an afternoon.

Read-only / Safea quick survey of handler names in a repository
grep -rhE '^[[:space:]]*- name:' roles/*/handlers/ \
| sed 's/^[[:space:]]*- name:[[:space:]]*//' \
| sort | uniq -c | sort -rn | head -20

Any count above 1 in that output is a name defined in two roles. It is not automatically a bug — the roles may never appear in the same play — but it is the list worth checking against the plays you actually run.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A handler is named "Restart {{ svc }}" and svc is set per host in the inventory. What happens?

  2. Q2. A play includes a webserver role and a monitoring role. Both define a handler named "Restart the service". The monitoring role changes its config and notifies that name. What happens?

  3. Q3. Which of these correctly avoid the templated-handler-name problem? Select all that apply.

  4. Q4. When two handlers share a name, Ansible emits a warning so the collision is visible in the run output.

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