Claude Batch Partial Failure Handling — Fix (2026) | AI Error Hub
Anthropic Claude Message Batches Severity: Medium Batch results

Claude Batch Partial Failure — Some Requests Errored

Your batch completed with a mix of statuses: some succeeded, some errored, some canceled or expired. Batches ALWAYS end with per-request statuses — never assume all-or-nothing. Iterate results, retry errored ones in a new batch, and track completion via custom_id.

Error surface: Batch results Category: Message Batches Result types: succeeded, errored, canceled, expired Last tested: Nov 15, 2026
Quick Fix (TL;DR)

Iterate batches.results.list(batch_id), check each result.type (succeeded / errored / canceled / expired), extract data or error per case, and re-submit errored requests in a new batch. Always reconcile by custom_id. Log per-request status for post-mortem.

The Symptoms You're Seeing

Batch result patterns
batch.request_counts:
  succeeded: 87500
  errored:   1200
  canceled:  0
  expired:   0

Some downstream records show empty results
  → You extracted only succeeded, dropped errored

Rate limit errors on ~2% of requests
  → Some batches hit per-org concurrent limits during processing

Silent data loss
  → Team assumed batch completed = all succeeded

Cost anomaly (retried succeeded requests too)
  → No idempotency in retry logic; charged twice

Result Type Reference

result.typeMeaningRetry?
succeededRequest completed; result.message contains responseNo
erroredRequest failed; result.error has detailsYes, if transient
canceledBatch was manually canceled before this request ranYes
expired24-hour SLA exceeded; not processedYes, in new batch

What Actually Causes Errors In Batches

35%
Rate limit hits during processingBatch spread requests over hours; some hit per-minute caps.
22%
Malformed individual requestsOne bad request in 100K; validation fails at processing time.
18%
Content policy hitsSome requests trigger safety filters; return refusal.
12%
Context window exceeded per-requestSome rows have longer input than model supports.
8%
Transient upstream errorsBrief provider hiccups; usually retry-safe.
5%
Batch expiration (24h)Very large batches queued behind others; some expire.

Fixes That Work (Tested Nov 2026)

1Iterate All Results, Classify by Type

Python · complete result iteration
import anthropic client = anthropic.Anthropic() def process_batch_results(batch_id): succeeded = {} errored = {} canceled = {} expired = {} for item in client.messages.batches.results.list(batch_id): cid = item.custom_id if item.result.type == "succeeded": succeeded[cid] = { "message": item.result.message, "tokens": item.result.message.usage.output_tokens } elif item.result.type == "errored": errored[cid] = { "error_type": item.result.error.type, "message": item.result.error.message } elif item.result.type == "canceled": canceled[cid] = {"reason": "batch_canceled"} elif item.result.type == "expired": expired[cid] = {"reason": "24h_sla_exceeded"} return succeeded, errored, canceled, expired # Report s, e, c, x = process_batch_results("batch_abc123") print(f"Succeeded: {len(s)}, Errored: {len(e)}, " f"Canceled: {len(c)}, Expired: {len(x)}")

2Retry Errored Requests With Classification

Python · smart retry logic
RETRYABLE_ERROR_TYPES = { "rate_limit_error", "overloaded_error", "api_error", "timeout_error", } TERMINAL_ERROR_TYPES = { "invalid_request_error", # fix the request "authentication_error", "permission_error", "not_found_error", "content_filter_blocked", # won't fix on retry } def build_retry_batch(errored, canceled, expired, original_requests): """Build new batch of retryable failures.""" retry = [] permanent = [] for cid, err_info in errored.items(): if err_info["error_type"] in RETRYABLE_ERROR_TYPES: retry.append(original_requests[cid]) elif err_info["error_type"] in TERMINAL_ERROR_TYPES: permanent.append({"cid": cid, "error": err_info}) else: # Unknown — retry once, cautiously retry.append(original_requests[cid]) # canceled + expired are always retryable for cid in canceled: retry.append(original_requests[cid]) for cid in expired: retry.append(original_requests[cid]) return retry, permanent # Submit retry batch retry_reqs, permanent_failures = build_retry_batch(e, c, x, orig_map) if retry_reqs: retry_batch = client.messages.batches.create(requests=retry_reqs) print(f"Retry batch: {retry_batch.id} with {len(retry_reqs)} requests") if permanent_failures: save_for_manual_review(permanent_failures)

3Full Reconciliation Pipeline

Python · production pattern
def run_batch_with_retries(requests, max_retries=3): """Run batch with automatic retry of failures.""" orig_map = {r["custom_id"]: r for r in requests} all_succeeded = {} remaining_requests = requests for attempt in range(max_retries): batch = client.messages.batches.create(requests=remaining_requests) print(f"Attempt {attempt+1}: batch {batch.id}, " f"{len(remaining_requests)} requests") wait_for_batch(batch.id) s, e, c, x = process_batch_results(batch.id) all_succeeded.update(s) print(f" → {len(s)} succeeded ({len(all_succeeded)} total)") retry, permanent = build_retry_batch(e, c, x, orig_map) if permanent: save_for_manual_review(permanent) if not retry: break # nothing more to try remaining_requests = retry # Exponential backoff between retry batches time.sleep(60 * (attempt + 1)) return all_succeeded
Idempotency via custom_id

Use STABLE custom_ids across retry attempts. If you assign fresh IDs on retry, you can't track "already succeeded" and will re-run good requests. Always: custom_id = deterministic_from_source_record. E.g., user_42_summary_v1.

Preventing This Error Going Forward

  • Never assume batches are all-or-nothing. Always iterate results.
  • Classify errors: retryable vs terminal. Retry only what makes sense.
  • Use stable custom_ids. Idempotent retries; no double-billing.
  • Cap retry attempts (3 typical). Beyond that, human review.
  • Save permanent failures separately. Track for manual investigation.
  • Backoff between retry batches. Rate limits often clear in an hour.
SK
Tested by Sana K.
Researcher · AI Error Hub
Last verified: Nov 15, 2026 · SDK: anthropic 0.42

Frequently Asked Questions

Depends on error type. Requests that Claude processed and returned errors for count as billable tokens. Requests that failed validation without model invocation are not billed. Rate limit errors typically don't bill (though behavior varies). Check your invoice against your success count.

Yes: batches.cancel(batch_id). In-flight requests may still complete; requests not yet started are marked canceled in results. Useful when you realize you sent the wrong data.

All requests marked expired. Retry in a new batch, ideally after checking why: was there a queue congestion, was the batch too large, was your account quota exceeded? Anthropic status page usually explains.

Anthropic enforces uniqueness only within a single batch. Across batches, IDs can repeat. That's your problem — track "which batch produced which custom_id" if you need cross-batch reconciliation.

Cache doesn't help within a single batch (each request runs independently, no reuse). But if your batches share a common prefix across MULTIPLE batches processed sequentially within cache TTL, then yes. Rare optimization; ignore for most workloads.