Module 3: Parallel Sub-Agent Delegation

4. Timeout, Error Handling, and Fallback Strategies

4. Timeout, Error Handling, and Fallback Strategies

Description

A parallel system that only works on the happy path isn't a system — it's a demo. Parallel agents fail: one subagent consumes more turns than expected, another needs a permission it doesn't have pre-approved, a third produces inconsistent output that breaks the merge. What happens to the other 3 subagents when one fails? Are they all canceled? Do you continue without it? Do you retry? These decisions define the robustness of your orchestration.

In sequential execution, an error stops everything — you see it immediately, fix it, and continue. In parallel execution, an error in one subagent can go unnoticed while the others continue, producing a partial result that looks complete but has a gap. Or worse: the subagent that failed was the one editing the shared file, and now the other 3 have changes that depend on that edit that never happened.

By the end of this capsule you'll know how to prevent most failures with proper configuration (maxTurns, pre-approved permissions), how to detect the ones that occur, how to recover from them (resume in foreground, retry, skip), and how to design your flow so it's resilient to partial failures.


maxTurns: The Subagents' Timeout

What it is and why you need it

maxTurns limits how many agentic turns a subagent can take. A "turn" is a complete interaction: Claude thinks, uses a tool, receives the result. If a subagent reaches its turn limit without completing the task, it stops and reports what it managed to do.

---
name: quick-scanner
maxTurns: 10
---

Without maxTurns, a subagent could run indefinitely — reading files, searching patterns, making changes, iterating... especially if the system prompt doesn't have a well-bounded scope. In sequential execution, you can cancel it manually (Ctrl+C). In background, you don't have that option — you need an automatic limit.

Choosing the right value

Task typeRecommended maxTurnsReasoning
Analysis of 1-2 files5-8Read + Grep + report
Review of a module10-15List files + read each + report
Implementation of changes15-25Read + edit + verify per file
Refactoring of a complete module20-30Multiple files, iterative edits
Broad codebase research10-15Search + selective reading + report

Rule for estimating: Count the operations you expect:

Example: refactor error handling in src/auth/ (5 files)

1. Glob to list files              → 1 turn
2. Read file 1                     → 1 turn
3. Edit file 1                     → 1 turn
4. Read file 2                     → 1 turn
5. Edit file 2                     → 1 turn
... (3 more files)                 → 6 turns
11. Verify with Grep               → 1 turn
12. Produce report                 → 1 turn

Total estimate: 12 turns
Recommended maxTurns: 15 (12 + 25% margin)

Add a 20-30% margin over your estimate. It's better for the subagent to finish with leftover turns than to fall short.

What happens when maxTurns is reached

When a subagent reaches its limit:

  1. The subagent stops — it doesn't run any more tools
  2. It reports what it managed to complete up to that point
  3. Claude (main) receives the partial result
  4. You can decide whether the partial result is enough or you need more
Subagent with maxTurns: 10

Turn 1-8: Reads files, edits 3 of 5, produces partial report
Turn 9: Edits file 4
Turn 10: LIMIT — reports: "I modified 4 of 5 files. The file
         src/auth/middleware.py was not processed due to the turn limit."

The partial result is better than no result. The subagent prioritizes the most important tasks first (if the system prompt is well designed) and leaves the less critical ones for the end.

maxTurns and parallel tasks

In a parallel flow, maxTurns acts as an individual timeout per worker:

auth worker    (maxTurns: 20) → finishes at turn 15 ✅
products worker (maxTurns: 20) → finishes at turn 18 ✅
orders worker  (maxTurns: 20) → reaches turn 20 ⚠️ (partial result)
notif worker   (maxTurns: 20) → finishes at turn 8 ✅

The orders worker produced a partial result. The other 3 completed normally. The merge coordinator will receive 3 complete results and 1 partial — it must decide what to do with the partial one.

To handle this, instruct the merge coordinator:

