Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/ingest-auth-no-retry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@vercel/flags-core": patch
---

Stop retrying ingest requests on 401/403 auth errors.

Previously the usage tracker would retry up to 3 times on any non-OK response, including authentication failures. Since auth errors are permanent, retrying them wastes requests. The SDK now returns immediately on 401 or 403 without retrying, matching the fast-fail behavior already used by the stream connection.
36 changes: 36 additions & 0 deletions packages/vercel-flags-core/src/utils/usage-tracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,42 @@ describe('UsageTracker', () => {
expect(requestCount).toBe(3);
});

it('should not retry on 401 response', async () => {
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

fetchMock.mockResolvedValue(new Response(null, { status: 401 }));

const tracker = createTracker();

tracker.trackRead();
await tracker.flush();

expect(fetchMock).toHaveBeenCalledTimes(1);
expect(errorSpy).toHaveBeenCalledWith(
'@vercel/flags-core: Ingest auth error (401), not retrying',
);

errorSpy.mockRestore();
});

it('should not retry on 403 response', async () => {
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});

fetchMock.mockResolvedValue(new Response(null, { status: 403 }));

const tracker = createTracker();

tracker.trackRead();
await tracker.flush();

expect(fetchMock).toHaveBeenCalledTimes(1);
expect(errorSpy).toHaveBeenCalledWith(
'@vercel/flags-core: Ingest auth error (403), not retrying',
);

errorSpy.mockRestore();
});

it('should retry on fetch error and succeed', async () => {
let requestCount = 0;
fetchMock.mockImplementation(async () => {
Expand Down
7 changes: 7 additions & 0 deletions packages/vercel-flags-core/src/utils/usage-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,13 @@ export class UsageTracker {
break; // Break the loop if the request succeeded
}

if (response.status === 401 || response.status === 403) {
console.error(
`@vercel/flags-core: Ingest auth error (${response.status}), not retrying`,
);
return;
}

throw new Error(
`Ingest endpoint responded with status ${response.status} for ${eventsToSend.length} events on request ${response.headers.get('x-vercel-id')}.\n` +
`Response body: ${await response.text().catch(() => null)}`,
Expand Down
Loading