Skip to content

fix: throw HttpError for non-retryable HTTP failures in updateRef#1158

Open
qozle wants to merge 2 commits intoanthropics:mainfrom
qozle:fix/fileops-non-retryable-http
Open

fix: throw HttpError for non-retryable HTTP failures in updateRef#1158
qozle wants to merge 2 commits intoanthropics:mainfrom
qozle:fix/fileops-non-retryable-http

Conversation

@qozle
Copy link
Copy Markdown
Contributor

@qozle qozle commented Apr 4, 2026

Summary

Fixes #1156. Two updateRef call sites in github-file-ops-server.ts threw plain Error for all non-2xx HTTP responses, so retryWithBackoff retried deterministic failures like 404 (branch not found), 422 (validation failed), and 409 (conflict) — wasting up to 10 seconds per failure.

What changed

Adds HttpError to src/utils/errors.ts:

export class HttpError extends Error implements NonRetryable {
  readonly retryable = false as const;
  constructor(public readonly status: number, message: string) { ... }
}

Both updateRef call sites now throw HttpError for non-403 non-2xx responses. Since HttpError implements NonRetryable, retryWithBackoff skips retries automatically — no shouldRetry option needed at the call sites.

403 intentionally keeps throwing plain Error — the existing comment documents these as intermittent transient GitHub API issues that succeed on retry.

Dependency

This PR builds on #1157 (which introduces NonRetryable and updates retryWithBackoff). It should be merged after #1157. The diff on top of main once #1157 lands is exactly this commit.

Tests

4 new tests in test/retry.test.ts:

  • HttpError has retryable=false and isNonRetryable returns true
  • HttpError exposes status and name
  • HttpError is not retried by retryWithBackoff
  • Plain Error (simulating 403) IS still retried

All 674 tests pass.

qozle added 2 commits April 4, 2026 13:40
…terface

retryWithBackoff retried all errors unconditionally, including deterministic
failures like WorkflowValidationSkipError (401 workflow-not-on-default-branch),
wasting ~35 seconds per occurrence.

Introduces a NonRetryable marker interface in src/utils/errors.ts. Any error
class that sets `retryable = false as const` is automatically skipped by
retryWithBackoff without requiring callers to pass a custom shouldRetry option.
The shouldRetry option is also added to RetryOptions for explicit control.

WorkflowValidationSkipError now implements NonRetryable, fixing the primary
reported case. The shouldRetry check is guarded inside `attempt < maxAttempts`
to prevent spurious "aborting retries" log messages on the final attempt.

Also fixes a gap in prepare.ts: WorkflowValidationSkipError was not caught
there, causing the action to fail instead of skip (run.ts already handled
this correctly). Updates misleading comments in github-file-ops-server.ts
that claimed non-403 errors would not be retried — they were. See anthropics#1156 for
the follow-up fix.

Closes anthropics#1081
The two updateRef call sites in github-file-ops-server.ts threw plain
Error for all non-2xx responses, causing retryWithBackoff to retry
deterministic failures (400, 404, 409, 422) that will never succeed.

Adds HttpError to src/utils/errors.ts. It implements NonRetryable
(retryable = false as const), so retryWithBackoff automatically skips
retries without needing an explicit shouldRetry option at the call sites.

403 errors intentionally keep throwing plain Error — they are documented
as intermittent transient GitHub API issues that succeed on retry.

Closes anthropics#1156
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

retryWithBackoff: non-retryable intent in github-file-ops-server.ts is unenforced

1 participant