## Handling Partial Results
If any worker reports a partial result (did not complete all tasks):
1. Note which tasks were not completed
2. Verify that completed tasks are internally consistent
3. Include partial worker's completed tasks in the merge
4. List uncompleted tasks as "PENDING — requires manual completion"

Permission Errors in Background

The problem

A background subagent can't ask for confirmation. If it needs a permission that isn't pre-approved by its tools allowlist, it fails on that specific action:

Background subagent with tools: [Read, Grep, Glob]

Turn 1: Read src/auth/models.py      → ✅ (Read is in the allowlist)
Turn 2: Grep "password" src/          → ✅ (Grep is in the allowlist)
Turn 3: Bash "pip install bcrypt"    → ❌ (Bash is NOT in the allowlist)
Turn 4: Write fix to models.py       → ❌ (Write is NOT in the allowlist)

The subagent doesn't crash — the individual action fails and the subagent continues with what it can do. But if the action that failed was critical to the task, the result will be incomplete or incorrect.

Prevention: complete allowlist

The best strategy is preventive — make sure the frontmatter includes all the tools the subagent might need:

---
name: module-refactorer
tools: Read, Write, Edit, Grep, Glob, Bash
background: true
isolation: worktree
---

Before launching a background subagent, review its system prompt and ask yourself: "Which tools will it use?" If it reads files and edits them, it needs Read + Write + Edit. If it runs commands, it needs Bash. If it searches patterns, it needs Grep + Glob.

Detection: signs of a permission failure

When a background subagent completes, its report may have signs that something failed:

Signs of a permission failure:
- "I couldn't run command X"
- "I couldn't modify file Y"
- Partial report when you expected a complete one
- Output that describes what should be done instead of doing it

If you see these signs, the subagent probably needed a tool it didn't have.

Recovery: resume in foreground

When a background subagent fails, you can resume it in foreground where it can ask you for permissions:

Claude: "The module-refactorer couldn't complete the task.
         It needed to run Bash to install a dependency."

You: "Resume it in foreground so it can request the permissions."

Claude: [re-runs the subagent in foreground]
Claude: "Can I run 'pip install bcrypt'?"
You: "Yes"
Claude: [completes the task]

To make resuming easier, the subagent can be designed to be idempotent — able to re-run without problems even if it already completed part of the work.


Fallback Strategies

Strategy 1: Retry

If a subagent fails for a transient reason (timeout, network error, tool temporarily unavailable), retrying it can resolve the problem.

Flow with retry:

Worker A → fails (timeout)
  ↓
Retry Worker A → success ✅
  ↓
Continue with merge

