You run ansible-playbook site.yml --check to preview changes, and it fails. Check mode (dry run) should let you see what would happen without actually changing anything, but not all tasks support it, and some failures are specific to check mode.

Understanding the Error

Check mode failures appear differently:

bash
fatal: [webserver01]: FAILED! => {"msg": "check mode not supported for this module"}

Or:

bash
fatal: [webserver01]: FAILED! => {"msg": "Cannot run command in check mode"}

Or more subtly - tasks report changes that wouldn't actually happen, or fail when they would succeed in normal mode.

Step 1: Identify Check Mode Incompatible Tasks

Some modules don't support check mode:

bash
ansible-playbook site.yml --check --diff -v

Look for tasks that fail:

bash
TASK [Run script] ***********************************************************
fatal: [webserver01]: FAILED! => {"msg": "check mode not supported for command module"}

Modules that typically don't support check mode: - command / shell - arbitrary commands can't be simulated - script - running scripts can't be previewed - Some custom modules

Mark tasks that don't support check mode:

yaml
- name: Run destructive script
  command: /opt/app/migrate.sh
  check_mode: no  # Always run, even in check mode

Or skip in check mode:

yaml
- name: Run script
  command: /opt/app/migrate.sh
  when: not ansible_check_mode  # Skip in check mode

Step 2: Handle Command/Shell in Check Mode

Command and shell modules need special handling:

```yaml # WRONG - fails in check mode - name: Create directory command: mkdir -p /opt/app

# CORRECT - use file module which supports check - name: Create directory file: path: /opt/app state: directory ```

If you must use command/shell:

```yaml - name: Run script command: /opt/app/setup.sh check_mode: no # Run even in check mode

  • name: Preview script output
  • command: /opt/app/setup.sh --dry-run
  • when: ansible_check_mode
  • `

Step 3: Force Tasks in Check Mode

Some tasks should always run even in check mode:

```yaml - name: Gather facts if missing setup: check_mode: no # Must run to get facts

  • name: Read current state
  • slurp:
  • src: /etc/app/config.yml
  • register: current_config
  • check_mode: no # Must read actual file
  • `

Step 4: Handle Changes Reported in Check Mode

Check mode reports what would change, but estimates can be wrong:

yaml
- name: Copy config
  template:
    src: config.j2
    dest: /etc/app/config.yml

In check mode, this compares template to destination. If destination doesn't exist:

bash
changed: [webserver01]  # Would create new file

But the task might fail in real mode due to permissions.

Check for potential failures:

bash
ansible-playbook site.yml --check --diff

Look for warnings about permissions, missing dependencies, etc.

Step 5: Handle Conditional Tasks in Check Mode

Conditionals using ansible_check_mode behave differently:

```yaml - name: Skip in check mode command: /bin/true when: not ansible_check_mode

  • name: Only in check mode
  • debug:
  • msg: "Running in check mode"
  • when: ansible_check_mode
  • `

Test conditionals work:

bash
ansible-playbook site.yml --check
ansible-playbook site.yml  # Run both modes to compare

Step 6: Fix Diff Output Issues

Diff shows what would change:

bash
ansible-playbook site.yml --check --diff

If diff fails or shows nothing:

yaml
- name: Enable diff for this task
  template:
    src: config.j2
    dest: /etc/app/config.yml
  diff: yes

Some modules don't support diff:

yaml
- name: No diff available
  command: echo "test"
  # diff not supported for command

Step 7: Handle Registered Variables in Check Mode

Variables registered in check mode may have different content:

```yaml - name: Check file stat: path: /etc/app/config.yml register: file_stat

  • name: Use registered variable
  • debug:
  • msg: "{{ file_stat.stat.exists }}"
  • `

In check mode, stat runs normally (it just reads, doesn't change). But:

yaml
- name: Create file
  copy:
    src: config.yml
    dest: /etc/app/config.yml
  register: copy_result

In check mode, copy_result shows what would change, not actual result.

Handle this:

```yaml - name: Use result safely debug: msg: "Changed in check mode: {{ copy_result.changed }}" when: ansible_check_mode

  • name: Use result normally
  • debug:
  • msg: "Actually changed: {{ copy_result.changed }}"
  • when: not ansible_check_mode
  • `

Step 8: Fix Tasks That Check Then Change

Tasks that check state then change can behave oddly in check mode:

```yaml - name: Check if service running command: systemctl is-active nginx register: service_status changed_when: false check_mode: no # Must run actual check

  • name: Start if not running
  • service:
  • name: nginx
  • state: started
  • when: service_status.rc != 0
  • `

The first task must run (check_mode: no) to provide accurate input for the second.

Step 9: Handle Loops in Check Mode

Loops can behave differently in check mode:

yaml
- name: Create multiple files
  file:
    path: "/opt/{{ item }}"
    state: directory
  loop:
    - app1
    - app2

In check mode, all loop items are evaluated:

bash
changed: [webserver01] => (item=app1)
changed: [webserver01] => (item=app2)

If one item would fail, check mode still shows others.

Step 10: Debug Check Mode Behavior

See check mode specifics:

bash
ansible-playbook site.yml --check -vvv

Look for:

bash
check_mode: True
running check

Debug check mode value:

yaml
- name: Debug check mode
  debug:
    var: ansible_check_mode

Step 11: Handle Module-Specific Check Mode

Many modules support check mode differently:

apt/yum modules:

yaml
- name: Install package
  apt:
    name: nginx
    state: present

Check mode queries package database to see if nginx installed.

file module:

yaml
- name: Create file
  file:
    path: /opt/app/file.txt
    state: file

Check mode checks if file exists.

template module:

yaml
- name: Deploy config
  template:
    src: config.j2
    dest: /etc/app/config.yml

Check mode renders template and compares to existing file.

Modules without check mode support need explicit handling:

yaml
- name: Unsupported module
  my_custom_module:
    param: value
  check_mode: no  # Force run

Step 12: Verify Check Mode Accuracy

Compare check mode prediction to actual run:

```bash # First, check mode ansible-playbook site.yml --check --diff > check_output.txt

# Then, actual run ansible-playbook site.yml --diff > actual_output.txt

# Compare diff check_output.txt actual_output.txt ```

Differences indicate tasks where check mode predictions differ from reality.

Quick Verification

Test check mode works:

bash
ansible-playbook site.yml --check

Success:

``` PLAY [all] ***************

TASK [Gathering Facts] *********** ok: [webserver01]

TASK [First task] *************** changed: [webserver01] # Would change in real run

PLAY RECAP *************** webserver01 : ok=2 changed=1 unreachable=0 failed=0 ```

Test with diff:

bash
ansible-playbook site.yml --check --diff

Prevention Best Practices

  1. 1.Test playbooks with check mode before production:
bash
ansible-playbook production.yml --check --diff
  1. 1.Mark check mode incompatible tasks:
yaml
- name: Run migration script
  script: migrate.sh
  check_mode: no
  1. 1.Use modules that support check mode:

```yaml # Prefer - file: path=/opt/app state=directory

# Over - command: mkdir -p /opt/app ```

  1. 1.Handle check mode in conditionals:
yaml
when: not ansible_check_mode or check_mode_allowed
  1. 1.Document check mode behavior:
yaml
# This role supports check mode except for:
# - migrate task (runs always)
# - restart task (skipped in check)

Check mode failures typically come from incompatible modules, missing check mode markers, or tasks that must run to provide data. Use check_mode: no for critical tasks, prefer modules that support check mode, and compare predictions to actual runs.