The Problem
Your playbook uses block/rescue for error handling, but it behaves unexpectedly:
fatal: [server]: FAILED! => {"msg": "Rescue task failed: undefined variable in rescue block"}Or rescue tasks never execute when the main block fails:
TASK [Main task] ***********************************************************
fatal: [server]: FAILED! => ...
# Rescue tasks should run but playbook stops insteadOr the always block runs even when main block succeeds, causing confusion.
Why This Happens
block/rescue errors stem from:
Rescue task syntax errors - Invalid YAML or undefined variables in rescue.
Block scope issues - Variables from failed task not available in rescue.
Always block misunderstanding - Always runs regardless of success/failure.
Nested block issues - Inner blocks don't trigger outer rescue.
Rescue task failures - Errors in rescue tasks cause playbook to fail.
Understanding Block Rescue
block/rescue provides structured error handling:
```yaml - name: Error handling block block: - name: Main task that might fail command: /opt/app/start.sh register: result
- name: Another task
- debug:
- msg: "Task succeeded"
rescue: - name: Handle failure debug: msg: "Main task failed: {{ result.msg | default('Unknown error') }}"
always: - name: Always runs debug: msg: "Cleanup complete" ```
Diagnosing the Issue
Run with verbosity:
ansible-playbook playbook.yml -vvDebug the block structure:
- name: Test block
block:
- debug:
msg: "In block"
rescue:
- debug:
msg: "In rescue - should only run on failure"
always:
- debug:
msg: "In always - runs always"The Fix
Fix 1: Access Failed Task Results
Failed task results are available in rescue:
```yaml - block: - name: Task that might fail command: /opt/app/process.sh register: process_result
rescue: - name: Handle failure debug: msg: "Failed with: {{ process_result.stderr | default(process_result.msg) }}" # process_result is available even on failure ```
Fix 2: Handle Undefined Variables in Rescue
Use defaults for potentially undefined variables:
```yaml - block: - name: Conditional task command: /opt/{{ app_name }}/start.sh # app_name might not be defined
rescue: - name: Handle failure debug: msg: "Failed - {{ ansible_failed_result.msg | default('Unknown') }}" # Use ansible_failed_result for guaranteed access ```
ansible_failed_result provides the failure:
- rescue:
- name: Log failure details
debug:
msg: |
Task: {{ ansible_failed_task.name }}
Result: {{ ansible_failed_result }}
Message: {{ ansible_failed_result.msg | default('No message') }}Fix 3: Understand Always Block Behavior
always runs regardless of outcome:
```yaml - block: - name: Main task command: /opt/app/start.sh
rescue: - name: Handle failure debug: msg: "Failed"
always: - name: Cleanup (runs even on success) command: rm -f /tmp/workfile # This runs whether block succeeds or fails ```
For cleanup only on failure:
```yaml - block: - name: Create temp file command: touch /tmp/workfile register: temp_created
- name: Main task
- command: /opt/app/process.sh
rescue: - name: Cleanup only on failure command: rm -f /tmp/workfile when: temp_created is defined and temp_created.changed ```
Fix 4: Prevent Rescue Task Failures
If rescue tasks fail, playbook fails entirely:
```yaml - block: - name: Main task command: /opt/app/start.sh
rescue: - name: Rescue that might fail command: /opt/app/recover.sh # If this fails, playbook fails (no nested rescue)
- name: Safer rescue
- debug:
- msg: "Attempting recovery"
- ignore_errors: yes # Allow rescue to continue despite errors
`
Fix 5: Nested Block Handling
Inner block failures don't trigger outer rescue:
```yaml - block: - name: Outer block task debug: msg: "Outer"
- block:
- - name: Inner task that fails
- command: /bin/false
rescue: - name: Inner rescue debug: msg: "Inner rescue runs" # Inner block handled its own failure
rescue: - name: Outer rescue debug: msg: "Outer rescue - NOT triggered by inner failure" ```
For outer rescue to handle inner failures, remove inner rescue:
```yaml - block: - block: - name: Inner task command: /bin/false # No inner rescue
- name: Outer continues
- debug:
- msg: "This won't run"
rescue: - name: Outer rescue handles inner failure debug: msg: "Handling inner block failure" ```
Fix 6: Block with Loops
Loops interact with block/rescue:
```yaml - block: - name: Process items command: /opt/process.sh {{ item }} loop: - item1 - item2 register: results
rescue: - name: Handle loop failure debug: msg: "Failed on item - check results" # results contains all loop iterations ```
For per-item rescue:
- name: Process items with per-item handling
loop:
- item1
- item2
block:
- name: Process item
command: /opt/process.sh {{ item }}
rescue:
- name: Handle this item's failure
debug:
msg: "Failed processing {{ item }}"Fix 7: Conditional Block Execution
Apply conditions to entire block:
```yaml - block: - name: Conditional tasks debug: msg: "Only runs when condition true"
when: some_condition # Entire block (including rescue/always) respects condition ```
Fix 8: Error Handling Patterns
Complete error handling pattern:
```yaml - name: Safe application deployment block: - name: Pre-deployment check command: /opt/app/healthcheck.sh register: health_check
- name: Deploy application
- command: /opt/app/deploy.sh
- name: Post-deployment validation
- command: /opt/app/validate.sh
rescue: - name: Log deployment failure debug: msg: | Deployment failed on {{ inventory_hostname }} Health check: {{ health_check.stdout | default('not run') }} Error: {{ ansible_failed_result.msg }}
- name: Notify team
- mail:
- to: ops@example.com
- subject: "Deployment failed on {{ inventory_hostname }}"
- body: "{{ ansible_failed_result }}"
- delegate_to: localhost
- connection: local
- name: Rollback
- command: /opt/app/rollback.sh
always: - name: Cleanup temp files file: path: /tmp/deploy state: absent ```
Verifying the Fix
Test block/rescue behavior:
```yaml # test_block.yml - hosts: localhost gather_facts: no tasks: - name: Test block rescue block: - name: Failing task command: /bin/false
- name: Won't run
- debug:
- msg: "This won't execute"
rescue: - name: Rescue task debug: msg: "Rescue executed"
always: - name: Always task debug: msg: "Always executed" ```
Run:
ansible-playbook test_block.yml -vExpected: ``` TASK [Failing task] *************** fatal: [localhost]: FAILED! => ...
TASK [Rescue task] *************** ok: [localhost] => {"msg": "Rescue executed"}
TASK [Always task] *************** ok: [localhost] => {"msg": "Always executed"} ```
Prevention
Test rescue logic separately:
```yaml - name: Force failure to test rescue block: - name: Intentional failure fail: msg: "Testing rescue"
rescue: - name: Test rescue logic debug: msg: "Rescue working" ```
Document block purpose:
- name: Deploy with rollback capability
block:
# Deployment tasks
rescue:
# Rollback on failure
always:
# Cleanup regardless