When to use retry:

  • The failure is transient (not a logic or configuration error)
  • The subagent is idempotent (re-running doesn't cause problems)
  • The cost of retrying is low (fast subagent with haiku)

When NOT to use retry:

  • The failure is consistent (same error every time)
  • The subagent already made partial changes that can't be easily undone
  • The cost is high (long subagent with opus)

Prompt for retry:

The auth worker failed on timeout. Retry the same task:
- Use the module-worker to refactor src/auth/
- Increase maxTurns to 30 (it was 20 before)
- If it fails again, report what it managed to complete

Strategy 2: Skip

If a subagent fails and its task isn't critical, you can skip it and continue with the others.

Flow with skip:

Worker A → success ✅
Worker B → fails ❌ → SKIP
Worker C → success ✅
Worker D → success ✅
  ↓
Merge coordinator → reports that B was skipped
  ↓
Result: 3 of 4 modules refactored

When to use skip:

  • The subagent's task is independent of the rest
  • The partial result (N-1 of N) is acceptable
  • You can complete the skipped task manually afterward

When NOT to use skip:

  • Other subagents depend on the result of the one that failed
  • Skipping produces an inconsistent result
  • The task is critical (security fix, data migration)

Prompt for skip:

If any of the 4 workers fails, continue with the ones that completed.
The merge coordinator should report:
- Successful workers and their changes
- Failed workers and the reason for the failure
- Pending tasks that need manual attention

Strategy 3: Manual intervention

If the failure requires a human decision, the flow pauses so you can intervene.

Flow with manual intervention:

Worker A → success ✅
Worker B → conflict that requires a decision ⚠️
  ↓
Claude: "Worker B found a conflict in src/shared/utils.py.
         Two options:
         1. Keep the current implementation
         2. Rewrite with the new pattern
         Which do you prefer?"
  ↓
You: "Option 2"
  ↓
Continue with merge

When to use manual intervention:

  • The failure involves an architecture or business decision
  • Automatic resolution could introduce errors
  • The cost of being wrong is high (production, user data)

Strategy 4: Graceful degradation

Design the flow so it works even if some subagents fail, producing a lower-quality but functional result.

Flow with graceful degradation:

4 workers to refactor error handling:
  auth worker     → success ✅ (migrated to custom exceptions)
  products worker → fails ❌ (stays with HTTPException)
  orders worker   → success ✅ (migrated to custom exceptions)
  notif worker    → success ✅ (migrated to custom exceptions)

Result: 3 of 4 modules migrated.
The products module stays functional with the previous pattern.
There's no inconsistency that breaks anything — only style inconsistency.

To enable graceful degradation, design each worker's changes to be self-contained — so the module works correctly with or without the refactor applied.


Monitoring Parallel Execution

Observing background subagents

When you launch subagents in the background, Claude Code shows progress indicators. You don't see the detailed output in real time, but you know they're running.

If you need more visibility, use the environment variable for debugging:

CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 claude

This forces all subagents to run in foreground, sequentially. You see each step in real time. It's slower but more transparent.

When to use debugging mode

SituationRecommended mode
Normal development with known subagentsBackground (parallel)
Developing a new subagentForeground (debugging)
A subagent fails silentlyForeground (debugging)
Verifying a subagent's permissionsForeground (debugging)
Production / daily useBackground (parallel)
Presentation or demonstrationForeground (visible)

Post-execution diagnosis

When a parallel subagent produces an unexpected result, these diagnostic steps help:

1. Verify the result:

Did the subagent produce output? → If not, it probably failed on permissions
Is the output in the expected format? → If not, the system prompt needs adjustment
Is the output complete? → If not, it probably reached maxTurns

2. Verify the files:

git diff --stat
git diff --name-only

If you expected changes in 5 files and only see 3, the subagent didn't finish or didn't have permission to edit the other 2.

3. Re-run in foreground:

Run the module-worker for src/auth/ in foreground (not background).
I want to see each step it takes.

This shows you exactly what it did, which tools it used, and where it stopped.


Resilient Design: Fail-Fast vs Resilient

Fail-fast: stop at the first error

In fail-fast, if a subagent fails, the whole flow stops. It's the most conservative strategy.

Fail-fast prompt:
"Run the 4 workers in parallel. If ANY worker fails or
produces a partial result, STOP everything. Don't merge partial
results. Report what failed and why."

Advantages:

  • No partial changes are applied that could be inconsistent
  • Easy to reason about — everything works or nothing works
  • Safe for tasks where consistency is critical

Disadvantages:

  • A trivial failure in a non-critical worker stops everything
  • You have to re-run everything, including workers that already completed
  • Inefficient if failures are common

Resilient: continue despite errors

In resilient, the workers that fail are skipped and the successful ones are merged.

Resilient prompt:
"Run the 4 workers in parallel. If any worker fails:
1. Continue with the others
2. The merge coordinator receives the available results
3. The final report lists which workers completed and which failed
4. The tasks of failed workers are listed as PENDING"

Advantages:

  • Maximizes the completed work
  • An isolated failure doesn't invalidate all the progress
  • Efficient for tasks where partial completion is useful

Disadvantages:

  • The result can be inconsistent if the failed workers had dependencies
  • Requires a merge coordinator that handles partial results
  • More complex to reason about

When to use each approach

ScenarioFail-fastResilient
Database migration✅
Code refactor✅
Security fixes✅
Documentation update✅
Shared interface change✅
Refactor of independent modules✅
Changes that must be atomic✅
Incremental improvements✅

The rule: if the inconsistency can cause bugs or data loss, use fail-fast. If the inconsistency is only style or completeness, use resilient.


Error Handling Patterns in Prompts

Pattern 1: Explicit error reporting

Instruct each worker to report errors in a structured format:

## Error Handling in System Prompt

If you encounter ANY error during execution:
1. DO NOT silently skip the problematic area
2. Report the error in your output under "### Errors Encountered"
3. Include: what you tried, what happened, and what remains undone
4. Continue with other tasks if possible

### Output Format
...
### Errors Encountered
- **[task]** — Error: [description] — Impact: [what wasn't done]

Pattern 2: Pre-flight checks

Before the parallel phase, run a verification subagent:

Before launching the 4 workers in parallel, verify:
1. Do the 4 module directories exist?
2. Is there a CLAUDE.md with conventions?
3. Do the tests pass in the current state?
4. Are there uncommitted changes that could interfere?

If any check fails, do NOT proceed with the workers.
Report what's missing and what needs to be fixed.

Pattern 3: Post-merge validation

After the merge, run an automatic validation:

After the merge coordinator finishes:
1. Run the linter on all the modified files
2. Run the project's tests
3. If the linter or the tests fail, report the errors
4. Do NOT try to fix the errors automatically — only report

Pattern 4: Checkpoint before merge

If the workers make significant changes, create a checkpoint before the merge:

Before applying the changes from the 4 workers:
1. Create a backup branch: git checkout -b backup-before-parallel-merge
2. Commit the current state
3. Proceed with the merge
4. If something goes wrong, we can return to the backup

Troubleshooting

"A parallel subagent produces no output"

Cause: The subagent failed silently for lack of permissions.

Diagnosis:

CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 claude

Re-run in foreground. Watch whether Claude asks for permissions that aren't in the subagent's allowlist.

Solution: Add the missing tools to the tools field in the frontmatter.

"A subagent takes much longer than the others"

Cause: The task scope is larger than expected, or the subagent is iterating unnecessarily.

Solution:

  1. Increase maxTurns if the task is legitimately large
  2. Narrow the scope in the system prompt: "Process ONLY the files listed, do not search for additional files"
  3. Use model: haiku for reading/analysis tasks (faster than sonnet)

"The merge coordinator reports inconsistencies that aren't real"

Cause: The coordinator's consistency criteria are too strict or too generic.

Solution: Refine the criteria with concrete examples:

## What counts as inconsistency:
- DIFFERENT error class constructors: AuthError(code, msg) vs ProductError(msg, code)
- DIFFERENT response schemas: {"data": ...} vs {"result": ...}

## What does NOT count as inconsistency:
- Different variable names inside different modules (each module has its own naming)
- Different number of endpoints per module
- Different test file organization

"Some changes are lost during the worktree merge"

Cause: A merge conflict where one version overwrote the other.

Solution:

git log --oneline -10
git diff HEAD~1

If changes were lost, check whether there are unresolved conflicts. To prevent it, make sure each worker edits unique files (no overlap).

"I don't know whether to use retry or skip when a worker fails"

Decision rule:

Is the failure transient (timeout, network error)?
  → RETRY with a higher maxTurns

Is the failure consistent (same error every time)?
  → Is the task critical?
    → Yes: MANUAL INTERVENTION
    → No: SKIP and complete later

Is the failure due to configuration (permissions, tools)?
  → FIX the configuration and RETRY in foreground

Exercises

Exercise 1: Choose maxTurns (Easy)

A subagent needs to: list files in src/api/ (1 turn), read each of the 8 files (8 turns), search for error handling patterns (2 turns), and produce a report (1 turn). What value of maxTurns would you give it?

See solution

Estimate: 1 + 8 + 2 + 1 = 12 turns

With a 25% margin: 12 × 1.25 = 15

maxTurns: 15

15 turns gives room for the subagent to do additional reads if it needs context (imports, parent classes) without reaching the limit.

Exercise 2: Diagnose a failure (Easy)

A background subagent with this frontmatter produces the report "I couldn't apply the changes because I don't have file-editing access":

---
name: quick-fixer
tools: Read, Grep, Glob
background: true
---

What's the problem and how do you fix it?

See solution

Problem: The system prompt asks it to apply changes (edit files), but tools only includes Read, Grep, Glob. It doesn't have Write or Edit. In foreground, Claude would ask for additional permission. In background, the action fails silently.

Fix:

---
name: quick-fixer
tools: Read, Write, Edit, Grep, Glob
background: true
---

Add Write and Edit to the allowlist. In background, these tools are pre-approved.

Exercise 3: Design a fallback strategy (Medium)

You have 4 parallel workers refactoring modules. The "payments" worker fails because the module has circular dependencies that the refactor can't resolve automatically. Design the complete fallback strategy: what happens to the other 3 workers, how the failure is reported, and what's done afterward.

See solution

Strategy: Resilient with manual intervention for the failed module

1. Workers auth, products, orders → complete normally
2. Payments worker → fails, reports: "Circular dependencies between
   payments/processor.py and payments/validator.py prevent automatic refactor"

3. Merge coordinator:
   - Merges the changes from auth, products, orders (the 3 successful ones)
   - Lists payments as PENDING with the reason for the failure
   - Verifies consistency of the 3 merged modules

4. Report to the user:
   "3 of 4 modules refactored successfully.
    PENDING: payments — requires manual resolution of the circular dependency.
    Recommendation: resolve the circular dependency first, then re-run
    the payments worker."

5. Manual action:
   - Resolve the circular dependency in payments
   - Re-run only the payments worker (not the other 3)
   - Verify consistency with the already-merged modules

Exercise 4: Implement pre-flight checks (Medium)

Write the complete prompt for a pre-flight check subagent that verifies 5 conditions before launching a parallel refactor of 4 modules. If any fails, the refactor must not proceed.

See solution
Before launching the 4 refactoring workers, run these checks:

1. DIRECTORIES: Verify that src/auth/, src/products/,
   src/orders/, src/notifications/ exist

2. GIT STATUS: Verify that there are no uncommitted changes
   (git status --porcelain must be empty)

3. TESTS: Run the test suite and verify that they all pass
   (we don't want to refactor over code with broken tests)

4. CLAUDE.MD: Verify that CLAUDE.md exists with at least one
   code conventions section

5. DEPENDENCIES: For each module, verify that it doesn't import
   directly from another module that's going to be refactored
   (grep "from src.auth" in products, orders, notifications)

For each check, report PASS or FAIL with detail.
If ANY check is FAIL, do NOT proceed with the workers.
Report what should be fixed first.

Only if the 5 checks are PASS, proceed with the 4 workers
in parallel.

Exercise 5: Choose fail-fast vs resilient (Hard)

For each scenario, decide whether you'd use fail-fast or resilient, and justify:

  1. Migrate authentication from sessions to JWT in 4 microservices
  2. Add logging to 6 independent modules
  3. Update imports after a package rename in 8 files
  4. Refactor the database schema across 3 related tables
See solution

1. Auth migration → FAIL-FAST The 4 microservices must use the same auth mechanism. If one fails and stays with sessions while the others migrate to JWT, there's inconsistency that breaks the communication between services.

2. Logging → RESILIENT Adding logging is additive and doesn't affect functionality. If 4 of 6 modules end up with logging and 2 don't, the system works perfectly — you just have less observability in 2 modules.

3. Import rename → FAIL-FAST If 6 of 8 files update the import and 2 don't, those 2 files produce an ImportError at runtime. The system is broken. It must be all or nothing.

4. DB schema → FAIL-FAST The 3 tables are related (foreign keys). If one table is refactored and the others aren't, the FK constraints can break. Schema changes must be atomic.

Pattern: If the inconsistency breaks functionality → fail-fast. If the inconsistency is only cosmetic or about completeness → resilient.

Exercise 6: Design a complete resilient flow (Hard)

Design a "code quality improvement" flow for a project with 5 modules. Each module needs: add type hints, improve docstrings, and standardize error handling. Design the complete flow with a fallback strategy, pre-flight checks, post-merge validation, and manual intervention triggers.

See solution
PHASE 0 — PRE-FLIGHT (sequential)
  pre-flight-checker:
  - [ ] 5 modules exist
  - [ ] Tests pass (baseline)
  - [ ] Git clean (no uncommitted changes)
  - [ ] CLAUDE.md has type hints and docstring conventions
  → If FAIL: report and STOP

PHASE 1 — PARALLEL WORKERS (5 workers with worktree)
  For each module: type-hints + docstrings + error-handling

  Fallback per worker:
  - maxTurns: 25 (with margin)
  - If timeout: mark as PARTIAL, continue with the others
  - If permission error: mark as FAILED, continue

  Global strategy: RESILIENT
  - Minimum acceptable: 3 of 5 workers successful
  - If < 3 successful: FAIL-FAST the whole flow

PHASE 2 — MERGE COORDINATOR (sequential)
  Verify:
  - Same type hint style (Optional[X] vs X | None)
  - Same docstring format (Google vs NumPy)
  - Same exception pattern

  If inconsistencies: report and recommend a fix
  If consistent: proceed

PHASE 3 — POST-MERGE VALIDATION (sequential)
  - Run mypy (verify type hints)
  - Run tests (no regressions)
  - Run the linter

  If mypy fails: report type errors
  If tests fail: ROLLBACK (git reset, report)
  If linter fails: report warnings (don't block)

PHASE 4 — REPORT
  - Completed modules: [list]
  - Partial modules: [list with reason]
  - Failed modules: [list with reason]
  - Validation: [mypy, tests, linter results]
  - Manual TODO: [pending tasks to complete]

MANUAL INTERVENTION TRIGGERS:
  1. Pre-flight: module doesn't exist → verify the project structure
  2. Worker: circular dependency → resolve manually
  3. Merge: pattern inconsistency → decide which one to adopt
  4. Validation: tests fail → analyze whether it's a regression or a flaky test

Summary

  • maxTurns is the subagents' timeout — estimate the needed turns and add a 25% margin
  • Background subagents have pre-approved permissions from the tools allowlist — if a tool is missing, the action fails silently
  • Four fallback strategies: retry (transient), skip (non-critical), manual intervention (human decision), graceful degradation (functional partial result)
  • Fail-fast for tasks where inconsistency breaks functionality; resilient for tasks where partial completion is acceptable
  • Pre-flight checks prevent predictable failures; post-merge validation detects introduced problems
  • CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 for debugging — forces sequential execution in foreground
  • Diagnose failures by checking: did it produce output? correct format? complete? If the answer is "no," re-run in foreground
  • Design idempotent subagents when possible — able to re-run without side effects

Additional Resources

  1. Create Custom Subagents (Anthropic Docs) — Official documentation with maxTurns, background, and permissions
  2. Claude Code Sub-agents — Background Execution — Details of pre-approved permissions and resume in foreground
  3. Claude Code CLI Reference — The CLAUDE_CODE_DISABLE_BACKGROUND_TASKS variable and other debugging flags
  4. Claude Code Best Practices — Error handling and robust delegation patterns
  5. Claude Code Tips and Tricks — Tips for monitoring and debugging
  6. Prompt Engineering: Be Clear and Direct — Clear instructions for error reporting in system prompts
  7. Git Reset Documentation — Reference for rolling back changes when validation fails
  8. Claude Models Documentation — Context windows and per-model limitations

Next capsule: In capsule 05 you'll build the complete project — a parallel refactor of 4 modules with workers isolated in worktrees, a merge coordinator that verifies consistency, pre-flight checks, error handling, and post-merge validation. Everything you learned in capsules 02, 03, and 04 is integrated into a functional end-to-end flow.