Repository navigation
Fix LOGIN_TOKEN re-login loop, unthrottled slim /api/adapters, keep /start-trial source - #1122
Merged
helpcodeai merged 1 commit intoOct 18, 2026
Conversation
…ial source LOGIN_TOKEN: the early-refresh window was 24h by default, so any token that lives less than a day counted as expiring the moment it was issued and every tool call logged in again. The 1885Data Supabase connectors (tokenTTLSeconds 3600, no proactiveRefreshSeconds) made one password grant per call and Supabase answered a third of them with 429. The window is now capped at a fifth of the token's lifetime (30 s floor); tokens that live five days or more keep the configured window. Concurrent callers share one DB read and one login, a failed early re-login keeps serving the still valid token (30 s backoff), and there is one LoginTokenService for the whole app instead of one per module. /api/adapters: no longer rate limited (the website renders through one IP and got 429s, then fell back to its static list), serialised once per process, sent with a strong ETag (304 on revalidation) and Cache-Control. New ?fields=slim view for the website (282 KB instead of 1.97 MB, under Next's 2 MB data-cache limit) with an `enriched` flag, and GET /api/adapters/:slug/instructions for one marketplace page. The full list is unchanged. Product events: `source` was missing from METADATA_KEYS, so start_trial_viewed and start_trial_checkout_started lost it. Kept now, for the CHECKOUT_SOURCES values only.
helpcodeai
deleted the
fix/login-token-window-adapters-slim-trial-source
branch
October 18, 2026 08:35
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three fixes from the 18 Oct review.
1. LOGIN_TOKEN connectors logged in before nearly every call
getToken()treated a cached token as stale when it had less thanproactiveRefreshSecondsleft, 24h by default. Any token that lives less than a day was stale the moment it was issued, so every call logged in again.Confirmed on the cloud DB (read only): the two 1885Data Supabase connectors use
tokenTTLSeconds: 3600, noexpiryJsonPathand noproactiveRefreshSeconds, and theirconnector_auth_cacherows are rewritten withexpires_at = updated_at + 1h. That gave about 89k password grants for about 91k calls in 72h, and Supabase answered a third of them with 429. 37 of the 41 cached login tokens on the cloud live less than 24h, so the 1885Data connectors weren't the only ones affected.Changes in
login-token.service.ts:obtainedAt(taken fromupdatedAtfor rows loaded from the DB). The refresh window ismin(configured, max(20% of lifetime, min(30s, lifetime/2))). A 1h token is reused for 48 minutes. Tokens that live 5 days or more (the 30 day default TTL, Sorare, ...) keep the configured window unchanged. If the lifetime is unknown, the configured window applies as before.getToken()calls for one connector share a single DB read and login. Before, only the login itself was deduplicated.LoginTokenServicefor the whole app, exported fromMcpServerModulelikeOAuth2TokenService. Before,ConnectorsModulehad its own instance with a separate cache and mutex.Tests: a 1h token reused across 200 calls, renewal inside the window and not before, one login for 25 parallel calls (cold cache and due for renewal), a DB-loaded 1h token after a restart, the fallback and backoff on a refused re-login, and the window function itself.
As a stopgap until this ships,
proactiveRefreshSeconds: 300could go into the authConfig of the two 1885Data connectors. I haven't changed anything in production.2.
/api/adaptersand the websiteAdaptersPublicControllerhas@SkipThrottle(). Every website render reaches the API from the website droplet's IP, so the global 100/min per-IP bucket returned 429 (14.9k in 72h) and the site fell back to its static list of 222 adapters. I didn't exempt the website IP instead because that would hard-code our infrastructure in the app and break on the next droplet move. The endpoint is a GET, the same for every caller, and now cheap: each view is built and serialised once per process (per deployment mode), sent with a strong ETag (304 onIf-None-Match) andCache-Control: public, max-age=300, stale-while-revalidate=3600.GET /api/adapters?fields=slim: slug, name, description, region, category, icon, docsUrl, requiredEnvVars, toolCount, version, authType, featured, priority, selfHostOnly, setupKind, envVarMeta reduced tokind/secret, plusenriched(instructions of 200+ characters, the website'sisEnrichedAdapterrule). It's 282 KB (70 KB gzipped) for the 467 cloud adapters, against 1.97 MB for the full list. Unknownfieldsvalues get a 400.GET /api/adapters/:slug/instructionsreturns{ slug, instructions }for one marketplace page. It returns 404 for unknown or unlisted slugs.GET /api/adaptersresponse is unchanged (same JSON, now with the extra caching headers).For the website PR: use
?fields=sliminfetchAdapters(), switchisEnrichedAdapterto theenrichedflag, fetch the instructions per slug onmarketplace/[slug], and keep a last-good copy instead of falling back toSTATIC_ADAPTERSon a non-200 response.scripts/generate-connector-guides.mjscan stay on the full list.3.
/start-trialevents lostsourceMETADATA_KEYSinproduct-event.service.tsdidn't includesource, sostart_trial_viewedandstart_trial_checkout_startedwere stored with onlykind. It's kept now, limited to theCHECKOUT_SOURCESvalues (anything else is dropped, likematch). Tests cover both events and an unknown value.Checks
Backend: lint (0 errors),
tsc --noEmit, jest 659 suites / 19,777 tests passed. Frontend: lint (0 errors),tsc --noEmit.