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.
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 hostCommon 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.
- 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
- Replace
shell/commandwith a purpose-built module (copy,template,service,package) that reports change accurately. - Make templated content deterministic so
templatedoes not rewrite the file each run. - Run the playbook twice in CI and assert
changed=0on the second pass.
How to prevent it
- Use idempotent modules instead of raw
command/shellwhere possible. - Add
changed_when/creates/removesto any unavoidable ad-hoc command. - Gate merges on a double-run idempotency check (
changed=0on the second run).