Skip to content

Commit 0c197e3

Browse files
paradoxboundParadoxboundclaude
authored
fix: retry on 429 with backoff in BookStackClient.request() (#28)
## Summary CI was hitting BookStack API rate limits when multiple pipelines ran concurrently against the same instance (reproduced when opening several PRs in quick succession). `request()` now retries up to **3 times** on a 429 response, honouring the `Retry-After` header (default 10 s) before each attempt. After 3 retries the 429 is thrown as normal. ## Behaviour | Attempt | Response | Action | |---|---|---| | 1 | 429 | Wait `Retry-After` seconds, retry | | 2 | 429 | Wait `Retry-After` seconds, retry | | 3 | 429 | Wait `Retry-After` seconds, retry | | 4 | 429 | Throw (status 429) | | Any | 2xx | Return response | ## Test plan - [ ] Functional tests pass without 429 errors on next CI run - [ ] Unit tests covering retry behaviour added in follow-up PR (`test/unit-tests`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Paradoxbound <paradoxbound@paradoxbound.org> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent e91816e commit 0c197e3

1 file changed

Lines changed: 9 additions & 1 deletion

File tree

packages/core/src/bookstack-client.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class BookStackClient {
3939
path: string,
4040
params?: Record<string, string | number | undefined>,
4141
body?: unknown,
42-
options?: { timeout?: number; returnText?: boolean }
42+
options?: { timeout?: number; returnText?: boolean; _retries?: number }
4343
): Promise<T> {
4444
let url = `${this.baseUrl}/api${path}`;
4545
if (params) {
@@ -66,6 +66,14 @@ export class BookStackClient {
6666
signal: controller.signal,
6767
});
6868
clearTimeout(timeoutId);
69+
if (res.status === 429) {
70+
const retriesLeft = options?._retries ?? 3;
71+
if (retriesLeft > 0) {
72+
const retryAfter = parseInt(res.headers.get('retry-after') ?? '10', 10);
73+
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1000));
74+
return this.request(method, path, params, body, { ...options, _retries: retriesLeft - 1 });
75+
}
76+
}
6977
if (!res.ok) {
7078
const text = await res.text();
7179
let message = text;

0 commit comments

Comments
 (0)