Skip to content

fix: skip background token refresh for disabled/unconfigured MCP clients and guarantee non-nil logger in sync workers - #4848

Merged
Pratham-Mishra04 merged 1 commit into
devfrom
07-02-fix_gates_auto_refresh_of_oauth_tokens_to_only_enabled_clients
Jul 2, 2026
Merged

fix: skip background token refresh for disabled/unconfigured MCP clients and guarantee non-nil logger in sync workers#4848
Pratham-Mishra04 merged 1 commit into
devfrom
07-02-fix_gates_auto_refresh_of_oauth_tokens_to_only_enabled_clients

Conversation

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Summary

Background OAuth token refresh was running indefinitely for tokens whose MCP clients were all disabled or removed. This meant the identity provider was being called on every tick for connections that nothing was actively consuming. This PR restricts GetExpiringOauthTokens to only return tokens whose oauth_config is referenced by at least one enabled MCP client, so background refresh stops for idle connections. When a client is re-enabled or a new one is attached, GetAccessToken handles the inline refresh on first use.

Additionally, the TokenRefreshWorker and PerUserOAuthSweepWorker now guarantee a non-nil logger by falling back to a no-op logger at construction time, removing all the scattered if w.logger != nil guards throughout the sync worker code. Token refresh failures that recur on every tick (transient network errors) are now logged at Debug instead of Error to avoid log spam, since permanent rejections are already surfaced by the oauth_config status flipping to expired.

Changes

  • GetExpiringOauthTokens now includes an EXISTS subquery requiring at least one non-disabled config_mcp_clients row joined through oauth_configs to the token. Tokens with no config, or configs with only disabled clients, are excluded from background refresh.
  • NewTokenRefreshWorker assigns a no-op logger when the caller passes nil, allowing all if w.logger != nil guards in sync.go to be removed unconditionally.
  • Refresh failure logging downgraded from Error to Debug for transient failures; a comment explains that permanent failures are already captured via the markExpiredIfPermanent path.
  • TestGetExpiringOauthTokens_ExcludesTerminalConfigs updated to attach enabled MCP clients so the terminal-status condition remains the sole deciding factor in that test.
  • New test TestGetExpiringOauthTokens_RequiresEnabledClient covers the enabled-client requirement across four scenarios: enabled client, disabled-only client, mixed enabled/disabled clients on a shared config, and a config with no client rows.
  • Shared test helpers seedExpiringTokenFixtures and expiringTokenIDs extracted to reduce duplication between the two test cases.

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./framework/configstore/... -run TestGetExpiringOauthTokens
go test ./framework/oauth2/...
go test ./...

Expected: both TestGetExpiringOauthTokens_ExcludesTerminalConfigs and TestGetExpiringOauthTokens_RequiresEnabledClient pass. Tokens with only disabled clients or no client rows must not appear in the refresh worker's selection.

Breaking changes

  • Yes
  • No

Related issues

Security considerations

Reduces unnecessary outbound calls to identity providers for disabled or detached OAuth connections, limiting credential exposure surface during background refresh cycles.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copy link
Copy Markdown
Collaborator Author

@greptile-apps

greptile-apps Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 4/5

Safe to merge with the minor provider-nil guard asymmetry noted; the missing check is pre-existing and realistic call sites always pass a valid provider.

The query change and logger-guard refactor are correct and well-covered by new tests. The only gap is that NewTokenRefreshWorker still dereferences provider.configStore without first checking provider == nil, inconsistent with the symmetric worker also edited in this PR.

framework/oauth2/sync.go — NewTokenRefreshWorker constructor lacks the provider == nil guard present in NewPerUserOAuthSweepWorker.

Important Files Changed

