You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix: skip browser OAuth flow when valid token exists in storage (#181)
* fix: skip browser OAuth flow when valid token exists in storage
When OAuth was completed via CLI or another process, the daemon would
still trigger browser OAuth flow even though a valid token existed in
persistent storage. This caused unnecessary OAuth prompts and confusion.
Root cause:
- `HasRecentOAuthCompletion()` only checks in-memory state (per-process)
- Cross-process OAuth completion was not detected
- Browser flow was triggered even when valid token existed
Fix:
- Add direct check for valid persisted token before OAuth flow
- Set `skipBrowserFlow` flag when valid token found
- Skip browser OAuth and return retriable error when flag is set
- Applied to both HTTP and SSE OAuth flows
- Applied to both initial connection and MCP init OAuth errors
Test results:
- Core unit tests pass
- OAuth unit tests pass
- OAuth E2E tests pass (29 tests)
Closes: docs/bugs/oauth-reconnection-after-login.md
🤖 Generated with [Claude Code](https://claude.ai/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add 'Connecting' state notification for better UI feedback
- Add NotifyServerConnecting helper method
- Add StateConnecting case to StateChangeNotifier
- UI now shows "Connecting" badge immediately when reconnection starts
- Triggers SSE event for real-time UI updates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: support logout for servers with discovered OAuth
Previously, ClearOAuthToken() required serverConfig.OAuth != nil,
which failed for servers using discovered OAuth (server-announced OAuth).
This caused a 500 error when clicking "Logout" in the Web UI for servers
like Sentry MCP that use OAuth discovery.
The fix removes the OAuth config requirement check, allowing logout
to work for both explicit OAuth config and discovered OAuth servers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: prevent showing Login and Logout buttons simultaneously
If a server is authenticated, only show Logout button.
Login button should only appear for non-authenticated servers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add connecting status to API response
The backend now returns a 'connecting' field in the server API response,
enabling the Web UI and CLI to show "Connecting" status when a server
is in the process of connecting (e.g., during OAuth flow or reconnection).
Changes:
- contracts/types.go: Add Connecting bool field to Server struct
- contracts/converters.go: Extract connecting field in converter
- management/service.go: Extract connecting field in ListServers
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: use actual server name for proactive token refresh
The proactive token refresh was failing because RefreshManager was
using the storage key format (serverName_hash) instead of the actual
server name when looking up servers for refresh.
Changes:
- Add DisplayName field to OAuthTokenRecord to store actual server name
- Add GetServerName() helper that returns DisplayName if set, falls back
to ServerName for backward compatibility
- Update PersistentTokenStore to store both serverKey and serverName
- Update RefreshManager to use GetServerName() when loading tokens
This fixes the "server not found" error during proactive token refresh.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: show Login button immediately after Logout in Web UI
Add optimistic update to triggerOAuthLogout to immediately set
authenticated = false, ensuring the Login button appears right
away instead of waiting for SSE event refresh.
The issue was that clicking Logout would clear the token but the
UI wouldn't update immediately because it was waiting for the
servers.changed SSE event to trigger a refresh. Now the
authenticated field is cleared optimistically with proper rollback
on error.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: improve OAuth UX with oauth_status for accurate state display
Use oauth_status field for more accurate OAuth state representation:
- Frontend: Update needsOAuth/canLogout logic to use oauth_status
- Login shows when: disconnected + token expired/error/none
- Logout shows when: connected OR has error with valid token
- Hidden states: Login during connecting, Logout when token expired
- Backend: Add oauth_status and token_expires_at to server response
- oauth_status: "authenticated" | "expired" | "error" | "none"
- Calculated from stored token validity in runtime.go
- CLI: Update auth status to show clearer messages
- "Reconnecting (Token Valid)" instead of "Authenticated (Disconnected)"
- "Token Expired (Login Required)" for expired tokens
- API: Add connecting field to OpenAPI Server schema
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: prevent auto-reconnection after explicit OAuth logout
When a user clicks Logout, the server should stay disconnected until
the user explicitly logs in again. Previously, the exponential backoff
reconnection logic would auto-reconnect the server shortly after logout.
This fix introduces a `userLoggedOut` flag in the StateManager that:
- Gets set to true when TriggerOAuthLogout is called (before other operations)
- Prevents ShouldRetry() from returning true while the flag is set
- Gets cleared on StateReady transition (successful connection)
- Gets cleared when TriggerOAuthLogin is called (explicit login)
The order of operations in TriggerOAuthLogout is critical to prevent
race conditions: the flag must be set FIRST before ClearOAuthToken
or DisconnectServer, otherwise reconnection could trigger before
the flag is set.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: make supervisor respect userLoggedOut flag to prevent reconnection
The previous fix (7aaba6a) added the userLoggedOut flag to StateManager
and prevented ShouldRetry() from returning true after explicit logout.
However, the supervisor's reconcile loop was operating at a higher level
and still reconnecting servers by computing ActionConnect for any enabled
server that wasn't connected.
This fix adds IsUserLoggedOut() to the UpstreamInterface and updates
computeReconcilePlan() to check this flag before deciding to reconnect.
Now when a user clicks Logout:
1. StateManager.userLoggedOut flag is set (prevents retry logic)
2. Supervisor sees the server is disconnected but checks IsUserLoggedOut()
3. If true, supervisor sets ActionNone instead of ActionConnect
4. Server stays disconnected until user explicitly logs in again
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix: improve OAuth UX with user_logged_out flag and button visibility
- Add user_logged_out field to API response and contracts for tracking
explicit user logout action
- Update supervisor to respect userLoggedOut flag and prevent auto-reconnection
after explicit logout
- Fix ServerCard.vue button visibility to prevent Login and Logout buttons
from appearing simultaneously when OAuth error present with stored token
- Update CLI auth status to show disabled/logged-out state correctly
- Add OAuth manager tests for token persistence and logout flow
- Update OpenAPI spec with user_logged_out field documentation
The user_logged_out flag captures user intent to logout separately from
connection state and config state, enabling proper UX where:
- After logout: Login button visible, no auto-reconnection
- Connected: Logout button visible
- OAuth error with expired token: Login button visible (not Logout)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* test: update golden file for API contract compliance
Add 'connecting' field to get_servers.json golden file to match
the actual API response. The 'user_logged_out' field is not included
because it uses omitempty and defaults to false.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
After successful OAuth authentication via browser flow, the server remains in "Disconnected" status instead of automatically connecting. Users must manually restart the server to establish a connection.
5
10
@@ -9,82 +14,113 @@ After OAuth login completes successfully:
9
14
2. Server automatically attempts connection using the new token
10
15
3. Server status updates to "Connected" in UI
11
16
12
-
## Actual Behavior
17
+
## Actual Behavior (BEFORE FIX)
13
18
After OAuth login completes:
14
19
1. Token is saved to persistent storage ✅
15
20
2.`RetryConnection` is called but fails with "OAuth authentication required"
16
21
3. Server remains "Disconnected" despite having a valid token
17
22
18
23
## Root Cause Analysis
19
24
20
-
The issue is in `internal/upstream/core/connection.go` lines 1028-1033:
21
-
22
-
```go
23
-
// Check if we have a recently completed OAuth flow that should have tokens
24
-
if oauth.GetTokenStoreManager().HasRecentOAuthCompletion(c.serverName) {
25
-
c.logger.Info("OAuth flow recently completed, tokens should be available",
26
-
zap.String("server", c.serverName))
27
-
// Skip the browser flow since tokens should be available
28
-
}
29
-
```
30
-
31
-
**Problem**: The code logs "Skip the browser flow since tokens should be available" but doesn't actually change the control flow. The browser OAuth flow is still triggered, which fails because OAuth is already complete.
25
+
The issue was in `internal/upstream/core/connection.go`:
32
26
33
-
### Why this happens:
27
+
1.**Cross-process OAuth issue**: When CLI completed OAuth, the daemon's `HasRecentOAuthCompletion()` check returned false because it only checks in-memory state (per-process)
28
+
2.**Missing token pre-check**: The code didn't directly check if a valid persisted token existed before triggering browser OAuth flow
29
+
3.**No flow skip logic**: Even when a valid token was detected, the code only logged the finding but didn't actually skip the browser OAuth flow
0 commit comments