Summary
#1507 added /api/workflows to AI_ROUTE_PREFIXES in apps/web/src/proxy.ts. isAiRoute classifies by path prefix only, so the frequently-polled GET status endpoint is metered against the AI budget (UVAI_AI_RATE_LIMIT_PER_MINUTE, default 12/min) alongside the POST that starts a run.
pollVideoToActions in apps/web/src/lib/studio-workflow.ts polls at attempts = 20, delayMs = 1500 — 40 requests/min. The start POST shares the same bucket, so one run issues 21 AI-class requests inside a ~30s window.
The 12th request 429s, roughly 17 seconds into a 30-second poll window — before a run that does a transcript fetch plus an action-agent call can plausibly finish. Studio reaches its Still running after N polls branch on essentially every run, so "Act on findings" reads as broken rather than slow.
Why it is worse than one endpoint being throttled
The rate-limit bucket is keyed by class, not by path:
const routeClass = isAiRoute(pathname) ? 'ai' : 'api';
const key = `${routeClass}:${clientIp}`;
Every entry in AI_ROUTE_PREFIXES shares one ai:<ip> counter. A single Studio run therefore drains the allowance that /api/chat, /api/transcribe, /api/pipeline, /api/extract-events and /api/agents/dispatch draw from, for the remainder of the 60s window. A user who clicks the new button then finds chat and transcription 429ing as collateral.
Current state
Present on main as of 5135f4e (#1507, merged 2026-08-07 21:22 UTC). The finding was raised on #1507 by the Vercel Agent review and in a follow-up comment, but the PR merged without it addressed.
Acceptance criteria
Notes
isAiRoute currently lives in proxy.ts, which imports next/server and so is awkward to unit test. apps/web/src/lib/auth-paths.ts exists precisely as the home for path policy "free of Next.js request types so vitest can import it offline" and already hosts the sibling shouldSkipRateLimit. Moving the classifier there makes it testable without a Next.js request harness.
Summary
#1507 added
/api/workflowstoAI_ROUTE_PREFIXESinapps/web/src/proxy.ts.isAiRouteclassifies by path prefix only, so the frequently-polled GET status endpoint is metered against the AI budget (UVAI_AI_RATE_LIMIT_PER_MINUTE, default 12/min) alongside the POST that starts a run.pollVideoToActionsinapps/web/src/lib/studio-workflow.tspolls atattempts = 20,delayMs = 1500— 40 requests/min. The start POST shares the same bucket, so one run issues 21 AI-class requests inside a ~30s window.The 12th request 429s, roughly 17 seconds into a 30-second poll window — before a run that does a transcript fetch plus an action-agent call can plausibly finish. Studio reaches its
Still running after N pollsbranch on essentially every run, so "Act on findings" reads as broken rather than slow.Why it is worse than one endpoint being throttled
The rate-limit bucket is keyed by class, not by path:
Every entry in
AI_ROUTE_PREFIXESshares oneai:<ip>counter. A single Studio run therefore drains the allowance that/api/chat,/api/transcribe,/api/pipeline,/api/extract-eventsand/api/agents/dispatchdraw from, for the remainder of the 60s window. A user who clicks the new button then finds chat and transcription 429ing as collateral.Current state
Present on
mainas of5135f4e(#1507, merged 2026-08-07 21:22 UTC). The finding was raised on #1507 by the Vercel Agent review and in a follow-up comment, but the PR merged without it addressed.Acceptance criteria
GET/HEADon/api/workflowsis not metered against the AI budget.POSTto/api/workflows/video-to-actionsstays AI-class — it does real model work.GETcarve-out across all AI routes.test-frontend, added in ci: run apps/web vitest as required test-frontend job (#1449) #1480), including a control proving the fix does not over-exempt.Notes
isAiRoutecurrently lives inproxy.ts, which importsnext/serverand so is awkward to unit test.apps/web/src/lib/auth-paths.tsexists precisely as the home for path policy "free of Next.js request types so vitest can import it offline" and already hosts the siblingshouldSkipRateLimit. Moving the classifier there makes it testable without a Next.js request harness.