#21514: fix(retry): make retryAsync abort-aware during backoff sleep
size: S
Cluster:
Error Resilience and Retry Logic
## Summary
`retryAsync` currently ignores `AbortSignal` entirely: when a signal fires
mid-backoff the sleep runs to completion, then the next attempt starts
(and succeeds or fails on its own). This means aborting a high-level
operation does not promptly propagate through the retry loop.
This PR wires `AbortSignal` into `retryAsync` with three guard points:
1. **Pre-attempt check** – throws immediately if the signal is already
aborted before the call to `fn()`.
2. **isAbortError short-circuit** – if `fn()` itself throws an abort
error the loop breaks without further retries.
3. **Abort-aware sleep** – replaces the bare `sleep(delay)` with the
existing `sleepWithAbort(delay, signal)` primitive so an in-flight
backoff sleep is cancelled as soon as the signal fires.
### Why this matters
Without this change a cancelled operation can trigger up to
`maxAttempts - 1` additional LLM round-trips (three by default) before
the abort is observed. In a three-layer retry stack
(per-call × per-step × auth-rotation) that multiplies quickly.
### API
The change is fully backwards-compatible: `signal` is an optional field
on the existing `RetryOptions` type, defaulting to `undefined` (current
behaviour unchanged).
```ts
// existing callers — no change required
const result = await retryAsync(fn, { label: "my-op" });
// new: pass an AbortSignal to stop retrying on cancellation
const result = await retryAsync(fn, { label: "my-op", signal: controller.signal });
```
### Implementation notes
- Reuses `sleepWithAbort` from `src/infra/backoff.ts` (already in tree,
already tested) – no new primitives.
- Reuses `isAbortError` from `src/infra/unhandled-rejections.ts` –
consistent with the rest of the codebase.
- Three new tests cover: abort during sleep, pre-aborted signal, and
normal retry path unchanged.
## Test plan
- [x] `pnpm format:check` — 0 issues
- [x] `pnpm tsgo` — 0 errors
- [x] New tests: abort during sleep, pre-aborted signal, normal retry path
- [x] All 11 tests in `src/infra/retry.test.ts` pass
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
Added `AbortSignal` support to `retryAsync` with three guard points: pre-attempt check, abort error short-circuit, and abort-aware sleep using the existing `sleepWithAbort` primitive.
- Wires optional `signal` field into `RetryOptions` (fully backwards-compatible)
- Checks `signal?.aborted` before each attempt and throws `AbortError` immediately
- Short-circuits retry loop when `fn()` throws an abort error via `isAbortError` check
- Replaces bare `sleep(delay)` with `sleepWithAbort(delay, signal)` to cancel in-flight backoff
- Reuses existing `sleepWithAbort` from `src/infra/backoff.ts` and `isAbortError` from `src/infra/unhandled-rejections.ts`
- Three new tests cover abort during sleep, pre-aborted signal, and normal retry unchanged
- Implementation is clean and consistent with codebase patterns
<h3>Confidence Score: 5/5</h3>
- This PR is safe to merge with minimal risk
- The implementation is well-designed with three clear guard points for abort handling. It reuses battle-tested primitives (`sleepWithAbort`, `isAbortError`) already in the codebase, maintains full backward compatibility (optional `signal` field, legacy number-based API unchanged), and includes comprehensive tests covering all three abort scenarios. The change is isolated to retry logic with no ripple effects.
- No files require special attention
<sub>Last reviewed commit: 0333880</sub>
<!-- greptile_other_comments_section -->
<!-- /greptile_comment -->
Most Similar PRs
#16195: feat(infra): add unified retry utility with exponential backoff
by bianbiandashen · 2026-02-14
74.5%
#22411: fix(cron): cancel timed-out runs before side effects
by Takhoffman · 2026-02-21
70.4%
#19284: fix(delivery): treat AbortErrors as failures for retry
by EdGuan · 2026-02-17
69.7%
#7247: fix(telegram): abort stale getUpdates connections after long-poll t...
by JanderV · 2026-02-02
69.1%
#6916: fix(slack): add timeout to file download to prevent DoS (CWE-400)
by hclsys · 2026-02-02
68.0%
#19540: feat: add timeout and exponential backoff retry for frontend API calls
by Mozzzaic · 2026-02-17
67.6%
#10509: fix(telegram): bare abort words bypass debounce + clear buffered me...
by romancircus · 2026-02-06
67.0%
#23497: feat(retry): add retryHttpAsync utility with comprehensive coverage
by thinstripe · 2026-02-22
66.9%
#4086: Test/add backoff tests
by TechWizard9999 · 2026-01-29
66.5%
#23745: fix(resilience): add timeout to unguarded fetch calls in browser su...
by kevinWangSheng · 2026-02-22
66.4%