Introduction

Ansible handlers only run when a notifying task ends in the changed state. When a task prints useful output but still reports ok, the handler is skipped and the service restart or reload never happens. The underlying problem is usually not the handler itself. It is the task's change detection logic.

Symptoms

  • The handler definition is correct, but it never runs
  • A configuration file changes on disk, yet the service is not restarted
  • Shell or command tasks appear to succeed without triggering the expected reload
  • Running the playbook with -vv shows ok where you expected changed

Common Causes

  • The notifying task uses command or shell without a proper changed_when
  • A validation or probe task has notify, even though it never changes managed state
  • The wrong Ansible module was used instead of template, copy, or lineinfile
  • The handler name matches, but the task never actually enters the changed state

Step-by-Step Fix

  1. 1.**Check whether the notifying task reports changed**
  2. 2.Run the playbook with enough verbosity to inspect the task result instead of staring only at the handler block.
bash
ansible-playbook site.yml -vv
  1. 1.Use a state-aware module whenever possible
  2. 2.Modules like template, copy, and lineinfile already know whether they changed the remote file, which makes handler notification reliable.
yaml
- name: Render application config
  template:
    src: app.conf.j2
    dest: /etc/app/app.conf
  notify: Restart app
  1. 1.**Add accurate changed_when logic for command-based tasks**
  2. 2.If you must use command or shell, define change detection from real output instead of forcing changed_when: true on every run.
yaml
- name: Enable the feature flag
  command: /usr/local/bin/appctl enable feature-x
  register: feature_enable
  changed_when: "'already enabled' not in feature_enable.stdout"
  notify: Restart app
  1. 1.Flush handlers deliberately when later tasks depend on the restart
  2. 2.If a follow-up task needs the handler side effect in the same play, force handler execution at the correct boundary.
yaml
- meta: flush_handlers

Prevention

  • Prefer idempotent file and package modules over shell and command
  • Treat changed_when: true as a last resort, not a default pattern
  • Review handler-triggering tasks with -vv during playbook debugging
  • Keep validation tasks separate from state-changing tasks so notify semantics stay clear