Skip to content

Ansible Result Tests


Every test so far has checked a piece of data — a variable’s type, its value. This chapter checks something different: what actually happened when a previous task ran. Paired with register, this is arguably the single most useful pattern in this entire section.

register Recap

From the variables section: attach register: name to a task, and its entire result — including whether it changed anything, succeeded, or failed — becomes available under that name for the rest of the play.

is changed

copy, recall from the idempotency discussion back in the playbook chapter, only reports changed when it genuinely wrote something different:

changed_demo.yaml
- name: Result Tests Demo
  hosts: servers
  tasks:
    - name: Copy config file
      ansible.builtin.copy:
        src: app.conf
        dest: /etc/app.conf
      register: copy_result

    - name: Report if it changed
      ansible.builtin.debug:
        msg: "The config file was updated"
      when: copy_result is changed

Run this once, and the report prints — the file didn’t exist there before. Run it again, unchanged, and the report is silently skipped, because copy_result is changed is now false. This is the pattern real playbooks use constantly: “only announce something (or act on it) if it actually happened,” rather than unconditionally after every single run.

Note

This only means something for a genuinely idempotent module like copy. Recall from the idempotency chapter that command/shell report changed on every run, regardless of whether anything really changed — checking is changed against a command task’s result is essentially meaningless, since it’s always true.

is succeeded And is failed

Checking whether a task succeeded or failed requires one piece of setup first: by default, a failed task stops the entire play for that host immediately, which means a later task checking is failed would never even run. ignore_errors: true is what lets execution continue past a failure so a later task can actually inspect it:

- name: Try something that might fail
  ansible.builtin.command: /bin/false
  register: attempt_result
  ignore_errors: true

- name: Report the failure
  ansible.builtin.debug:
    msg: "The command failed, but we're continuing anyway"
  when: attempt_result is failed
FAILED - RETRYING
ok: [ubuntu] => {
    "msg": "The command failed, but we're continuing anyway"
}

Without ignore_errors: true on the first task, this playbook would simply stop at that failure — the second task, and its is failed check, would never be reached at all.

is skipped

Checks whether a previous task was skipped by its own when: condition — useful when a later task’s behavior needs to depend on whether an earlier conditional one actually ran:

- name: Maybe run something
  ansible.builtin.debug:
    msg: "Conditional task"
  when: some_condition
  register: maybe_result

- name: React to it being skipped
  ansible.builtin.debug:
    msg: "The previous task was skipped"
  when: maybe_result is skipped

Best Practices

  • Only rely on is changed for genuinely idempotent modulescopy, and others like it — never for command/shell, whose changed status doesn’t reflect anything real.
  • Pair ignore_errors: true with any task you plan to check is failed/is succeeded against — without it, the check will never be reached, since the play stops at the failure first.
  • Use is skipped when later logic genuinely depends on whether an earlier conditional task ran — it’s a real, distinct outcome from both success and failure, worth checking explicitly rather than assuming.
Last updated on