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.
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.
In a normal run:
- if
/opt/myapp/.builtexists, the task is skipped or reported asok - 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:
Normal execution:
Dry run:
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:
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:
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:
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
- '
createsis a file guard that prevents a command from running if the target file already exists.' - In Ansible check mode,
commandandshellcan predictchangedwithout executing the command. - A
changedresult in dry-run mode means "would change," not "did change." - Use
ansible_check_modeor job settings to confirm whether the run is a simulation. - If needed, use
check_mode: noor astatpluswhenpattern for more explicit behavior.
Related reading
- ansible variables shared with multiple groups if inventory is only localhost
- Apache kafka production cluster setup problems
- Apache Storm java.nio.channels.ClosedChannelException null
- Apache ZooKeeper with out a client
- ant warning 'includeantruntime' was not set
- Apache Beam pipeline running on Dataflow failed to read from KafkaIO SSL handshake failed
- AppFabric cache configuration and hosts
- Application Load Balancers vs API Gateway

System Design Fundamentals
Build a strong foundation in designing scalable, reliable distributed systems.
View the courseSystem Design practice on Codemia
Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.