Why command Breaks Idempotency
The trap of raw shell and how to avoid it.
Why command Breaks Idempotency is a free Ansible Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Ansible Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
command Always Runs
The command module just runs whatever you give it on the host. It has no idea what 'done' looks like, so it runs every single time.
ansible.builtin.command: useradd deployAlways changed
Because command cannot inspect state, Ansible reports it as changed on every run, even when the command actually did nothing new.
changed: [web1]Re-running Can Hurt
Worse, some commands fail or duplicate work on a repeat run, like useradd erroring because the user already exists.
Prefer a State-Based Module
For users, reach for the user module instead. It checks if the account exists and only creates it when missing, staying idempotent.
ansible.builtin.user:
name: deploy
state: presentThere Is Usually a Module
Most common shell tasks have a dedicated module: file, copy, lineinfile, git. Use them so Ansible can compare and skip when correct.
Guard command with creates
If you must use command, add creates. Ansible skips the task when that path already exists, restoring idempotency.
ansible.builtin.command: ./build.sh
args:
creates: /opt/app/builtOr Guard with removes
The mirror of creates is removes: the command runs only if the named path still exists, useful for cleanup steps.
ansible.builtin.command: rm /tmp/lock
args:
removes: /tmp/lockGate command with when
You can also wrap a command in a when condition driven by a registered check, so it runs only when truly needed.
Tell Ansible It Did Nothing
Set changed_when: false on a read-only command so Ansible stops reporting it as changed on every run.
ansible.builtin.command: cat /etc/hostname
changed_when: falseshell Has the Same Problem
The shell module shares this flaw. It runs through a shell, so it too is non-idempotent unless you guard it the same way.
Treat Raw Commands as a Last Resort
Raw command is an escape hatch, not a default. Each one is a place idempotency can quietly break, so reach for it only when no module fits.
Quick Check
You used command to run a build script and it shows changed on every single run.
Recap
The command and shell modules always run and always report changed. Prefer real modules, or guard them with creates, removes, or changed_when. 🛡️
Frequently asked questions
Is the “Why command Breaks Idempotency” lesson free?
Yes — the full text of “Why command Breaks Idempotency” is free to read here on the web, and the Ansible Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Ansible Academy course, upgrade to CoddyKit PRO.
What will I learn in “Why command Breaks Idempotency”?
The trap of raw shell and how to avoid it. You practise Ansible Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Ansible Academy?
No prior experience is required. Ansible Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Why command Breaks Idempotency” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Ansible Academy lesson?
Yes. Every Ansible Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Desired State, Not Step-by-Step Scripts
- Reading changed vs ok in Output
- Why command Breaks Idempotency
- Check Mode: Dry-Run with --check