Ansible
task condition
creates module
execution issue
automation troubleshooting

Ansible showing task changed but the task has condition creates and does not actually execute

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

If an Ansible task uses creates and shows changed even though the command did not run, the first thing to check is whether the playbook was executed in check mode. For command and shell, Ansible can predict that a task would change the target when the creates file does not exist, report changed, and still skip the actual command because dry-run mode does not perform changes.

What creates Actually Does

The creates argument is supported by modules such as ansible.builtin.command and ansible.builtin.shell. It is a guard condition: if the file already exists, the task is skipped because Ansible assumes the work has already been done.

yaml
1- name: Build the application only once
2  ansible.builtin.command: /usr/local/bin/build-app
3  args:
4    creates: /opt/myapp/.built

In a normal run:

  • if /opt/myapp/.built exists, the task is skipped or reported as ok
  • if the file does not exist, the command runs and the task reports changed

That behavior is straightforward. The confusing part appears in check mode.

Why Check Mode Reports changed Without Running

According to the Ansible documentation for command, check mode has partial support when creates or removes is present. The module checks the target file and predicts whether the command would have run. If the file is missing, Ansible reports changed because the task would have changed the system in a real run, but it does not execute the command under --check.

This playbook demonstrates the behavior:

yaml
1- hosts: all
2  tasks:
3    - name: Create a marker file through a command
4      ansible.builtin.command: /usr/bin/touch /tmp/example-marker
5      args:
6        creates: /tmp/example-marker

Normal execution:

bash
ansible-playbook playbook.yml

Dry run:

bash
ansible-playbook playbook.yml --check

In the dry run, if /tmp/example-marker does not exist yet, the task is likely to report changed even though the file is not created. That is expected behavior, not a failed guard.

Confirm Whether You Are in Check Mode

This issue often happens in CI pipelines, Ansible Tower, or Automation Controller jobs where check mode is enabled indirectly. It is easy to forget that the playbook is running in dry-run mode when the output still contains change predictions.

You can make the mode obvious in the play itself:

yaml
1- hosts: all
2  tasks:
3    - name: Show whether this run is check mode
4      ansible.builtin.debug:
5        var: ansible_check_mode

If that prints true, then a changed result without execution is completely consistent with the documented behavior.

Force Real Execution or Use a Different Pattern

If you want the command to run even when the playbook is invoked with --check, you can opt that task out of check mode:

yaml
1- name: Always run this command even in check mode
2  ansible.builtin.command: /usr/bin/touch /tmp/example-marker
3  args:
4    creates: /tmp/example-marker
5  check_mode: no

Use that carefully. It breaks the expectation that a dry run does not modify the target system.

Another option is to separate the file check from the command using stat and when, which gives you more control over reporting:

yaml
1- name: Check for marker file
2  ansible.builtin.stat:
3    path: /tmp/example-marker
4  register: marker_file
5
6- name: Create marker file
7  ansible.builtin.command: /usr/bin/touch /tmp/example-marker
8  when: not marker_file.stat.exists

This is more verbose, but it can be easier to reason about in complex playbooks.

Keep Idempotency and Reporting Aligned

The real purpose of creates is idempotency. It is not a promise that the task will always show ok unless it physically ran. In check mode, Ansible is allowed to simulate the result. That simulation is useful because it tells you which tasks would change a system during a real deployment.

Once you understand that, the output stops looking contradictory. The task is not saying "I ran and changed something." It is saying "under a real execution, I would change something because the guard file is absent."

Common Pitfalls

The biggest mistake is reading a changed result in check mode as proof that the command actually executed. Another frequent issue is forgetting that Tower or CI may launch jobs in dry-run mode even when the playbook itself does not mention --check. Some users also expect creates to fully manage reporting semantics for every scenario, even though it only gives the module enough information to make a prediction. Finally, forcing check_mode: no on too many tasks defeats the safety of dry runs and should be used sparingly.

Summary

  • 'creates is a file guard that prevents a command from running if the target file already exists.'
  • In Ansible check mode, command and shell can predict changed without executing the command.
  • A changed result in dry-run mode means "would change," not "did change."
  • Use ansible_check_mode or job settings to confirm whether the run is a simulation.
  • If needed, use check_mode: no or a stat plus when pattern for more explicit behavior.

Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design