Filename Overview
framework/configstore/rdb.go Adds EXISTS subquery to GetExpiringOauthTokens requiring at least one non-disabled config_mcp_clients row via JOIN through oauth_configs; logic is correct and consistent with the pre-existing NOT EXISTS guard.
framework/configstore/rdb_oauth2_test.go Fixtures refactored into shared helpers; new test covers four client-presence scenarios; ExcludesTerminalConfigs updated so terminal status is the sole variable. Coverage is thorough.
framework/oauth2/sync.go Logger nil-guard added to both constructors, removing all if-logger-nil guards. NewTokenRefreshWorker still lacks provider == nil check before dereferencing provider.configStore (pre-existing asymmetry with NewPerUserOAuthSweepWorker, which has the guard).

Reviews (2): Last reviewed commit: "fix: gates auto refresh of oauth tokens ..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 870cd87a-6de2-41cc-8710-1e7b101f68d6

📥 Commits

Reviewing files that changed from the base of the PR and between a77ddfd and 95ae623.

📒 Files selected for processing (3)
  • framework/configstore/rdb.go
  • framework/configstore/rdb_oauth2_test.go
  • framework/oauth2/sync.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • framework/configstore/rdb.go
  • framework/configstore/rdb_oauth2_test.go

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved token refresh selection to consider only expiring OAuth tokens referenced by at least one enabled MCP client.
    • Continued excluding tokens tied to terminal OAuth configurations, and tightened handling of missing configuration/client relationships.
    • Made background token refresh and per-user sweep logging more reliable by always using a safe logger.
  • Tests
    • Refreshed and expanded coverage for token eligibility rules around enabled/disabled MCP clients.

Walkthrough

Adds an enabled-MCP-client requirement to GetExpiringOauthTokens with updated test coverage, and changes OAuth2 workers to default nil loggers to a no-op implementation while removing nil-logger guards from worker logging paths.

Changes

OAuth Token Refresh Eligibility

Layer / File(s) Summary
Enabled-client filter in GetExpiringOauthTokens
framework/configstore/rdb.go
Adds an EXISTS constraint so expiring tokens are returned only when their oauth_config is linked to at least one enabled MCP client.
Fixtures and coverage for enabled-client requirement
framework/configstore/rdb_oauth2_test.go
Adds shared seeding and lookup helpers, rewrites the terminal-config test, and adds coverage for enabled, disabled, shared, no-client, and no-config cases.

OAuth2 Worker Logging Cleanup

Layer / File(s) Summary
No-op logger default and refresh logging
framework/oauth2/sync.go
TokenRefreshWorker now substitutes a no-op logger for nil and removes nil-logger guards from start/stop and token refresh logging.
Sweep worker logging cleanup
framework/oauth2/sync.go
PerUserOAuthSweepWorker now also defaults nil loggers to a no-op logger and removes nil-logger guards from start/stop and sweep error logging.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • maximhq/bifrost#3565: Related changes in framework/oauth2/sync.go around OAuth2 worker logging and control flow.
  • maximhq/bifrost#4754: Related changes to GetExpiringOauthTokens and its terminal-status filtering in framework/configstore/rdb.go.

Suggested reviewers: akshaydeo, danpiths, roroghost17

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: enabled-client filtering for token refresh and default no-op loggers in sync workers.
Description check ✅ Passed The description matches the template well, covering summary, changes, type, affected areas, testing, breaking changes, security, and checklist.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 07-02-fix_gates_auto_refresh_of_oauth_tokens_to_only_enabled_clients

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
framework/oauth2/sync.go (1)

161-198: 🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift

PerUserOAuthSweepWorker can panic on nil logger — constructor wasn't updated to match TokenRefreshWorker's fix.

NewTokenRefreshWorker now defaults a nil logger to bifrost.NewNoOpLogger() (lines 25-27), which is what makes it safe to drop the w.logger != nil guards in Start/Stop/refreshExpiredTokens. NewPerUserOAuthSweepWorker was not given the same treatment — it still stores the caller-supplied logger as-is (line 174: logger: logger), including when it's nil (the only nil-check, lines 162-166, only fires when provider/configStore is nil, not when logger is nil).

Since this PR also removes the w.logger != nil guards from PerUserOAuthSweepWorker.Start (line 183), Stop (line 197), sweepExpiredFlows (line 228), and sweepOrphanedTokens (line 242), any caller constructing this worker with provider != nil and logger == nil will panic with a nil-interface method call the moment Start() runs.

