DevOpsInterviewPrep logo
Infrastructure as Code & Configuration / 05
mediumNewRed HatIBMInfosys

Your Ansible playbook reports changed on every run. Why is that a bug, and how do you fix it?

The question that finds out whether someone writes playbooks or writes shell scripts wrapped in YAML. Idempotence is a property of the module you chose, not of Ansible.

Updated Sep 2026 · Grounded in researched DevOps, SRE and platform engineering interview loops, written to a senior-engineer editorial bar, and never padded to hit a word count.

TL;DR: Many Ansible modules support idempotent operations; verify the selected action. command and shell cannot check, so they always report changed unless you tell them how to know. A playbook that reports changed every run has lost its ability to tell you when something actually drifted, which is the entire value of running it repeatedly.

How to approach it

Say why always-changed is a problem before saying how to fix it. The problem is signal loss, not noise, and that framing is what makes this a real answer.

A strong answer

An idempotent module inspects current state, acts only if it differs from the declared state, and reports changed only when it did something. package checks whether the package is installed. copy compares a checksum. service checks whether the unit is running and enabled. Run the playbook twice against a converged host and the second run should report ok for everything and changed: 0.

That zero is the point. Once it holds, a non-zero changed count on a scheduled run means something drifted, and the playbook has become a drift detector as well as a configurator. A playbook that always reports changed cannot tell you that, so you have lost the signal and nobody reads the output any more.

The cause is almost always command or shell. They execute something and have no way to know whether it needed executing, so they report changed unconditionally. Three fixes, in order of preference:

Use the right module. Most of what people write as shell has a module: package, service, file, lineinfile, template, user, mount, unarchive. Check the module's documented idempotence and check-mode support. Existence of a module guarantees neither.

Give the command a guard. creates skips the task if a path already exists; removes is the inverse. This turns a one-shot command into something safe to re-run:

- name: Extract the release bundle
  ansible.builtin.command: tar -xzf /tmp/app.tgz -C /opt/app
  args:
    creates: /opt/app/bin/server

Declare how to judge the result. changed_when changes reporting, not whether an operation executes or converges. With a safely repeatable command, changed_when and failed_when let you interpret output, so a command that reports nothing to do is recorded as ok rather than as a change:

- name: Reload configuration
  ansible.builtin.command: appctl reload
  register: reload
  changed_when: "'no changes' not in reload.stdout"

The other half worth having: --check runs a playbook in dry-run mode and --diff shows what would change, and both only work if your tasks are honest about change. A playbook full of unguarded shell tasks cannot be dry-run, which removes your ability to see what a run would do before it does it.

On structure: roles exist so that this discipline is reusable. A role bundles tasks, handlers, templates, files, defaults and variables in a fixed layout, so it can be versioned and shared. The important convention is defaults for values a consumer may override and vars for higher-precedence role settings. They are not immutable: extra variables can override them, since the precedence difference is the most common source of confusion when a role behaves differently than expected.

What interviewers probe next

"What is a handler and why not just restart the service?" A handler normally runs once per notified flush cycle; meta: flush_handlers can run it earlier, and a later notification can run it again, so ten config changes cause one restart instead of ten.

"How is this different from Terraform's idempotence?" Terraform compares against recorded state and can therefore destroy what you removed. Ansible compares against the machine and cannot, so removing a task does not undo it.

"How do you test a role?" Molecule, which converges the role in a container and then runs it again asserting zero changes. That second run is the idempotence test.

Common mistakes

Treating always-changed as cosmetic. It is the loss of the only signal that repeated runs give you.

Reaching for shell when a module exists, which also breaks check mode.

Assuming Ansible is idempotent by nature rather than by module choice.

Sources: Ansible check-mode support.

That one was free, and so are 10 answers per topic without an account. Signing in doubles that to 20, keeps your bookmarks, and tracks which topics you keep getting wrong.one Google click · no card · nothing to cancel
HOW DID IT GO?
0
UP NEXT ON YOUR JOURNEY
DISCUSSION · 0

Nothing here yet. Say how you would answer it.