Skip to content
Latchkey

Ansible "changed" in --check Mode - Fix Non-Idempotent Tasks

A task reports changed on every run - including --check mode - so an idempotency gate that expects a converged "no changes" run fails. Usually a command/shell task Ansible cannot reason about.

What this error means

A second ansible-playbook --check (or a CI idempotency assertion) still shows changed=N instead of changed=0. It is deterministic: the offending task lacks the metadata for Ansible to know it made no change.

ansible output
PLAY RECAP   # second run, expected changed=0
app01  : ok=6  changed=2  unreachable=0  failed=0
Idempotency check failed: tasks reported changes on a converged host

Common causes

command/shell with no change signal

The command/shell modules always report changed unless you tell Ansible otherwise. Without changed_when, creates, or removes, they look like a change every time.

A task that mutates state unconditionally

Writing a file with a timestamp, restarting a service every run, or templating non-deterministic content makes the task genuinely change something each run.

How to fix it

Add changed_when / creates to ad-hoc commands

Tell Ansible when the command actually changed something, or guard it with creates so it skips when already done.

playbook.yml
- name: Initialize the database
  ansible.builtin.command: /opt/init-db.sh
  args:
    creates: /var/lib/app/.initialized
  # or: changed_when: "'created' in result.stdout"

Prefer idempotent modules over shell

  1. Replace shell/command with a purpose-built module (copy, template, service, package) that reports change accurately.
  2. Make templated content deterministic so template does not rewrite the file each run.
  3. Run the playbook twice in CI and assert changed=0 on the second pass.

How to prevent it

  • Use idempotent modules instead of raw command/shell where possible.
  • Add changed_when/creates/removes to any unavoidable ad-hoc command.
  • Gate merges on a double-run idempotency check (changed=0 on the second run).

Frequently asked questions

What causes "reports changed every run"?
The command/shell modules always report changed unless you tell Ansible otherwise. Without changed_when, creates, or removes, they look like a change every time.
How do I fix reports changed every run?
Tell Ansible when the command actually changed something, or guard it with creates so it skips when already done.

Related guides

References

Latchkey auto-heals failures like this one - detected, fixed, and retried without you. Start free → 30-day trial · No credit card