The Problem
Your playbook uses include_tasks to dynamically include task files, but it fails:
ERROR! Could not locate file in lookup: tasks/subtasks.ymlOr:
fatal: [server]: FAILED! => {"msg": "Variables not available in included task: 'my_var' is undefined"}Or:
ERROR! A variable included in 'include_tasks' was undefinedWhy 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:
ls -la tasks/setup.yml
cat tasks/setup.ymlDebug include path:
- name: Debug include path
debug:
msg: "Including: {{ tasks_file }}"
include_tasks: "{{ tasks_file }}"Run with verbosity:
ansible-playbook playbook.yml -vvThe Fix
Fix 1: Verify Task File Path
The task file must exist relative to the playbook:
# playbook.yml in /ansible/
- hosts: all
tasks:
- include_tasks: tasks/setup.yml # File must be at /ansible/tasks/setup.ymlUse absolute paths for reliability:
- include_tasks: "{{ playbook_dir }}/tasks/setup.yml"Check file location:
# 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:
# 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# CORRECT - define vars or use defaults
- hosts: webservers
vars:
app_name: myapp
tasks:
- include_tasks: tasks/configure.ymlPass specific vars:
- include_tasks: tasks/configure.yml
vars:
config_path: /etc/app
app_mode: productionFix 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:
# Condition applies to included tasks
- include_tasks: tasks/production.yml
when: env == 'production'
# All tasks in production.yml inherit this conditionOr apply to the include statement:
- 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:
- name: Include multiple task files
include_tasks: "tasks/{{ item }}.yml"
loop:
- setup
- configure
- deploy
loop_control:
loop_var: task_namePass loop variable to included file:
- 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:
ansible-playbook test_include.yml -vExpected:
``
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"
`