perf(api,desktop,host-service): cut api traffic that no one needed
Merged
SA
1mo ago+160+160+012345678901234567890123456789-402-402-012345678901234567890123456789
What & why
The Vercel bill is on track for ~$5.3k this month, and the api project is 93% of it. A sample of 5,000 production requests showed a lot of that traffic isn't needed:
| Change | Share of api requests | What was wrong |
|---|---|---|
proxy.ts matcher narrowed | invocations were 2× requests | It ran on every request only to add CORS headers. It now covers only paths a browser fetches (/api/trpc, /api/auth/* except jwks and oauth2/token, /api/desktop/version, /api/proxy/linear-image). Webhooks, host-service, the CLI, the SDK and MCP skip it. |
| Token refresh loop (host-service) | ~9% | Once a refresh failed, refreshNeeded stayed true, so a host with a dead login retried the rejected refresh token on every api call, forever. Every sampled oauth2/token response with a recorded status was 400 or 429. A token rejected with 400 or 401 is no longer sent until the config holds a new one; network errors, 429s and 5xx still retry. |
/api/auth/jwks CDN-cacheable | ~8% | Cache-Control: public, s-maxage=300, stale-while-revalidate=3600. The relay's Workers isolates keep refetching the key set, and each fetch ran a function. We never rotate the RS256 key, so 5 minutes of staleness is safe. Depends on the proxy.ts change: before it, the proxy ran on jwks too. |
getByBranches gated | ~6% | It polled every 30s from every desktop, even with no cloud workspace rows to show. It now runs only when there are refs. The 30s poll stays for those users; moving it to a realtime nudge is a separate discussion. |
| Automations badge removed | ~6% | The sidebar header polled automation.latestRuns + automation.list every 2 min and on every window focus, in every window. The badge is gone; clicking Automations goes straight to the page on a paid plan and otherwise fetches the list once to decide on the paywall. The getting-started card stops its 60s poll, and useFailedAutomations moves next to the automations page, its only user. The seen-failures store is deleted and automation-failures-v1 is added to DEAD_KEYS. |
Separately (not in this PR): the Sazabi log drains were switched to 5% head sampling. They had been costing ~$775/month.
How I tested it
- Checked the new
proxy.tsmatcher with Next'sunstable_doesMiddlewareMatchagainst 17 real paths (tRPC batches,get-session,token,accept-invitation,oauth2/consentproxied;jwks,oauth2/token, webhooks,process-delivery,/mcp,/api/v2/agent,/.well-knownskipped). All matched as intended. - New
ConfigFileSessionTokenSourcetests: a rejected refresh token is sent once across three attempts, a re-login gets through, and a 503 still retries. Against the old code the first test fails (3 sends). bun run lint, typecheck for desktop, api and host-service, 375 desktop tests in the affected areas, host-service auth tests, andcheck:i18n(removes only the three badge strings).- Not yet clicked through in the app. Worth checking on the preview deployment:
- The web app and the desktop renderer still reach the api: sign-in, tRPC, and accepting an invitation.
- The Automations nav item on a free and a paid org.
curl -I .../api/auth/jwksshowsx-vercel-cache: HITon the second request.
Checklist
- PR title follows conventional commits (
type(scope): subject) -
bun run lintandbun run typecheckpass (CI fails on lint warnings too) - "Allow edits from maintainers" is checked on fork PRs
Summary by cubic
Cuts avoidable api traffic driving a ~$5.3k/month Vercel bill, with the api project at 93% of it.
API and host-service
- The
proxy.tsmatcher now covers only browser-fetched paths; it previously ran on every request to add CORS headers, billing each as two invocations. /api/auth/jwksis now CDN-cacheable for 5 minutes instead of being refetched constantly by the relay's Workers isolates.- host-service stops resending a refresh token the api rejected (400/401) until the config holds a new one; network errors, 429s and 5xx still retry.
Desktop
getByBranchespolls only when there are refs to show, not every 30s from every desktop regardless.- Removed the sidebar automations failure badge that polled
latestRuns+listevery 2 minutes and on every window focus; the paywall now fetches the list once on click, the getting-started card stops its 60s poll, and the seen-failures store is deleted.
Summary by CodeRabbit
- Bug Fixes
- Expired sessions now prompt you to sign in again when the server rejects a refresh token, while temporary server errors can still be retried.
- Automation navigation handles access checks and unavailable automation lists more reliably.
- Changes
- Failed-run count badges and failure-count labels are no longer shown in the Automations sidebar.
- Latest automation run information refreshes periodically.
- Successful authentication key requests are cached to help responses load efficiently.