Introduction

Ansible Lint rule 505 (missing-import) flags playbooks and roles that reference files, tasks, or roles that cannot be found on the Ansible controller. This is a syntax-check level rule — meaning it catches errors that would cause your playbook to fail at runtime before you even run it.

This article covers the root causes of this error, every common scenario that triggers it, how to fix each one, and best practices for organizing imports in Ansible projects.

Understanding the Error

When ansible-lint encounters a reference to a file that does not exist, it reports:

WARNING  Listing 1 violation(s) that are fatal
syntax-check[missing-file]: Unable to retrieve file contents
playbook.yml:5 Could not find or access 'tasks/missing.yml' on the Ansible Controller.

This rule is tagged as core and unskippable — you cannot disable it because a missing file will always cause a runtime failure.

Common Causes

1. Typo in Filename or Path

The most frequent cause — a simple misspelling in the import path:

# Error: filename misspelled
- name: Include setup tasks
  ansible.builtin.import_tasks: taks/setup.yml  # should be tasks/setup.yml

2. File Does Not Exist Yet

Referencing a task file you planned to create but have not yet written:

# Error: file not created
- name: Include monitoring tasks
  ansible.builtin.include_tasks: tasks/monitoring.yml  # file doesn't exist

3. Wrong Relative Path

Ansible resolves relative paths differently depending on context:

# From a role's tasks/main.yml:
- ansible.builtin.import_tasks: setup.yml        # looks in roles/myrole/tasks/
- ansible.builtin.import_tasks: ../files/setup.yml  # wrong - traverses up

# From a playbook:
- ansible.builtin.import_tasks: tasks/setup.yml  # looks relative to playbook dir

4. Missing Role in requirements.yml

When a playbook references a role that is not installed:

# Error: role not installed
- hosts: all
  roles:
    - geerlingguy.docker  # not in roles/ and not installed via galaxy

5. Case-Sensitive Filenames

On case-sensitive filesystems, Tasks/Setup.yml is different from tasks/setup.yml:

# Error on Linux (case-sensitive)
- ansible.builtin.import_tasks: Tasks/Setup.yml  # actual file: tasks/setup.yml

Error Examples

Example 1: Missing include File

Playbook:

---
- name: Example playbook
  hosts: all
  tasks:
    - name: Include non-existing tasks
      ansible.builtin.include_tasks: 'non-existing.yml'

Lint Output:

$ ansible-lint playbook.yml
WARNING  Listing 1 violation(s) that are fatal
syntax-check[missing-file]: Unable to retrieve file contents
playbook.yml:1:1 Could not find or access 'non-existing.yml' on the Ansible Controller.

                    Rule Violation Summary
 count tag                        profile rule associated tags
     1 syntax-check[missing-file] min     core, unskippable

Failed: 1 failure(s), 0 warning(s) on 1 files.

Example 2: Missing import_playbook

---
- name: Main playbook
  hosts: localhost
  tasks:
    - name: dummy task
      ansible.builtin.debug:
        msg: "hello"

- ansible.builtin.import_playbook: site-cleanup.yml  # file doesn't exist

Lint Output:

syntax-check[missing-file]: Unable to retrieve file contents
main.yml:9 Could not find or access 'site-cleanup.yml' on the Ansible Controller.

Example 3: Missing Role

---
- name: Deploy application
  hosts: webservers
  roles:
    - common
    - deploy_app  # role directory doesn't exist

Lint Output:

syntax-check[missing-file]: the role 'deploy_app' was not found

Fixing the Error

Fix 1: Create the Missing File

The most straightforward fix — create the file that is being referenced:

# Create the missing task file
mkdir -p tasks
cat > tasks/setup.yml << 'EOF'
---
- name: Ensure packages are installed
  ansible.builtin.package:
    name: "{{ item }}"
    state: present
  loop:
    - vim
    - curl
    - wget
EOF

Fix 2: Correct the Path

Verify the relative path is correct for your project structure:

