The Problem

Your playbook uses include_tasks to dynamically include task files, but it fails:

bash
ERROR! Could not locate file in lookup: tasks/subtasks.yml

Or:

bash
fatal: [server]: FAILED! => {"msg": "Variables not available in included task: 'my_var' is undefined"}

Or:

bash
ERROR! A variable included in 'include_tasks' was undefined

Why This Happens

include_tasks errors stem from:

Task file not found - Wrong path or missing file.

Variable not passed - Variables from playbook not available in included file.

Dynamic include issues - Variable in filename undefined or invalid.

Conditional include problems - when clause evaluated incorrectly.

Loop include confusion - Variables in loops behave differently.

Understanding include_tasks

include_tasks dynamically includes a task file at runtime:

```yaml # main.yml - hosts: webservers tasks: - name: Include tasks include_tasks: tasks/setup.yml

# tasks/setup.yml - name: Setup task 1 debug: msg: "Running setup" ```

Unlike import_tasks, include_tasks is evaluated at runtime, allowing dynamic behavior.

Diagnosing the Issue

Check file path exists:

bash
ls -la tasks/setup.yml
cat tasks/setup.yml

Debug include path:

yaml
- name: Debug include path
  debug:
    msg: "Including: {{ tasks_file }}"
  include_tasks: "{{ tasks_file }}"

Run with verbosity:

bash
ansible-playbook playbook.yml -vv

The Fix

Fix 1: Verify Task File Path

The task file must exist relative to the playbook:

yaml
# playbook.yml in /ansible/
- hosts: all
  tasks:
    - include_tasks: tasks/setup.yml  # File must be at /ansible/tasks/setup.yml

Use absolute paths for reliability:

yaml
- include_tasks: "{{ playbook_dir }}/tasks/setup.yml"

Check file location:

bash
# From playbook directory
find . -name "*.yml" -path "*/tasks/*"

Fix 2: Pass Variables to Included Tasks

Variables from the play are available, but explicit passing helps:

yaml
# WRONG - expecting vars that don't exist
- hosts: webservers
  tasks:
    - include_tasks: tasks/configure.yml
      vars:
        app_name: "{{ app_name }}"  # Error if app_name undefined in play
yaml
# CORRECT - define vars or use defaults
- hosts: webservers
  vars:
    app_name: myapp
  tasks:
    - include_tasks: tasks/configure.yml

Pass specific vars:

yaml
- include_tasks: tasks/configure.yml
  vars:
    config_path: /etc/app
    app_mode: production

Fix 3: Handle Dynamic Includes

Using variables in filename:

```yaml # WRONG - variable may be undefined - include_tasks: "tasks/{{ task_type }}.yml"

# CORRECT - with validation - name: Include dynamic tasks include_tasks: "tasks/{{ task_type | default('default') }}.yml" when: task_type is defined ```

Verify file exists before including:

```yaml - name: Check task file exists stat: path: "{{ playbook_dir }}/tasks/{{ task_type }}.yml" delegate_to: localhost connection: local register: task_file

  • name: Include if exists
  • include_tasks: "tasks/{{ task_type }}.yml"
  • when: task_file.stat.exists
  • `

Fix 4: Conditional Include Tasks

Apply conditions to include:

yaml
# Condition applies to included tasks
- include_tasks: tasks/production.yml
  when: env == 'production'
  # All tasks in production.yml inherit this condition

Or apply to the include statement:

yaml
- name: Include based on condition
  include_tasks: tasks/{{ env }}.yml
  when: env in ['production', 'staging']

Fix 5: Include Tasks in a Loop

Loop over includes with apply:

yaml
- name: Include multiple task files
  include_tasks: "tasks/{{ item }}.yml"
  loop:
    - setup
    - configure
    - deploy
  loop_control:
    loop_var: task_name

Pass loop variable to included file:

yaml
- include_tasks: tasks/process.yml
  vars:
    current_item: "{{ item }}"
  loop: "{{ items }}"

Fix 6: Include vs Import Difference

Key differences between include_tasks and import_tasks:

```yaml # include_tasks - runtime evaluation - include_tasks: "{{ dynamic_file }}.yml" # Works, evaluated at runtime when: some_condition # Condition applies to all included tasks loop: "{{ items }}" # Can loop over includes

# import_tasks - parse-time evaluation - import_tasks: tasks/static.yml # Must be static filename # Cannot use variables in filename # Cannot loop over imports ```

Use include_tasks when: - Filename is dynamic - You need conditional includes - You want to loop over task files

Use import_tasks when: - Performance matters (faster) - You need task-level conditions - Filename is static

Fix 7: Handle Variable Scope in Includes

Variables behave differently in includes:

```yaml # main.yml - hosts: webservers vars: global_var: value tasks: - set_fact: host_var: "{{ inventory_hostname }}"

  • include_tasks: tasks/sub.yml
  • vars:
  • include_var: passed_value

# tasks/sub.yml - debug: msg: "global_var: {{ global_var }}" # Available (play vars) - debug: msg: "host_var: {{ host_var }}" # Available (set_fact) - debug: msg: "include_var: {{ include_var }}" # Available (passed vars) ```

Fix 8: Return Values from Included Tasks

Register results from includes:

```yaml - include_tasks: tasks/process.yml register: process_results

  • debug:
  • msg: "Result: {{ process_results }}"
  • `

Access included task results:

```yaml # tasks/process.yml - name: Process item command: /opt/process.sh register: item_result

# In main playbook - debug: msg: "Last output: {{ process_results.item_result.stdout }}" ```

Verifying the Fix

Test include_tasks:

```yaml # test_include.yml - hosts: localhost gather_facts: no vars: test_var: hello tasks: - include_tasks: test_sub.yml vars: passed_var: world

# test_sub.yml (create this file) - name: Test task debug: msg: "{{ test_var }} {{ passed_var }}" ```

Run:

bash
ansible-playbook test_include.yml -v

Expected: `` TASK [Test task] *********************************************************** ok: [localhost] => {"msg": "hello world"}

Prevention

Validate include paths:

```yaml - name: Pre-flight check block: - name: Verify task files exist stat: path: "{{ playbook_dir }}/tasks/{{ item }}.yml" delegate_to: localhost connection: local loop: - setup - configure register: file_check

  • name: Validate all files exist
  • assert:
  • that:
  • - file_check.results | selectattr('stat.exists', 'equalto', true) | list | length == file_check.results | length
  • fail_msg: "Some task files not found"
  • `