Skip to content

Failure Actions

Failure Actions

IMPORTANT: This subsection documents the on_failure mechanism for workflow shell commands, which uses TestDebugConfig for automatic debugging and retry. This is distinct from the theoretical retry_config.on_failure field shown in the parent chapter overview, which is defined in code but not yet integrated into command execution.

Configure automatic debugging and retry behavior when shell commands fail using the on_failure field. This mechanism allows Claude to automatically diagnose and fix test failures, build errors, and other command failures.

Source: src/config/command.rs:168-183 (TestDebugConfig struct), src/config/command.rs:372 (WorkflowStepCommand.on_failure field)

What is TestDebugConfig?

The on_failure field in workflow commands accepts a TestDebugConfig object that specifies: - A Claude command to run when the shell command fails - How many times to retry the debug-fix cycle - Whether to fail the entire workflow if debugging doesn't fix the issue - Whether the debug command should create git commits

This enables self-healing workflows where Claude automatically attempts to fix failures before escalating them.

Source: src/config/command.rs:168-183

pub struct TestDebugConfig {
    /// Claude command to run on test failure
    pub claude: String,

    /// Maximum number of retry attempts (default: 3)
    pub max_attempts: u32,

    /// Whether to fail the workflow if max attempts reached (default: false)
    pub fail_workflow: bool,

    /// Whether the debug command should create commits (default: true)
    pub commit_required: bool,
}

Basic Syntax

The simplest form specifies just the Claude command to run on failure:

- shell: "cargo test"
  on_failure:
    claude: "/prodigy-debug-test-failure --output ${shell.output}"

Execution Flow: 1. Run cargo test 2. If it fails → Capture output in ${shell.output} 3. Run /prodigy-debug-test-failure --output ${shell.output} 4. Claude analyzes failure and attempts fix 5. If Claude creates commits → Re-run cargo test 6. Repeat up to max_attempts times (default: 3)

Source: Example from workflows/documentation-drift.yml:47-53

Configuration Options

Full Configuration

All on_failure options:

- shell: "cargo test --doc"
  on_failure:
    claude: "/prodigy-debug-test-failure --output ${shell.output}"
    max_attempts: 3           # Maximum debug-fix-retest cycles (default: 3)
    fail_workflow: false      # Continue workflow even if can't fix (default: false)
    commit_required: true     # Debug command should create commits (default: true)

Source: Example from workflows/documentation-drift.yml:47-53

max_attempts

Controls how many debug-fix-retest cycles to attempt:

- shell: "just fmt-check && just lint"
  on_failure:
    claude: "/prodigy-lint ${shell.output}"
    max_attempts: 5           # Try up to 5 times to fix linting issues

Default: 3 attempts (defined in src/config/command.rs:173)

Use Cases: - 1 attempt: Quick fixes only, don't waste time on hard problems - 3 attempts (default): Reasonable for most test failures - 5+ attempts: Complex issues that might need multiple iterations

Source: workflows/implement.yml:27-30

fail_workflow

Controls whether to stop the entire workflow if debugging can't fix the issue:

# CRITICAL: Must succeed - fail workflow if can't fix
- shell: "cargo test --lib"
  on_failure:
    claude: "/prodigy-debug-test-failure --output ${shell.output}"
    max_attempts: 3
    fail_workflow: true       # Stop workflow if tests still fail after 3 attempts
# NON-CRITICAL: Best effort - continue even if can't fix
- shell: "cargo test --doc"
  on_failure:
    claude: "/prodigy-fix-doc-tests --output ${shell.output}"
    max_attempts: 2
    fail_workflow: false      # Continue workflow even if doc tests fail

Default: false - workflow continues even if debugging doesn't fix the issue (defined in src/config/command.rs:177)

Source: Examples from workflows/coverage-with-test-debug.yml:14-23

commit_required

Controls whether the debug command must create git commits:

- shell: "cargo clippy -- -D warnings"
  on_failure:
    claude: "/prodigy-lint ${shell.output}"
    commit_required: true     # Must commit fixes (default)

Default: true - debug command must create commits (defined in src/config/command.rs:181)

When to set to false: Rare - only if the debug command doesn't modify code (e.g., just logs diagnostic info)

Source: Field definition src/config/command.rs:180-182

Real-World Examples

Example 1: Test Debugging with Multiple Attempts

From workflows/implement.yml:

- shell: "cargo test"
  timeout: 600
  on_failure:
    claude: "/prodigy-debug-test-failure --spec $ARG --output ${shell.output}"
    max_attempts: 5
    fail_workflow: false      # Continue even if tests can't be fixed

Behavior: - Run tests with 10-minute timeout - On failure, Claude analyzes output and fixes issues - Try up to 5 debug-fix-test cycles - If still failing after 5 attempts, continue workflow anyway - Each fix creates a commit

Source: workflows/implement.yml:19-24

Example 2: Critical vs Non-Critical Commands

From workflows/coverage-with-test-debug.yml:

# CRITICAL: Library tests must pass
- shell: "cargo test --lib"
  on_failure:
    claude: "/prodigy-debug-test-failure --spec ${coverage.spec} --output ${shell.output}"
    max_attempts: 3
    fail_workflow: true       # STOP if can't fix

# NON-CRITICAL: Doc tests are best-effort
- shell: "cargo test --doc"
  on_failure:
    claude: "/prodigy-fix-doc-tests --output ${shell.output}"
    max_attempts: 2
    fail_workflow: false      # CONTINUE if can't fix

Source: workflows/coverage-with-test-debug.yml:13-24

