Skip to content

Comments

feat(chat): add provider-scoped OAuth reauth flow#1738

Open
Kitenite wants to merge 1 commit intomainfrom
kitenite/reauth
Open

feat(chat): add provider-scoped OAuth reauth flow#1738
Kitenite wants to merge 1 commit intomainfrom
kitenite/reauth

Conversation

@Kitenite
Copy link
Collaborator

@Kitenite Kitenite commented Feb 24, 2026

Summary

  • split Anthropic credential handling into focused modules with explicit OAuth + API-key credential parsing
  • add OAuth refresh behavior (preflight near expiry, forced refresh on auth failure, timeout handling, in-flight dedupe by config path)
  • wire agent startup and run orchestration through provider-scoped auth retry so only Anthropic uses OAuth retry logic
  • emit structured OAuth reauth error code in stream errors and preserve error codes in message materialization
  • add desktop reauth UX (ChatErrorMessage) with structured-code-first detection and fallback text matching
  • add focused tests across auth refresh, provider dispatch, run orchestration, stream error handling, materialization, and renderer detection

Key Files

  • packages/chat/src/host/auth/anthropic/*
  • packages/chat/src/host/chat-service/agent-manager/agent-manager.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/*
  • packages/chat/src/session-db/collections/messages/materialize.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/*
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatErrorMessage/*

Validation

  • bun test packages/chat/src/host/auth/anthropic/anthropic.test.ts packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/oauth-retry.test.ts packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/provider-auth-retry.test.ts packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-oauth.test.ts packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-options.test.ts packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-stream.test.ts packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent.test.ts packages/chat/src/session-db/collections/messages/materialize.test.ts apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/oauth-error.test.ts
    • result: 78 pass, 0 fail
  • bunx biome check on touched files
  • bunx tsc -p packages/chat/tsconfig.json --noEmit

Notes

  • left existing unstaged local bun.lock change out of this PR commit
  • apps/desktop full tsc currently reports unrelated pre-existing route generation/type issues in this branch

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced chat error messaging with improved visual feedback and action buttons for error resolution.
    • OAuth token auto-refresh capability for seamless re-authentication without manual intervention.
    • Support for agent thinking mode with configurable token budget for extended reasoning.
  • Bug Fixes

    • Improved error recovery in chat with OAuth-aware re-authentication flows and retry logic.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

📝 Walkthrough

Walkthrough

This PR introduces OAuth reauthentication support for Anthropic Claude across the chat service and desktop UI. It adds new authentication infrastructure modules for managing OAuth credentials, token refresh, and retry logic; implements error handling for OAuth expiration in both the UI and agent runtime; and refactors the run-agent flow to integrate provider-aware retry mechanisms with proper session and stream management.

Changes

Cohort / File(s) Summary
Chat Error Message Component
apps/desktop/src/renderer/.../ChatErrorMessage/ChatErrorMessage.tsx, apps/desktop/src/renderer/.../ChatErrorMessage/index.ts
New reusable error message component with optional title, icon, and action button; supports styled error blocks with configurable layouts.
OAuth Error Detection and Rendering
apps/desktop/src/renderer/.../MessagePartsRenderer/MessagePartsRenderer.tsx, apps/desktop/src/renderer/.../MessagePartsRenderer/oauth-error.ts, apps/desktop/src/renderer/.../MessagePartsRenderer/oauth-error.test.ts
Replaces inline error rendering with OAuth-aware error detection; resolves OAuth reauth UI with actionable links; added structured error code preservation and detection logic with test coverage.
Inline Error Block Replacement
apps/desktop/src/renderer/.../ChatInputFooter/ChatInputFooter.tsx
Replaces inline error block with ChatErrorMessage component for consistency.
Authentication Infrastructure
packages/chat/src/host/auth/anthropic/types.ts, packages/chat/src/host/auth/anthropic/constants.ts, packages/chat/src/host/auth/anthropic/config-credentials.ts, packages/chat/src/host/auth/anthropic/keychain-credentials.ts, packages/chat/src/host/auth/anthropic/oauth-refresh.ts
New modular auth system with credential types, OAuth constants, config file parsing, macOS keychain integration, and automatic token refresh with concurrent request deduplication.
Auth Module Refactoring
packages/chat/src/host/auth/anthropic/anthropic.ts, packages/chat/src/host/auth/anthropic/index.ts
Refactored to delegate credential resolution to dedicated modules; maintains public API while decoupling concerns.
OAuth Retry Infrastructure
packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/oauth-retry.ts, packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/oauth-retry.test.ts
Self-contained OAuth retry utility with preflight checks, expired token detection, forced refresh, and retry orchestration with detailed error signaling and test coverage.
Agent Manager Startup
packages/chat/src/host/chat-service/agent-manager/agent-manager.ts
Integrated OAuth credential auto-refresh with layered fallback to legacy credentials and adjusted logging.
Provider-Aware Retry
packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/provider-auth-retry.ts, packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/provider-auth-retry.test.ts
New provider-specific retry handler selection with model ID parsing and graceful fallback to direct operation execution.
Run Agent OAuth Integration
packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-oauth.ts, packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-oauth.test.ts
Manages OAuth token synchronization with proper credential retrieval, refresh, and state management across sync results.
Run Agent Options and Session
packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-options.ts, packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-options.test.ts, packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-session.ts
New centralized agent configuration builders for request entries, stream input, tool approval, thinking options, and session abort lifecycle management.
Run Agent Stream Utilities
packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-stream.ts, packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-stream.test.ts
Streaming and error handling utilities including standardized error chunks, metadata prepending, durable stream writing, and run failure logging with OAuth reauth awareness.
Run Agent Refactoring
packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent.ts, packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent.test.ts
Refactored from inline logic to modular helpers; integrated provider auth retry, session lifecycle management, and new abort controller handling with comprehensive test coverage for all flows.
Message Materialization
packages/chat/src/session-db/collections/messages/materialize.ts, packages/chat/src/session-db/collections/messages/materialize.test.ts
Updated to preserve error chunk code field in materialized message parts with test coverage for code preservation.
Token Type Update
packages/agent/src/superagent.ts
Extended setAnthropicAuthToken parameter type to accept `string

Sequence Diagram(s)

sequenceDiagram
    participant Agent as Agent Manager
    participant OAuth as OAuth Refresh
    participant Config as Config
    participant Token as Superagent
    
    Agent->>Agent: startup()
    Agent->>OAuth: getOrRefreshAnthropicOAuthCredentials()
    OAuth->>Config: getCredentialsFromConfig()
    Config-->>OAuth: ClaudeOAuthCredentials | null
    alt OAuth Credentials Found
        OAuth->>OAuth: Check expiration & refresh if needed
        OAuth->>Config: saveOAuthCredentialsToConfig()
        OAuth-->>Agent: Updated credentials
        Agent->>Token: setAnthropicAuthToken(token)
    else No OAuth or Refresh Failed
        Agent->>Token: setAnthropicAuthToken(null)
        Agent->>Agent: Log fallback to legacy credentials
    end
Loading
sequenceDiagram
    participant Client as run-agent
    participant Retry as Provider Auth Retry
    participant OAuth as OAuth Retry
    participant Sync as OAuth Sync
    participant Agent as Anthropic Agent
    
    Client->>Retry: runWithProviderAuthRetry(operation, modelId)
    Retry->>Retry: resolveModelProvider(modelId)
    alt Anthropic Model
        Retry->>OAuth: withAnthropicOAuthRetry(operation)
        OAuth->>Sync: syncToken() [preflight]
        Sync-->>OAuth: "synced" | "reauth-required"
        alt Reauth Required at Preflight
            OAuth-->>Retry: throw AnthropicOAuthReauthRequiredError
        else Reauth Not Required
            OAuth->>Agent: operation()
            alt Operation Succeeds
                Agent-->>OAuth: result
                OAuth-->>Retry: result
            else Operation Fails with Expired Token
                OAuth->>Sync: syncToken({ forceRefresh: true })
                Sync-->>OAuth: "synced" | error
                alt Refresh Succeeds
                    OAuth->>Agent: retry operation()
                    Agent-->>OAuth: result
                    OAuth-->>Retry: result
                else Refresh Fails
                    OAuth-->>Retry: throw AnthropicOAuthReauthRequiredError
                end
            end
        end
    else Other Provider
        Retry->>Agent: operation() [direct execution]
        Agent-->>Retry: result
    end
Loading
sequenceDiagram
    participant UI as Desktop UI
    participant Parser as MessagePartsRenderer
    participant OAuth as OAuth Error Handler
    participant Error as ChatErrorMessage
    
    Parser->>Parser: process error part
    Parser->>OAuth: resolveOAuthReauthErrorUi(part)
    alt OAuth Reauth Detected
        OAuth-->>Parser: OAuthReauthErrorUi { title, description, actionUrl }
        Parser->>Error: ChatErrorMessage({ title, message: description, action: { label, onClick: openUrl } })
        Error-->>UI: Rendered error block with action button
    else Regular Error
        Parser->>Error: ChatErrorMessage({ message: error.text, showIcon: true })
        Error-->>UI: Rendered error block with icon
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 Hops through OAuth with care,
Tokens refresh in the air,
Errors now speak with grace,
Reauth finds its rightful place!
Sessions persist, the flow is clear—
Claude hops on, year after year! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.85% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding an OAuth reauthentication flow that is scoped by provider.
Description check ✅ Passed The description is comprehensive and follows the template structure with clear sections covering summary, key files, validation steps, and notes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kitenite/reauth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Contributor

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Fly.io Electric (Fly.io) Failed to deploy
Vercel API (Vercel) Failed to deploy
Vercel Web (Vercel) Failed to deploy
Vercel Marketing (Vercel) Failed to deploy
Vercel Admin (Vercel) Failed to deploy
Vercel Docs (Vercel) Failed to deploy

Preview updates automatically with new commits

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/oauth-error.ts (1)

20-53: Optional: DRY the OAuth reauth UI payload to avoid string drift.
The same object is duplicated in both branches; a small helper keeps title/description/action consistent.

♻️ Suggested refactor
+const buildAnthropicOAuthReauthUi = (): OAuthReauthErrorUi => ({
+	kind: "oauth-reauth",
+	title: "Claude authentication required",
+	description:
+		"Your Anthropic OAuth session expired and could not be refreshed. Run `claude auth login` in your terminal, then retry.",
+	actionLabel: "Open Anthropic Console",
+	actionUrl: ANTHROPIC_CONSOLE_URL,
+});
+
 export function resolveOAuthReauthErrorUi(
 	part: ErrorPartLike,
 ): OAuthReauthErrorUi | null {
 	if (part.code === ANTHROPIC_OAUTH_REAUTH_ERROR_CODE) {
-		return {
-			kind: "oauth-reauth",
-			title: "Claude authentication required",
-			description:
-				"Your Anthropic OAuth session expired and could not be refreshed. Run `claude auth login` in your terminal, then retry.",
-			actionLabel: "Open Anthropic Console",
-			actionUrl: ANTHROPIC_CONSOLE_URL,
-		};
+		return buildAnthropicOAuthReauthUi();
 	}
@@
-	return {
-		kind: "oauth-reauth",
-		title: "Claude authentication required",
-		description:
-			"Your Anthropic OAuth session expired and could not be refreshed. Run `claude auth login` in your terminal, then retry.",
-		actionLabel: "Open Anthropic Console",
-		actionUrl: ANTHROPIC_CONSOLE_URL,
-	};
+	return buildAnthropicOAuthReauthUi();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/oauth-error.ts`
around lines 20 - 53, The OAuth reauth UI payload is duplicated in
resolveOAuthReauthErrorUi; extract the repeated object into a single helper
(e.g., a constant or small factory function like oauthReauthUiPayload) and
return that helper from both the ANTHROPIC_OAUTH_REAUTH_ERROR_CODE branch and
the text-match branch to ensure title/description/actionLabel/actionUrl (use
ANTHROPIC_CONSOLE_URL) remain consistent and avoid string drift.
packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-session.ts (1)

29-32: Consider consolidating session state cleanup in clearSessionStateForFailure for defensive programming.

While abort controllers are properly released via releaseSessionAbortController in finally blocks for all failure paths, consolidating all session state cleanup—including sessionAbortControllers—into clearSessionStateForFailure improves code maintainability and prevents future bugs if the function is reused elsewhere without paired cleanup.

♻️ Suggested change
 export function clearSessionStateForFailure(sessionId: string): void {
 	sessionRunIds.delete(sessionId);
 	sessionContext.delete(sessionId);
+	sessionAbortControllers.delete(sessionId);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-session.ts`
around lines 29 - 32, The function clearSessionStateForFailure currently removes
sessionRunIds and sessionContext but omits sessionAbortControllers; update
clearSessionStateForFailure to also remove the abort controller entry (i.e.,
call sessionAbortControllers.delete(sessionId)) so all session state is
consolidated there, and then review callers (e.g., sites that call
releaseSessionAbortController in finally blocks) to ensure no double-use — you
can keep releaseSessionAbortController for invoking abort() but rely on
clearSessionStateForFailure for the shared-map cleanup to prevent stale entries.
packages/chat/src/host/auth/anthropic/keychain-credentials.ts (1)

10-35: Add a timeout to keychain lookups to prevent potential blocking.

execSync can hang if the macOS keychain subsystem stalls or prompts unexpectedly. The timeout option protects against this and ensures the host remains responsive during initialization.

♻️ Suggested change (timeout)
 		const result = execSync(
 			'security find-generic-password -s "claude-cli" -a "api-key" -w 2>/dev/null',
-			{ encoding: "utf-8" },
+			{ encoding: "utf-8", timeout: 2000 },
 		).trim();
@@
 		const result = execSync(
 			'security find-generic-password -s "anthropic-api-key" -w 2>/dev/null',
-			{ encoding: "utf-8" },
+			{ encoding: "utf-8", timeout: 2000 },
 		).trim();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat/src/host/auth/anthropic/keychain-credentials.ts` around lines
10 - 35, The two synchronous keychain lookups using execSync in
keychain-credentials.ts can hang; add a timeout option to both execSync calls
(e.g., include a timeout in the options object such as { encoding: "utf-8",
timeout: <ms> }) so the call will throw if the macOS keychain blocks, leaving
the existing try/catch behavior to treat it as "not found"; update both
occurrences that run 'security find-generic-password -s "claude-cli"...' and
'security find-generic-password -s "anthropic-api-key"...' to include the
timeout option.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/chat/src/host/chat-service/agent-manager/agent-manager.ts`:
- Around line 50-70: The fallback logic after
getOrRefreshAnthropicOAuthCredentials() currently ignores non-oauth credentials;
update the branch that handles cliCredentials (from getCredentialsFromConfig()
?? getCredentialsFromKeychain()) to recognize and apply apiKey credentials by
calling setAnthropicAuthToken(...) with the API key and logging a message
indicating the source; keep the existing oauth-warning behavior for kind ===
"oauth" and the ignore-warning for other kinds. Ensure you reference
getOrRefreshAnthropicOAuthCredentials, setAnthropicAuthToken,
getCredentialsFromConfig, getCredentialsFromKeychain, and cliCredentials.kind
when locating and modifying the code.

In
`@packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-oauth.ts`:
- Around line 12-37: In syncAnthropicOAuthToken, when
getOrRefreshAnthropicOAuthCredentials returns null do not unconditionally call
setAnthropicAuthToken(null); instead only clear the token and return
"reauth-required" if hasConfiguredOAuthCredentials is true or
options?.forceRefresh is true, otherwise leave the existing token untouched and
return "unavailable"; update the null branch around
getOrRefreshAnthropicOAuthCredentials and keep the catch behavior consistent (it
already conditionally clears the token).

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/oauth-error.ts`:
- Around line 20-53: The OAuth reauth UI payload is duplicated in
resolveOAuthReauthErrorUi; extract the repeated object into a single helper
(e.g., a constant or small factory function like oauthReauthUiPayload) and
return that helper from both the ANTHROPIC_OAUTH_REAUTH_ERROR_CODE branch and
the text-match branch to ensure title/description/actionLabel/actionUrl (use
ANTHROPIC_CONSOLE_URL) remain consistent and avoid string drift.

In `@packages/chat/src/host/auth/anthropic/keychain-credentials.ts`:
- Around line 10-35: The two synchronous keychain lookups using execSync in
keychain-credentials.ts can hang; add a timeout option to both execSync calls
(e.g., include a timeout in the options object such as { encoding: "utf-8",
timeout: <ms> }) so the call will throw if the macOS keychain blocks, leaving
the existing try/catch behavior to treat it as "not found"; update both
occurrences that run 'security find-generic-password -s "claude-cli"...' and
'security find-generic-password -s "anthropic-api-key"...' to include the
timeout option.

In
`@packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-session.ts`:
- Around line 29-32: The function clearSessionStateForFailure currently removes
sessionRunIds and sessionContext but omits sessionAbortControllers; update
clearSessionStateForFailure to also remove the abort controller entry (i.e.,
call sessionAbortControllers.delete(sessionId)) so all session state is
consolidated there, and then review callers (e.g., sites that call
releaseSessionAbortController in finally blocks) to ensure no double-use — you
can keep releaseSessionAbortController for invoking abort() but rely on
clearSessionStateForFailure for the shared-map cleanup to prevent stale entries.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 964c4a4 and b123928.

📒 Files selected for processing (31)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatErrorMessage/ChatErrorMessage.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatErrorMessage/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/ChatInputFooter/ChatInputFooter.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/MessagePartsRenderer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/oauth-error.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/ChatPane/ChatInterface/components/MessagePartsRenderer/oauth-error.ts
  • packages/agent/src/superagent.ts
  • packages/chat/src/host/auth/anthropic/anthropic.test.ts
  • packages/chat/src/host/auth/anthropic/anthropic.ts
  • packages/chat/src/host/auth/anthropic/config-credentials.ts
  • packages/chat/src/host/auth/anthropic/constants.ts
  • packages/chat/src/host/auth/anthropic/index.ts
  • packages/chat/src/host/auth/anthropic/keychain-credentials.ts
  • packages/chat/src/host/auth/anthropic/oauth-refresh.ts
  • packages/chat/src/host/auth/anthropic/types.ts
  • packages/chat/src/host/chat-service/agent-manager/agent-manager.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/oauth-retry.test.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/oauth-retry.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/provider-auth-retry.test.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/provider-auth-retry.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-oauth.test.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-oauth.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-options.test.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-options.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-session.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-stream.test.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-stream.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent.test.ts
  • packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent.ts
  • packages/chat/src/session-db/collections/messages/materialize.test.ts
  • packages/chat/src/session-db/collections/messages/materialize.ts

Comment on lines +50 to +70
// Initialize Claude OAuth credentials (auto-refresh if close to expiry).
const oauthCredentials = await getOrRefreshAnthropicOAuthCredentials();
if (oauthCredentials) {
setAnthropicAuthToken(oauthCredentials.apiKey);
console.log("[agent-manager] Using Claude OAuth credentials from config");
} else {
setAnthropicAuthToken(null);

const cliCredentials =
getCredentialsFromConfig() ?? getCredentialsFromKeychain();
if (cliCredentials) {
if (cliCredentials.kind === "oauth") {
console.warn(
"[agent-manager] Claude OAuth token is unavailable or expired and could not be refreshed",
);
} else {
console.warn(
`[agent-manager] Ignoring non-OAuth Claude credentials from ${cliCredentials.source}`,
);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check where ANTHROPIC_API_KEY is used
echo "=== Searching for ANTHROPIC_API_KEY usage ==="
rg -n -C3 'ANTHROPIC_API_KEY' --type ts --type tsx

echo ""
echo "=== Searching for getCredentialsFromConfig, getCredentialsFromKeychain, and setAnthropicAuthToken ==="
rg -n -C5 'getCredentialsFromConfig|getCredentialsFromKeychain|setAnthropicAuthToken' --type ts --type tsx

Repository: superset-sh/superset

Length of output: 285


🏁 Script executed:

#!/bin/bash
# Let's also check the file structure and understand the context better
echo "=== File location and size ==="
wc -l packages/chat/src/host/chat-service/agent-manager/agent-manager.ts

echo ""
echo "=== Check the agent-manager file around lines 50-70 and beyond ==="
sed -n '40,100p' packages/chat/src/host/chat-service/agent-manager/agent-manager.ts

Repository: superset-sh/superset

Length of output: 1939


🏁 Script executed:

#!/bin/bash
# Search for where setAnthropicAuthToken might be setting the API key
echo "=== Searching for setAnthropicAuthToken definition and usage ==="
rg -n 'setAnthropicAuthToken|const.*setAnthropicAuthToken' --type ts --type tsx -B2 -A5

echo ""
echo "=== Look for Anthropic client initialization ==="
rg -n 'new Anthropic|Anthropic\(' --type ts --type tsx -B2 -A2

Repository: superset-sh/superset

Length of output: 251


🏁 Script executed:

#!/bin/bash
# Search without file type restrictions
echo "=== Searching for ANTHROPIC_API_KEY usage ==="
rg -n 'ANTHROPIC_API_KEY' -C3

echo ""
echo "=== Searching for setAnthropicAuthToken definition ==="
rg -n 'setAnthropicAuthToken\s*=' -C5

echo ""
echo "=== Searching for getCredentialsFromConfig definition ==="
rg -n 'function\s+getCredentialsFromConfig|const\s+getCredentialsFromConfig|export.*getCredentialsFromConfig' -C3

Repository: superset-sh/superset

Length of output: 7857


🏁 Script executed:

#!/bin/bash
# Look at the full agent-manager.ts file to understand the complete flow
cat -n packages/chat/src/host/chat-service/agent-manager/agent-manager.ts

Repository: superset-sh/superset

Length of output: 6376


🏁 Script executed:

#!/bin/bash
# Search for where cliCredentials.kind === "apiKey" is handled
echo "=== Search for apiKey handling ==="
rg -n 'apiKey|kind.*===.*"apiKey"' -C3

echo ""
echo "=== Search for Anthropic imports and initialization ==="
rg -n 'import.*Anthropic|new Anthropic' -C2

Repository: superset-sh/superset

Length of output: 41768


🏁 Script executed:

#!/bin/bash
# Check how setAnthropicAuthToken is implemented and if it handles apiKey
echo "=== Looking for setAnthropicAuthToken implementation in `@superset/agent` ==="
find . -path ./node_modules -prune -o -type f -name "*.ts" -o -name "*.tsx" | grep -v node_modules | xargs grep -l "export.*setAnthropicAuthToken" 2>/dev/null

echo ""
echo "=== Search for where apiKey credentials should be applied ==="
rg -n 'kind.*apiKey|apiKey.*kind' -B3 -A3 packages/chat/src/host/auth/anthropic/

Repository: superset-sh/superset

Length of output: 3478


🏁 Script executed:

#!/bin/bash
# Check if there's another credential application path after agent-manager.start()
echo "=== Looking for where cliCredentials.kind === 'apiKey' is handled elsewhere ==="
rg -n 'cliCredentials|\.kind.*===.*["'"'"']apiKey' packages/chat/src/ -B2 -A2

Repository: superset-sh/superset

Length of output: 1502


Missing API-key fallback: Non-OAuth credentials are ignored instead of applied.

When OAuth credentials are unavailable, the code logs a warning for any config/keychain credentials but only if their kind is "oauth"—and does nothing for API-key credentials. This breaks the API-key authentication fallback; the agent remains unauthenticated unless process.env.ANTHROPIC_API_KEY is already set.

Add a case to apply API-key credentials:

if (cliCredentials.kind === "apiKey") {
	setAnthropicAuthToken(cliCredentials.apiKey);
	console.log(`[agent-manager] Using Claude API key from ${cliCredentials.source}`);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/chat/src/host/chat-service/agent-manager/agent-manager.ts` around
lines 50 - 70, The fallback logic after getOrRefreshAnthropicOAuthCredentials()
currently ignores non-oauth credentials; update the branch that handles
cliCredentials (from getCredentialsFromConfig() ?? getCredentialsFromKeychain())
to recognize and apply apiKey credentials by calling setAnthropicAuthToken(...)
with the API key and logging a message indicating the source; keep the existing
oauth-warning behavior for kind === "oauth" and the ignore-warning for other
kinds. Ensure you reference getOrRefreshAnthropicOAuthCredentials,
setAnthropicAuthToken, getCredentialsFromConfig, getCredentialsFromKeychain, and
cliCredentials.kind when locating and modifying the code.

Comment on lines +12 to +37
export async function syncAnthropicOAuthToken(
options?: OAuthTokenSyncOptions,
): Promise<OAuthTokenSyncResult> {
const configuredCredentials = getCredentialsFromConfig();
const hasConfiguredOAuthCredentials = configuredCredentials?.kind === "oauth";

try {
const oauthCredentials = await getOrRefreshAnthropicOAuthCredentials({
forceRefresh: options?.forceRefresh,
});

if (!oauthCredentials) {
setAnthropicAuthToken(null);
return hasConfiguredOAuthCredentials ? "reauth-required" : "unavailable";
}

setAnthropicAuthToken(oauthCredentials.apiKey);
return "synced";
} catch (error) {
console.warn("[run-agent] Failed to sync Anthropic OAuth token:", error);
if (hasConfiguredOAuthCredentials || options?.forceRefresh) {
setAnthropicAuthToken(null);
return "reauth-required";
}
return "unavailable";
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for all usages of setAnthropicAuthToken to understand its scope
rg -n "setAnthropicAuthToken" -g'*.{ts,tsx}' -A 2 -B 2

Repository: superset-sh/superset

Length of output: 4507


🏁 Script executed:

#!/bin/bash
# Search for anthropicAuthToken usage to understand token management
rg -n "anthropicAuthToken" -g'*.{ts,tsx}' -A 2 -B 2

Repository: superset-sh/superset

Length of output: 1082


🏁 Script executed:

#!/bin/bash
# Look for getOrRefreshAnthropicOAuthCredentials to understand when it returns null
rg -n "getOrRefreshAnthropicOAuthCredentials" -g'*.{ts,tsx}' -A 5 -B 2

Repository: superset-sh/superset

Length of output: 20415


🏁 Script executed:

#!/bin/bash
# Check the file structure around the sync function and any related API-key handling
fd "run-agent" -g'*.{ts,tsx}' | head -20

Repository: superset-sh/superset

Length of output: 294


🏁 Script executed:

#!/bin/bash
# Check if API-key credentials interact with setAnthropicAuthToken
# Look for patterns where API keys are used alongside setAnthropicAuthToken
rg -n "apiKey|api-key" -g'*.{ts,tsx}' -B 3 -A 3 | grep -A 5 -B 5 "setAnthropicAuthToken"

Repository: superset-sh/superset

Length of output: 3368


🏁 Script executed:

#!/bin/bash
# Check the calling context of syncAnthropicOAuthToken
rg -n "syncAnthropicOAuthToken" -g'*.{ts,tsx}' -B 2 -A 5

Repository: superset-sh/superset

Length of output: 7024


🏁 Script executed:

#!/bin/bash
# Examine how the function is used in the full test file to understand the expected behavior
cat -n packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-oauth.test.ts | head -80

Repository: superset-sh/superset

Length of output: 2226


Only clear the token when OAuth is configured or forceRefresh is requested.

The current code unconditionally clears the Anthropic token when getOrRefreshAnthropicOAuthCredentials returns null, even in the "unavailable" case where OAuth was never configured. This is semantically incorrect—token clearing should only happen when OAuth was actually configured (indicating a reauth is needed) or when explicitly forced via forceRefresh.

Suggested adjustment
-		if (!oauthCredentials) {
-			setAnthropicAuthToken(null);
-			return hasConfiguredOAuthCredentials ? "reauth-required" : "unavailable";
-		}
+		if (!oauthCredentials) {
+			if (hasConfiguredOAuthCredentials || options?.forceRefresh) {
+				setAnthropicAuthToken(null);
+			}
+			return hasConfiguredOAuthCredentials ? "reauth-required" : "unavailable";
+		}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/chat/src/host/chat-service/agent-manager/stream-watcher/run-agent/run-agent-oauth.ts`
around lines 12 - 37, In syncAnthropicOAuthToken, when
getOrRefreshAnthropicOAuthCredentials returns null do not unconditionally call
setAnthropicAuthToken(null); instead only clear the token and return
"reauth-required" if hasConfiguredOAuthCredentials is true or
options?.forceRefresh is true, otherwise leave the existing token untouched and
return "unavailable"; update the null branch around
getOrRefreshAnthropicOAuthCredentials and keep the catch behavior consistent (it
already conditionally clears the token).

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