Why Ansible register Variable Is Undefined in the Next Task

You registered a variable in one task, and the next task fails with "msg": "The task includes an option with an undefined variable". The variable is right there — you can see it in the task above. What’s going on?

This is one of the most common Ansible debugging rabbit holes. There are four distinct causes, and they look identical until you know what to look for.

The Setup

Say you have this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
- name: Check if config file exists
  stat:
    path: /etc/myapp/config.yml
  register: config_stat

- name: Deploy config if missing
  template:
    src: config.yml.j2
    dest: /etc/myapp/config.yml
  when: not config_stat.stat.exists

You run it and get:

fT'ahcteoanlef:rirg[o_wrsetbwa0at1s']::iesFrAruIonLrdEeDwf!hiin=le>ed"{e}"vmaslgu"a:ti"nTghecocnodnidtiitoinoanlal(ncohtecckon'fniogt_sctoantf.isgt_astt.aetx.issttast).:exists'failed.

Cause 1: Task Was Skipped, Not Run

The most common cause. If the registering task is skipped (via when: or a failed block:), Ansible never populates the variable. A skipped task doesn’t set the register variable — it leaves it undefined entirely.

1
2
3
4
5
6
7
8
- name: Only run on RedHat
  command: rpm -qa | grep myapp
  register: rpm_check
  when: ansible_os_family == "RedHat"   # skipped on Ubuntu

- name: Use result
  debug:
    msg: "{{ rpm_check.stdout }}"       # FAILS on Ubuntu — rpm_check never set

Fix: Use default() to guard against undefined:

1
2
3
4
- name: Use result
  debug:
    msg: "{{ rpm_check.stdout | default('not checked') }}"
  when: rpm_check is defined

Or initialize the variable before the conditional task:

1
2
3
4
5
6
7
8
- name: Init rpm_check
  set_fact:
    rpm_check: { stdout: '', rc: 1 }

- name: Only run on RedHat
  command: rpm -qa | grep myapp
  register: rpm_check
  when: ansible_os_family == "RedHat"

Cause 2: Variable Scope in include_tasks

Variables registered inside include_tasks do not automatically propagate back to the parent play in all Ansible versions.

1
2
3
4
5
6
# main.yml
- include_tasks: check_service.yml

- name: Use registered var from included file
  debug:
    msg: "{{ service_status.stdout }}"   # May be undefined
1
2
3
4
# check_service.yml
- name: Check service
  command: systemctl is-active nginx
  register: service_status              # Only lives in include scope

Fix: Use set_fact inside the included file to explicitly promote the variable:

1
2
3
4
5
6
7
8
# check_service.yml
- name: Check service
  command: systemctl is-active nginx
  register: _service_status_raw

- name: Expose result to parent
  set_fact:
    service_status: "{{ _service_status_raw }}"

Or switch from include_tasks to import_tasks — imported tasks share the parent scope by default.

Cause 3: ignore_errors Hides the Real Problem

When a task fails and you’ve set ignore_errors: true, Ansible still registers the variable — but the structure is different from a successful run. If your next task assumes stdout exists, it will fail when the command errored out.

1
2
3
4
5
6
7
8
- name: Get app version
  command: /opt/myapp/bin/myapp --version
  register: app_version
  ignore_errors: true

- name: Print version
  debug:
    msg: "Version: {{ app_version.stdout }}"   # Fails if myapp doesn't exist

Fix: Check rc or failed before accessing output:

1
2
3
4
5
6
7
8
9
- name: Print version
  debug:
    msg: "Version: {{ app_version.stdout }}"
  when: app_version.rc == 0

# Or use default:
- name: Print version
  debug:
    msg: "Version: {{ app_version.stdout | default('unknown') }}"

Cause 4: Hosts That Never Ran the Task

In a multi-host play, if a host failed an earlier task and any_errors_fatal or max_fail_percentage caused it to be removed from the batch, that host never ran the registering task. If a later task runs on all hosts (or uses delegate_to), the variable is undefined on the removed host.

1
2
3
4
5
6
7
- name: Get token
  uri:
    url: https://auth.internal/token
  register: auth_token           # Host B fails here and is removed

- name: Use token on all hosts
  command: deploy.sh --token {{ auth_token.json.token }}  # Undefined on host B

Fix: Guard with hostvars or when: auth_token is defined.

Quick Diagnostic Checklist

When you hit an undefined register variable, check these in order:

  1. Was the registering task skipped? Add - debug: var=your_var immediately after to confirm it’s set.
  2. Is the task inside include_tasks? Try import_tasks or use set_fact to promote the variable.
  3. Did the task fail with ignore_errors? Check rc before accessing stdout.
  4. Multi-host play with failures? Use when: varname is defined defensively.
1
2
3
4
# Add this temporarily to see exactly what got registered:
- debug:
    var: your_register_var
    verbosity: 1

Running with -v will then print the full variable structure only when it matters, without cluttering normal runs.

The underlying principle: Ansible registers variables lazily. If the task didn’t execute — for any reason — the variable doesn’t exist. default() and is defined are your safety nets.