The Problem

Your playbook uses block/rescue for error handling, but it behaves unexpectedly:

bash
fatal: [server]: FAILED! => {"msg": "Rescue task failed: undefined variable in rescue block"}

Or rescue tasks never execute when the main block fails:

bash
TASK [Main task] ***********************************************************
fatal: [server]: FAILED! => ...
# Rescue tasks should run but playbook stops instead

Or 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:

bash
ansible-playbook playbook.yml -vv

Debug the block structure:

yaml
- 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:

yaml
- 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:

yaml
- 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:

bash
ansible-playbook test_block.yml -v

Expected: ``` 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:

yaml
- name: Deploy with rollback capability
  block:
    # Deployment tasks
  rescue:
    # Rollback on failure
  always:
    # Cleanup regardless