Example 3: Chaining with on_success

From workflows/implement-with-tests.yml:

- shell: "cargo test"
  on_failure:
    # If tests fail, debug and fix them
    claude: "/prodigy-debug-test-failures '${test_output}'"
    commit_required: true
    on_success:
      # After fixing, verify tests now pass
      - shell: "cargo test"
        on_failure:
          # If STILL failing, try deeper analysis
          claude: "/prodigy-fix-test-failures '${shell.output}' --deep-analysis"
          commit_required: true

Nested Retry Logic: 1. Run tests 2. If fail → Debug and fix 3. If fix succeeded (commit created) → Re-run tests 4. If tests STILL fail → Try deeper analysis 5. Verify second fix works

Source: workflows/implement-with-tests.yml:27-39

Example 4: Linting After Implementation

From workflows/documentation-drift.yml:

- shell: "just fmt-check && just lint"
  on_failure:
    claude: "/prodigy-lint ${shell.output}"
    commit_required: true
    max_attempts: 3
    fail_workflow: false      # Non-blocking - formatting issues shouldn't stop workflow

Use Case: After implementing features, ensure code is properly formatted and linted, but don't block if there are style issues.

Source: workflows/documentation-drift.yml:56-61

How It Works: Execution Flow

Retry Loop

When a shell command with on_failure fails:

  1. Initial Failure: Shell command exits with non-zero code
  2. Capture Output: Stderr/stdout saved to ${shell.output}
  3. First Debug Attempt:
  4. Run Claude command with failure output
  5. Claude analyzes error and makes fixes
  6. If commit_required=true, check for git commits
  7. Retry Original Command:
  8. If commits were created → Re-run original shell command
  9. If no commits → Debug didn't fix anything, stop retrying
  10. Repeat: Continue debug-fix-retry cycle up to max_attempts
  11. Final Result:
  12. If any attempt succeeded → Command passes
  13. If all attempts failed and fail_workflow=true → Workflow stops
  14. If all attempts failed and fail_workflow=false → Workflow continues

Commit Requirement Logic

The commit_required field interacts with the retry loop:

  • commit_required=true (default): Only retry if Claude created commits
  • Rationale: If Claude didn't commit, it didn't find a fix
  • Prevents infinite loops where Claude can't solve the problem

  • commit_required=false: Retry even if no commits

  • Rare use case: Debug command doesn't modify code (logging, diagnostics)

Source: Conceptual flow based on src/config/command.rs:168-183 definition and actual usage in workflows

Limitations and Gotchas

1. Only Works for Shell Commands

The on_failure field is only available for shell: commands, NOT for claude: commands:

# ✓ WORKS - shell command with on_failure
- shell: "cargo test"
  on_failure:
    claude: "/debug-test"

# ✗ DOES NOT WORK - claude command doesn't support on_failure
- claude: "/implement-feature"
  on_failure:              # This field is ignored!
    claude: "/fix-issue"

Source: src/config/command.rs:320-400 - WorkflowStepCommand has separate shell and claude fields, on_failure applies only to shell commands

2. Not the Same as retry_config

IMPORTANT: The on_failure: TestDebugConfig documented here is NOT the same as the theoretical retry_config.on_failure: FailureAction mentioned in the parent chapter.

# ✓ ACTUAL SYNTAX (what this subsection documents)
- shell: "cargo test"
  on_failure:
    claude: "/debug"
    max_attempts: 3

# ✗ DOES NOT WORK (theoretical syntax, not implemented)
- shell: "cargo test"
  retry_config:
    on_failure: stop        # This field exists in code but is NOT wired into execution

The retry_config field is not defined in WorkflowStepCommand (see src/config/command.rs:320-400). The retry_v2::FailureAction enum exists in src/cook/retry_v2.rs:157-165 but is not integrated into command execution.

3. TestDebugConfig is Not Retry Configuration

Despite the name and retry-like behavior, TestDebugConfig is a debugging mechanism, not a retry mechanism:

  • Retry: Re-run the same command without changes (for transient failures)
  • Debug: Run a DIFFERENT command (Claude) to diagnose and fix the issue, THEN re-run

The TestDebugConfig mechanism assumes failures are due to code issues that need fixing, not transient errors.

4. Max Attempts Includes Initial Failure

If max_attempts: 3, the execution pattern is: 1. Initial run (fails) 2. Debug attempt #1 → Retry 3. Debug attempt #2 → Retry 4. Debug attempt #3 → Retry

So the original command runs up to 4 times total (1 initial + 3 debug-retry cycles).

Comparison with Other Retry Mechanisms

Prodigy has multiple mechanisms that might be confused:

Mechanism Purpose Scope When to Use
on_failure: TestDebugConfig Auto-debug and fix code issues Shell commands Test failures, build errors that need code fixes
retry_config (theoretical) Retry with backoff for transient errors Commands (not implemented) Network errors, timeouts (when implemented)
Workflow-level retry Retry entire work items MapReduce jobs Bulk operation failures, DLQ retry
on_success chaining Sequential command execution Any command Multi-step validation, verification after fixes

This subsection documents only: on_failure: TestDebugConfig

For workflow-level retry, see Workflow-Level vs Command-Level Retry.

Migration from Deprecated test: Syntax

The test: command type is deprecated. Migrate to shell: with on_failure::

Old (Deprecated):

- test:
    command: "cargo test"
    on_failure:
      claude: "/debug"

New (Current):

- shell: "cargo test"
  on_failure:
    claude: "/debug"

Source: Deprecation warning in src/config/command.rs:446-455