project/
├── playbook.yml
├── tasks/
│   └── setup.yml
└── roles/
    └── webserver/
        └── tasks/
            └── main.yml
# From playbook.yml:
- ansible.builtin.import_tasks: tasks/setup.yml  # ✅ correct

# From roles/webserver/tasks/main.yml:
- ansible.builtin.import_tasks: install.yml  # ✅ looks in roles/webserver/tasks/

Fix 3: Install Missing Roles

# Install from Galaxy
ansible-galaxy install geerlingguy.docker

# Or install all roles from requirements
ansible-galaxy install -r requirements.yml

# Or install collections
ansible-galaxy collection install -r requirements.yml

Fix 4: Use Conditional Includes

If a file might not exist in all environments, use include_tasks with when:

- name: Check if optional tasks file exists
  ansible.builtin.stat:
    path: "{{ playbook_dir }}/tasks/optional.yml"
  register: optional_tasks
  delegate_to: localhost

- name: Include optional tasks if they exist
  ansible.builtin.include_tasks: tasks/optional.yml
  when: optional_tasks.stat.exists

import_tasks vs include_tasks Behavior

The error manifests differently depending on which directive you use:

DirectiveWhen ResolvedMissing File Behavior
import_tasksAt playbook parse time (static)Fails immediately at parse time
include_tasksAt runtime (dynamic)Fails when the task is reached
import_playbookAt playbook parse time (static)Fails immediately at parse time
import_roleAt playbook parse time (static)Fails immediately at parse time
include_roleAt runtime (dynamic)Fails when the task is reached

Static imports (import_*) are caught by ansible-lint because the file must exist at parse time. Dynamic includes (include_*) are also flagged because they would fail at runtime.

Best Practices for Organizing Imports

1. Use a Consistent Directory Structure

project/
├── ansible.cfg
├── inventory/
│   ├── production
│   └── staging
├── playbooks/
│   ├── site.yml
│   └── deploy.yml
├── tasks/
│   ├── common.yml
│   └── security.yml
└── roles/
    └── webserver/
        ├── tasks/
        │   ├── main.yml
        │   ├── install.yml
        │   └── configure.yml
        ├── handlers/
        │   └── main.yml
        ├── templates/
        └── defaults/
            └── main.yml

2. Pin Role Versions in requirements.yml

# requirements.yml
roles:
  - name: geerlingguy.docker
    version: "6.1.0"
  - name: geerlingguy.nginx
    version: "3.2.0"

collections:
  - name: community.general
    version: ">=7.0.0"

3. Validate Imports in CI/CD

Add ansible-lint to your CI pipeline:

# .github/workflows/lint.yml
- name: Run ansible-lint
  run: |
    pip install ansible-lint
    ansible-lint playbooks/ roles/

4. Use Descriptive Filenames

# ✅ Clear and descriptive
- ansible.builtin.import_tasks: tasks/install-nginx.yml
- ansible.builtin.import_tasks: tasks/configure-ssl.yml

# ❌ Vague
- ansible.builtin.import_tasks: tasks/setup.yml
- ansible.builtin.import_tasks: tasks/do-stuff.yml

5. Document External Dependencies

Add a README listing required roles and collections:

## Dependencies

Install before running:
  ansible-galaxy install -r requirements.yml

Troubleshooting Checklist

When you encounter missing-import:

  1. Check the filename — is it spelled correctly?
  2. Check the path — is it relative to the right directory?
  3. Check file existence — does ls -la <path> show the file?
  4. Check case sensitivity — does the case match exactly?
  5. Check role installation — run ansible-galaxy list to see installed roles
  6. Check collection installation — run ansible-galaxy collection list
  7. Check ansible.cfg — is roles_path configured correctly?

Conclusion

Ansible Lint Error 505 (missing-import) is a critical check that ensures all referenced files, tasks, and roles exist before playbook execution. The fix is usually straightforward — create the missing file, correct the path, or install the missing role/collection. By maintaining a well-organized project structure, using descriptive filenames, pinning dependency versions, and running ansible-lint in CI, you can prevent this error from reaching production.