Skip to content

fix(oauth): clear the token-refresh backoff when a downstream token is saved - #7004

Open
pedrofrxncx wants to merge 1 commit into
mainfrom
fix/oauth-token-save-backoff-clear-w4
Open

fix(oauth): clear the token-refresh backoff when a downstream token is saved#7004
pedrofrxncx wants to merge 1 commit into
mainfrom
fix/oauth-token-save-backoff-clear-w4

Conversation

@pedrofrxncx

@pedrofrxncx pedrofrxncx commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Bug in apps/api/src/oauth/token-refresh.ts / apps/api/src/api/routes/downstream-token.ts.

refreshAndStore arms a per-connection exponential backoff (refreshBackoff, up to REFRESH_BACKOFF_CAP_MS = 5 minutes) after a failed refresh, so a broken upstream isn't hammered. That map is keyed only by connectionId and, until now, was only ever cleared on a successful refresh or on connection delete (#6980).

Failure scenario: a connection's token refresh keeps failing (upstream blip, or a refresh_token that's dead but not classified permanent) and arms the backoff window. The user then reconnects — the frontend calls POST /connections/:id/oauth-token with a brand-new, never-used access+refresh token. If a proxy request needs a forced refresh (e.g. it just got a 401) before the old backoff window naturally expires, refreshAndStore short-circuits on the stale backoff entry and returns null immediately, without even attempting the new refresh_token. The connection looks broken for up to 5 minutes after the user did everything right to fix it.

Fix: call clearRefreshBackoff(connectionId) right after the token upsert in the oauth-token POST route — a freshly saved token has never failed, so any suppression window is stale.

Regression test: added clears the token-refresh backoff entry on save to downstream-token.integration.test.ts, mocking ../../oauth/token-refresh (same mock.module pattern as delete.test.ts's equivalent case for #6980) and asserting clearRefreshBackoff is called with the connection id after a successful save.

To confirm: bun test apps/api/src/api/routes/downstream-token.integration.test.ts (needs a running Postgres — this laptop has none, so I verified with cd apps/api && bunx tsc --noEmit (clean) and bunx oxlint on both changed files (0 warnings/errors); full CI runs the integration suite.

Does not touch delete.ts/delete.test.ts (already covered by open PR #6980) — this is the reconnect/save path, not the delete path.


Summary by cubic

Clears the token-refresh backoff when a downstream token is saved, so reconnects aren't blocked by a stale suppression window from the previous dead token.

  • Previously, a failed refresh armed a per-connection backoff that could bounce forced refreshes for up to 5 minutes even after a fresh token was saved.
  • The POST /connections/:id/oauth-token route now calls clearRefreshBackoff immediately after the token upsert.
  • Adds a regression test that asserts the backoff is cleared on save.

Written for commit 568eb01. Summary will update on new commits.

Review in cubic

…s saved

Reconnecting a connection (POST /connections/:id/oauth-token) saves a fresh, never-failed token, but left any suppression window from the connection's previous dead token armed for up to 5 minutes, so a forced refresh right after reconnect could still be bounced by stale backoff state.
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