Skip to content

Commit f3a5a58

Browse files
committed
Return 403 for country_blocked so old CLIs back off instead of tight-polling
1 parent 52b8c7f commit f3a5a58

3 files changed

Lines changed: 25 additions & 4 deletions

File tree

cli/src/hooks/use-freebuff-session.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,20 @@ async function callSession(
5050
if (resp.status === 404) {
5151
return { status: 'disabled' }
5252
}
53+
// 403 with a country_blocked body is a terminal signal, not an error — the
54+
// server rejects non-allowlist countries up front (see session _handlers.ts)
55+
// so users don't wait through the queue only to be rejected at chat time.
56+
// The 403 status (rather than 200) is deliberate: older CLIs that don't
57+
// know this status treat it as a generic error and back off on the 10s
58+
// error-retry cadence instead of tight-polling an unrecognized 200 body.
59+
if (resp.status === 403) {
60+
const body = (await resp.json().catch(() => null)) as
61+
| FreebuffSessionResponse
62+
| null
63+
if (body && body.status === 'country_blocked') {
64+
return body
65+
}
66+
}
5367
if (!resp.ok) {
5468
const text = await resp.text().catch(() => '')
5569
throw new Error(

web/src/app/api/v1/freebuff/session/__tests__/session.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ describe('POST /api/v1/freebuff/session', () => {
110110
makeReq('ok', { cfCountry: 'FR' }),
111111
makeDeps(sessionDeps, 'u1'),
112112
)
113-
expect(resp.status).toBe(200)
113+
// 403 (not 200) so older CLIs that don't know `country_blocked` fall into
114+
// their error-retry backoff instead of tight-polling.
115+
expect(resp.status).toBe(403)
114116
const body = await resp.json()
115117
expect(body.status).toBe('country_blocked')
116118
expect(body.countryCode).toBe('FR')
@@ -143,7 +145,7 @@ describe('GET /api/v1/freebuff/session', () => {
143145
makeReq('ok', { cfCountry: 'FR' }),
144146
makeDeps(sessionDeps, 'u1'),
145147
)
146-
expect(resp.status).toBe(200)
148+
expect(resp.status).toBe(403)
147149
const body = await resp.json()
148150
expect(body.status).toBe('country_blocked')
149151
expect(body.countryCode).toBe('FR')

web/src/app/api/v1/freebuff/session/_handlers.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@ import type { NextRequest } from 'next/server'
2020
* the caller's country and it's not on the allowlist, short-circuit with a
2121
* terminal `country_blocked` response so the CLI can show the warning
2222
* screen without ever joining the queue. Null country (VPN / localhost)
23-
* fails open — chat/completions will catch it later if it matters. */
23+
* fails open — chat/completions will catch it later if it matters.
24+
*
25+
* Returns HTTP 403 (not 200) so older CLIs — which don't know the
26+
* `country_blocked` status and would tight-poll on an unrecognized 200
27+
* body — fall into their existing `!resp.ok` error path and back off on
28+
* the 10s error retry cadence. The new CLI parses the 403 body directly. */
2429
function countryBlockedResponse(req: NextRequest): NextResponse | null {
2530
const countryCode = getCountryCode(req)
2631
if (!countryCode) return null
2732
if (FREE_MODE_ALLOWED_COUNTRIES.has(countryCode)) return null
2833
return NextResponse.json(
2934
{ status: 'country_blocked', countryCode },
30-
{ status: 200 },
35+
{ status: 403 },
3136
)
3237
}
3338

0 commit comments

Comments
 (0)