feat: retry rate-limited requests and share pagination logic#218
Conversation
- Retry 429/503 responses in the axios response interceptor, honoring Retry-After (seconds or HTTP-date) with exponential backoff fallback (max 3 attempts, 60s delay cap) - Skip retries for stream bodies, which cannot be replayed - Extract shared collectPaginated helper and use it in getSpaces, getAllComments, getAllAttachments, and getAllProperties - Request only the remaining number of items on the final page instead of a full page
A 503 can be returned by a proxy after the backend already applied a write, so replaying POST/PUT could create duplicate content or mask a successful update with a version conflict. 429 responses are rejected before processing and remain retryable for all methods.
Retry policy rationale: why 429 retries all methods but 503 only readsTwo review rounds flagged replay safety of the retry logic, so recording the reasoning here. 503 → retried only for GET/HEAD/OPTIONS. A 503 commonly comes from a gateway/proxy after the backend timed out, so the write may have already been applied. Replaying a POST could duplicate a page/comment, and replaying a versioned PUT that actually succeeded would fail with a misleading version conflict. This was fixed in 9a422e0. 429 → retried for all methods (including writes), intentionally. Rate limiters reject requests before processing — that is their semantic purpose (RFC 6585), and Atlassian's rate-limiting docs explicitly state that 429'd requests were not processed and are safe to retry. Gating 429 retries to reads would also defeat the primary motivation of this PR: If a real environment surfaces post-commit 429s, the right follow-up would be an idempotency guard, not disabling write retries. |
# [2.18.0](v2.17.1...v2.18.0) (2026-07-19) ### Features * retry rate-limited requests and share pagination logic ([#218](#218)) ([281b9a6](281b9a6))
|
🎉 This PR is included in version 2.18.0 🎉 The release is available on: Your semantic-release bot 📦🚀 |
Summary
Confluence Cloud rate-limits aggressively, and bulk operations (
copy --recursive,getAll*listings) previously hard-failed on the first 429/503, potentially leaving a partially-copied tree. This PR adds transparent retry handling and deduplicates the pagination scaffolding that was copy-pasted across four methods.Rate-limit retry
apiand recursive copy).Retry-Afterheader (both seconds and HTTP-date formats), falling back to exponential backoff (1s → 2s → 4s), max 3 attempts, 60s delay cap.Shared paginator
collectPaginated(fetchPage, { maxResults, pageSize, start })helper replaces the four duplicatedwhile (hasNext)loops ingetSpaces,getAllComments,getAllAttachments, andgetAllProperties.getSpacesalready used). The existingmaxResults: 0= unlimited quirk is preserved.Test plan
maxRetries(attempt count asserted), 400 not retried, stream body not retried, andretryDelayMsunit coverage (Retry-After seconds/date/backoff/cap).getSpaces,getAllPropertiesaccumulation) pass unchanged, confirming parity.