🐛 Proposed fix — mirror the TokenRefreshWorker default
 func NewPerUserOAuthSweepWorker(provider *OAuth2Provider, orphanRetention time.Duration, logger schemas.Logger) *PerUserOAuthSweepWorker {
+	if logger == nil {
+		logger = bifrost.NewNoOpLogger()
+	}
 	if provider == nil || provider.configStore == nil {
-		if logger != nil {
-			logger.Warn("per-user OAuth sweep worker not started: provider or config store is nil")
-		}
+		logger.Warn("per-user OAuth sweep worker not started: provider or config store is nil")
 		return nil
 	}
 	return &PerUserOAuthSweepWorker{

Please run this to confirm no current caller relies on passing a nil logger here (which would currently be silently tolerated but crash after this PR):

#!/bin/bash
rg -nP -A3 'NewPerUserOAuthSweepWorker\(' --type=go

Also applies to: 225-248

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/oauth2/sync.go` around lines 161 - 198, `PerUserOAuthSweepWorker`
still stores a nil logger and will panic after the new `w.logger != nil` guards
were removed from `Start`/`Stop`/sweep methods. Update
`NewPerUserOAuthSweepWorker` to mirror `NewTokenRefreshWorker` by defaulting a
nil `logger` to `bifrost.NewNoOpLogger()` before assigning it to the worker
struct. Keep the existing provider/configStore nil handling, and ensure `Start`,
`Stop`, `sweepExpiredFlows`, and `sweepOrphanedTokens` can safely call
`w.logger` without nil checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@framework/oauth2/sync.go`:
- Around line 161-198: `PerUserOAuthSweepWorker` still stores a nil logger and
will panic after the new `w.logger != nil` guards were removed from
`Start`/`Stop`/sweep methods. Update `NewPerUserOAuthSweepWorker` to mirror
`NewTokenRefreshWorker` by defaulting a nil `logger` to
`bifrost.NewNoOpLogger()` before assigning it to the worker struct. Keep the
existing provider/configStore nil handling, and ensure `Start`, `Stop`,
`sweepExpiredFlows`, and `sweepOrphanedTokens` can safely call `w.logger`
without nil checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f8bbdc03-93aa-4ca8-8b44-a704e59d98ea

📥 Commits

Reviewing files that changed from the base of the PR and between 98645f7 and a77ddfd.

📒 Files selected for processing (3)
  • framework/configstore/rdb.go
  • framework/configstore/rdb_oauth2_test.go
  • framework/oauth2/sync.go

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 2, 2026
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-02-fix_gates_auto_refresh_of_oauth_tokens_to_only_enabled_clients branch from a77ddfd to 95ae623 Compare July 2, 2026 13:35
@coderabbitai
coderabbitai Bot requested a review from roroghost17 July 2, 2026 13:36

Pratham-Mishra04 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Jul 2, 1:44 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 2, 1:45 PM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04
Pratham-Mishra04 merged commit 871725b into dev Jul 2, 2026
14 of 15 checks passed
@Pratham-Mishra04
Pratham-Mishra04 deleted the 07-02-fix_gates_auto_refresh_of_oauth_tokens_to_only_enabled_clients branch July 2, 2026 13:45
yangtuooc added a commit to yangtuooc/bifrost that referenced this pull request Jul 2, 2026
* upstream/dev:
  feat(mcp): add per-MCP-server tool execution timeout (maximhq#4472)
  fix: billing on failed responses stream requests anthropic and bedrock (maximhq#4842)
  fix: gemini openai through signature compatibility (maximhq#4810)
  fix: cancelled state in logs (maximhq#4831)
  fix: perplexity responses api compatibility (maximhq#4813)
  docs: clarify two-layer token refresh behavior and disabled-client refresh token expiry (maximhq#4849)
  fix: skip background token refresh for disabled/unconfigured MCP clients and guarantee non-nil logger in sync workers (maximhq#4848)
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.

2 participants