Fix 'ERROR! conflicting action statements: copy, template' in Ansible Tasks

beginner๐Ÿ”ง Ansible2026-05-16| Ansible 2.x and above, any OS (Linux/macOS), any playbook

Error Message

ERROR! conflicting action statements: copy, template
#task#module#syntax#playbook

The Error

Your playbook hits a wall mid-run:

ERROR! conflicting action statements: copy, template

Or some variation depending on what's in your task:

ERROR! conflicting action statements: apt, yum
ERROR! conflicting action statements: file, copy

The module names differ, but the root cause never does: one task block is trying to run two action modules at once.

Why This Happens

Ansible enforces a strict rule โ€” one action module per task, full stop. When the parser encounters two module keys sitting at the same indentation level, it has no way to decide which one wins. So it doesn't guess. It stops.

Nine times out of ten this creeps in during copy-paste. You merge two task blocks and don't notice a duplicate key has snuck along for the ride:

- name: Deploy config file
  copy:
    src: files/app.conf
    dest: /etc/app/app.conf
  template:
    src: templates/app.conf.j2
    dest: /etc/app/app.conf

Both copy and template are siblings here. Ansible raises conflicting action statements: copy, template and refuses to go any further.

Step-by-Step Fix

Step 1 โ€” Find the broken task

Ansible's error output is actually pretty helpful here. It names the conflicting modules and tells you exactly where to look:

ERROR! conflicting action statements: copy, template

The error appears to be in '/home/deploy/playbooks/site.yml': line 14, column 5

Open the file. Jump to line 14. The offending task will be right there.

Step 2 โ€” Pick the right module

Before deleting anything, decide which module actually belongs. Here's a quick cheat sheet for the most common collisions:

  • copy vs template โ€” Static file with no variables? Use copy. File has Jinja2 expressions like {{ db_host }}? Use template.
  • apt vs yum โ€” Match the package manager to the target OS, or sidestep the whole debate with the generic package module.
  • file vs copy โ€” Managing permissions, ownership, or a symlink on an existing path? That's file. Pushing a file from your control node to the remote? That's copy.

Step 3 โ€” Split or trim

Option A: Keep one module, drop the other

# Before (broken)
- name: Deploy config file
  copy:
    src: files/app.conf
    dest: /etc/app/app.conf
  template:
    src: templates/app.conf.j2
    dest: /etc/app/app.conf

# After (fixed) โ€” pick whichever you actually need
- name: Deploy config file
  template:
    src: templates/app.conf.j2
    dest: /etc/app/app.conf
    owner: root
    group: root
    mode: '0644'

Option B: Both actions are genuinely needed โ€” split into two tasks

- name: Copy static base config
  copy:
    src: files/base.conf
    dest: /etc/app/base.conf
    mode: '0644'

- name: Render dynamic app config
  template:
    src: templates/app.conf.j2
    dest: /etc/app/app.conf
    mode: '0644'

Step 4 โ€” Hunt for hidden conflicts from YAML anchors

Sometimes the broken line isn't obvious. YAML anchors can expand a block that already carries an action key, burying the duplicate:

defaults: &defaults
  copy:
    src: files/default.conf
    dest: /etc/app/default.conf

- name: Deploy config
  <<: *defaults
  template:           # conflict โ€” 'copy' merged in from the anchor above
    src: templates/app.conf.j2
    dest: /etc/app/app.conf

Expand the anchor inline or restructure the tasks so the merge doesn't pull in a competing module key.

Verify the Fix

Don't run the full playbook blind. Start with a syntax check โ€” it costs nothing and catches problems before any host is touched:

ansible-playbook site.yml --check --syntax-check

Success looks like this โ€” just the playbook name, nothing else:

playbook: site.yml

Good. Now do a dry run against real hosts to see what would change:

ansible-playbook site.yml --check -v

Happy with the output? Run it for real:

ansible-playbook site.yml

Quick Reference: One Module Per Task

A valid task has exactly one action module. Everything else โ€” become, when, notify, register, loop, tags โ€” are task directives. They sit alongside the module and don't conflict with it:

- name: Descriptive name of what this task does
  module_name:          # exactly ONE action module
    param1: value1
    param2: value2
  become: true          # task directive โ€” fine to have alongside the module
  when: some_condition
  notify: restart app

Two action modules at the same level? Never. That's the only rule you need to remember.

Preventing This Going Forward

  • Run yamllint before every playbook run โ€” it flags duplicate keys before Ansible even sees the file.
  • ansible-lint has a dedicated rule for conflicting module usage. Worth adding to your CI pipeline.
  • After merging role tasks or copy-pasting between playbooks, do a quick scan: each task block should have exactly one module key.
  • The Ansible extension for VS Code highlights conflicting keys as you type, which catches the problem at the source.

Related Error Notes