Skip to content

Reject banned users at freebuff session endpoints so queueDepth stops flickering#533

Merged
jahooma merged 1 commit intomainfrom
jahooma/waiting-room-flicker
Apr 22, 2026
Merged

Reject banned users at freebuff session endpoints so queueDepth stops flickering#533
jahooma merged 1 commit intomainfrom
jahooma/waiting-room-flicker

Conversation

@jahooma
Copy link
Copy Markdown
Contributor

@jahooma jahooma commented Apr 22, 2026

Summary

  • Banned bots with valid API keys were POSTing /api/v1/freebuff/session every few seconds and re-queuing between the 15s admission-tick evictBanned sweeps, causing the user-facing queueDepth/position counter to jump between ticks.
  • Added a terminal banned session status (HTTP 403, mirroring country_blocked) so banned accounts never create a queued row; CLI shows an "Account unavailable" screen and stops polling.
  • Filtered banned users out of queueDepthsByModel and queuePositionFor so the displayed position/depth stays stable even if a banned row briefly exists.

Test plan

  • Added unit tests: POST and GET return 403 banned without creating a queue row
  • Added unit test: requestSession / getSessionState short-circuit on userBanned
  • Manually verify CLI renders "Account unavailable" message for a banned account

🤖 Generated with Claude Code

… flickering

Banned bots with valid API keys were POSTing /session every few
seconds and re-entering the queue between the 15s admission-tick
`evictBanned` sweeps, making the user-facing queue counter jump
between ticks. Add a terminal `banned` status (403, mirroring
`country_blocked`) so banned accounts never create a queued row, and
filter banned users out of `queueDepthsByModel` / `queuePositionFor`
so the displayed position/depth stays stable.
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR fixes a queueDepth/position counter flickering bug caused by banned bots with valid API keys repeatedly POSTing to the freebuff session endpoint and re-entering the queue between the 15-second evictBanned sweeps. The fix operates at two levels: early rejection (banned users now receive a terminal HTTP 403 banned response before any DB row is created) and metric filtering (queueDepthsByModel and queuePositionFor now exclude banned rows via a NOT EXISTS subquery, keeping counters stable for legitimate users even during the inter-tick window).

Key changes:

  • New { status: 'banned' } terminal variant added to FreebuffSessionServerResponse (shared type, no wire-compat break).
  • resolveUser fetches the banned field from getUserInfoFromApiKey and propagates userBanned through handlers → requestSession / getSessionState.
  • Both session public-API functions short-circuit before any DB read/write when userBanned is true.
  • queueDepthsByModel and queuePositionFor in store.ts add correlated NOT EXISTS subqueries matching the pre-existing pattern in evictBanned.
  • CLI: use-freebuff-session treats banned as terminal (stops polling); waiting-room-screen renders an "Account unavailable" message; app.tsx routes banned to the waiting-room surface rather than the chat surface.
  • Test coverage added at both the HTTP handler layer and the public-API unit layer, verifying no queue row is created and the correct 403 status is returned.

One minor observation: the country-block check in _handlers.ts runs before the banned check inside requestSession, so a user who is simultaneously banned and country-blocked will receive country_blocked instead of banned. This is a pre-existing ordering choice and has no functional impact on the queue stability fix.

Confidence Score: 5/5

Safe to merge — the change is additive, well-tested, and doesn't touch any hot paths for non-banned users.

All changed paths have unit tests; the new banned status is terminal at every layer (type, server, CLI) so there is no risk of an unhandled state. The NOT EXISTS subquery is structurally identical to the pre-existing evictBanned query that is already in production. Non-banned users see no change in behaviour.

No files require special attention — store.ts warrants a look to confirm the intentional omission of banned filtering from the single-model queueDepth function (used for admission-tick observability after evictBanned has already run).

Important Files Changed

Filename Overview
web/src/server/free-session/store.ts Adds NOT EXISTS banned-user subquery to queueDepthsByModel and queuePositionFor so user-facing counters are stable between evictBanned ticks; the standalone queueDepth (used only for admission-tick metrics after eviction) is intentionally not updated.
web/src/server/free-session/public-api.ts Short-circuits requestSession and getSessionState with { status: 'banned' } before any DB writes or reads when userBanned is true, cleanly preventing queue row creation.
web/src/app/api/v1/freebuff/session/_handlers.ts Adds banned field to getUserInfoFromApiKey fields list and propagates userBanned into requestSession/getSessionState; returns HTTP 403 for banned status (mirroring country_blocked).
cli/src/hooks/use-freebuff-session.ts Adds banned to the 403-body allowlist and to nextDelayMs terminal cases, stopping polling immediately on a banned response.
common/src/types/freebuff-session.ts Adds { status: 'banned' } as a new terminal variant to FreebuffSessionServerResponse; the type is shared by both server and CLI, keeping the wire contract in sync.

Sequence Diagram

sequenceDiagram
    participant CLI as CLI (banned bot)
    participant Handler as _handlers.ts
    participant PublicAPI as public-api.ts
    participant Store as store.ts
    participant DB as Database

    CLI->>Handler: POST /api/v1/freebuff/session
    Handler->>DB: getUserInfoFromApiKey (fields: id, email, banned)
    DB-->>Handler: { id, email, banned: true }
    Handler->>Handler: resolveUser → { userBanned: true }
    Handler->>Handler: countryBlockedResponse (if not blocked)
    Handler->>PublicAPI: requestSession({ userBanned: true })
    PublicAPI->>PublicAPI: if (userBanned) return { status: 'banned' }
    PublicAPI-->>Handler: { status: 'banned' }
    Handler-->>CLI: HTTP 403 { status: 'banned' }
    Note over CLI: nextDelayMs('banned') → null<br/>Polling stops. Shows Account unavailable

    Note over Store: Every 15s admission tick
    Store->>DB: evictBanned() — removes lingering rows
    Store->>DB: queueDepthsByModel() — NOT EXISTS banned
    Store->>DB: queuePositionFor() — NOT EXISTS banned
    Note over Store: Non-banned users see stable position/depth counters
Loading

Reviews (1): Last reviewed commit: "Reject banned users at freebuff session ..." | Re-trigger Greptile

@jahooma jahooma merged commit b6a8d1b into main Apr 22, 2026
34 checks passed
@jahooma jahooma deleted the jahooma/waiting-room-flicker branch April 22, 2026 01:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant