The Problem
Your playbook uses post_tasks for cleanup after roles, but issues occur:
fatal: [server]: FAILED! => {"msg": "post_tasks failed after roles completed"}Or post_tasks run even when earlier tasks failed, causing cleanup on failed deployment:
roles:
- deploy # Fails
post_tasks:
- name: Enable in LB
# Runs despite deploy failureOr handlers from post_tasks don't flush properly.
Why This Happens
post_tasks errors stem from:
Execution regardless of failure - post_tasks run even if roles fail.
Handler timing - Handlers wait until post_tasks complete.
Variable scope - Variables from roles not available.
Serial interaction - post_tasks run for each batch.
Condition confusion - Conditions don't prevent post_tasks on failure.
Understanding post_tasks
post_tasks run after roles in a play:
```yaml - hosts: webservers roles: - deploy
post_tasks: - name: Cleanup after roles debug: msg: "Post-task running" ```
Execution order: pre_tasks -> roles -> tasks -> post_tasks -> handlers
Diagnosing the Issue
Run with verbose output:
ansible-playbook playbook.yml -vvCheck execution order:
ansible-playbook playbook.yml --list-tasks
# Shows post_tasks after role tasksTest with intentional failure:
```yaml roles: - name: Fails fail: msg: "Test failure"
post_tasks: - debug: msg: "This still runs!" ```
The Fix
Fix 1: Prevent post_tasks on Failure
Use conditionals to skip post_tasks on failure:
```yaml - hosts: webservers roles: - deploy
post_tasks: - name: Enable in LB only on success command: lb-cli enable {{ inventory_hostname }} delegate_to: lb-server when: not deploy_failed | default(false) ```
Track deployment status:
```yaml - hosts: webservers tasks: - name: Deploy block: - name: Deploy application command: /opt/deploy.sh register: deploy_result
- set_fact:
- deploy_failed: false
rescue: - set_fact: deploy_failed: true deploy_error: "{{ ansible_failed_result.msg }}"
post_tasks: - name: Cleanup only on success command: rm -f /tmp/deploy-work when: not deploy_failed
- name: Report failure
- debug:
- msg: "Deploy failed: {{ deploy_error }}"
- when: deploy_failed
`
Fix 2: Flush Handlers in post_tasks
Handlers wait until post_tasks complete - flush explicitly:
```yaml - hosts: webservers roles: - configure
post_tasks: - name: Verify config command: /opt/verify-config.sh
- name: Flush handlers before verification
- meta: flush_handlers
- name: Check service running
- wait_for:
- port: 8080
`
Fix 3: post_tasks with Serial
post_tasks run for EACH serial batch:
```yaml - hosts: webservers serial: 3 roles: - deploy
post_tasks: - name: Enable in LB command: lb-cli enable {{ inventory_hostname }} # Runs for each host in each batch ```
For batch-level post_tasks:
post_tasks:
- name: Notify monitoring per batch
command: notify.sh batch complete
run_once: yesFix 4: Access Role Variables in post_tasks
Role variables may not persist to post_tasks:
```yaml - hosts: webservers roles: - role: deploy vars: deploy_version: 1.2.3
post_tasks: - name: Use role variable debug: msg: "Deployed {{ deploy_version }}" # deploy_version may not be defined here ```
Use set_fact in role to persist:
```yaml # roles/deploy/tasks/main.yml - name: Set deployment info set_fact: deployed_version: "{{ deploy_version }}" deployed_at: "{{ ansible_date_time.iso8601 }}" cacheable: yes
# post_tasks can now use deployed_version ```
Fix 5: post_tasks Common Patterns
Load balancer re-enable:
```yaml - hosts: webservers pre_tasks: - name: Disable in LB command: lb-cli disable {{ inventory_hostname }} delegate_to: lb-server
roles: - deploy
post_tasks: - name: Wait for service wait_for: port: 8080 timeout: 60
- name: Health check
- uri:
- url: http://{{ inventory_hostname }}:8080/health
- status_code: 200
- name: Enable in LB
- command: lb-cli enable {{ inventory_hostname }}
- delegate_to: lb-server
`
Notification on completion:
```yaml - hosts: appservers roles: - deploy
post_tasks: - name: Notify success uri: url: https://hooks.slack.com/services/xxx method: POST body: text: "Deployed {{ inventory_hostname }} successfully" delegate_to: localhost connection: local run_once: yes ```
Cleanup temporary files:
```yaml - hosts: all roles: - install
post_tasks: - name: Cleanup temp file: path: /tmp/install state: absent
- name: Clear caches
- command: rm -rf /var/cache/app/*
- ignore_errors: yes
`
Fix 6: post_tasks with Conditions
Apply conditions to post_tasks:
```yaml - hosts: webservers roles: - deploy
post_tasks: - name: Post-deployment verification block: - name: Run tests command: /opt/test.sh register: test_result
- name: Report results
- debug:
- msg: "Tests: {{ test_result.stdout }}"
- when: run_tests | default(false)
`
Environment-based post_tasks:
post_tasks:
- name: Production-only tasks
block:
- name: Backup after deploy
command: /opt/backup.sh
when: env == 'production'Fix 7: post_tasks Failure Handling
post_tasks failure still marks host as failed:
```yaml - hosts: webservers roles: - deploy
post_tasks: - name: Optional cleanup command: rm -rf /tmp/deploy ignore_errors: yes # Don't fail the host
- name: Critical verification
- command: /opt/verify.sh
- # If this fails, host is marked failed
`
Use block/rescue for post_tasks:
```yaml post_tasks: - name: Post-deployment with error handling block: - name: Verify command: /opt/verify.sh
- name: Notify
- command: /opt/notify.sh
rescue: - name: Log post_tasks failure debug: msg: "Post-tasks failed but deploy succeeded"
- name: Manual cleanup needed
- debug:
- msg: "Run cleanup manually"
`
Fix 8: Verify Role Success Before post_tasks
Check role execution status:
```yaml - hosts: webservers tasks: - name: Deploy block: - include_role: name: deploy register: deploy_role_result
- name: Check role success
- set_fact:
- role_success: "{{ deploy_role_result is not failed }}"
post_tasks: - name: Post only if role succeeded debug: msg: "Role succeeded, running post_tasks" when: role_success | default(false) ```
Verifying the Fix
Test post_tasks execution:
```yaml # test_post.yml - hosts: localhost gather_facts: no roles: - test_role
post_tasks: - name: Post-task 1 debug: msg: "First post-task"
- name: Post-task 2
- debug:
- msg: "Second post-task"
`
Run:
ansible-playbook test_post.yml -vExpected order:
``
TASK [test_role : main] ***********************************************************
TASK [Post-task 1] ***********************************************************
TASK [Post-task 2] ***********************************************************
Test post_tasks on failure:
```yaml # test_post_failure.yml - hosts: localhost gather_facts: no tasks: - name: Intentional failure fail: msg: "Test failure"
post_tasks: - name: Still runs debug: msg: "Post-tasks run despite failure" ```
Run:
ansible-playbook test_post_failure.yml
# Post-tasks still executePrevention
Document post_tasks purpose:
- hosts: webservers
# post_tasks: Enable in LB and verify after deployment
post_tasks:
- name: Enable in LB
...Add success verification:
post_tasks:
- name: Verify deployment success
assert:
that:
- deploy_result is not failed
fail_msg: "Deployment failed, skipping post_tasks"
success_msg: "Deployment succeeded"