Skip to content

Fix LOGIN_TOKEN re-login loop, unthrottled slim /api/adapters, keep /start-trial source - #1122

Merged
helpcodeai merged 1 commit into
mainfrom
fix/login-token-window-adapters-slim-trial-source
Oct 18, 2026
Merged

helpcodeai merged 1 commit into
mainfrom
fix/login-token-window-adapters-slim-trial-source

Conversation

@helpcodeai

Copy link
Copy Markdown
Contributor

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 than proactiveRefreshSeconds left, 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, no expiryJsonPath and no proactiveRefreshSeconds, and their connector_auth_cache rows are rewritten with expires_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:

  • The bundle now records obtainedAt (taken from updatedAt for rows loaded from the DB). The refresh window is min(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.
  • Concurrent getToken() calls for one connector share a single DB read and login. Before, only the login itself was deduplicated.
  • If the early re-login fails while the current token is still valid (a 429 from the vendor, say), the call goes ahead with the current token and the login isn't retried for 30s. Once the token has actually expired, the error is raised as before.
  • One LoginTokenService for the whole app, exported from McpServerModule like OAuth2TokenService. Before, ConnectorsModule had 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: 300 could go into the authConfig of the two 1885Data connectors. I haven't changed anything in production.

2. /api/adapters and the website

  • AdaptersPublicController has @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 on If-None-Match) and Cache-Control: public, max-age=300, stale-while-revalidate=3600.
  • New GET /api/adapters?fields=slim: slug, name, description, region, category, icon, docsUrl, requiredEnvVars, toolCount, version, authType, featured, priority, selfHostOnly, setupKind, envVarMeta reduced to kind/secret, plus enriched (instructions of 200+ characters, the website's isEnrichedAdapter rule). It's 282 KB (70 KB gzipped) for the 467 cloud adapters, against 1.97 MB for the full list. Unknown fields values get a 400.
  • New GET /api/adapters/:slug/instructions returns { slug, instructions } for one marketplace page. It returns 404 for unknown or unlisted slugs.
  • The full GET /api/adapters response is unchanged (same JSON, now with the extra caching headers).

For the website PR: use ?fields=slim in fetchAdapters(), switch isEnrichedAdapter to the enriched flag, fetch the instructions per slug on marketplace/[slug], and keep a last-good copy instead of falling back to STATIC_ADAPTERS on a non-200 response. scripts/generate-connector-guides.mjs can stay on the full list.

3. /start-trial events lost source

METADATA_KEYS in product-event.service.ts didn't include source, so start_trial_viewed and start_trial_checkout_started were stored with only kind. It's kept now, limited to the CHECKOUT_SOURCES values (anything else is dropped, like match). 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.

…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
helpcodeai requested a review from D3nisty as a code owner October 18, 2026 08:25
@helpcodeai
helpcodeai merged commit caa13bf into main Oct 18, 2026
20 of 21 checks passed
@helpcodeai
helpcodeai deleted the fix/login-token-window-adapters-slim-trial-source branch October 18, 2026 08:35
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 18, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant