fix(auth): show actionable provider re-auth errors (#450)

This commit is contained in:
Iuliia Ivashko
2026-02-20 00:34:34 +02:00
committed by GitHub
parent 135b2c89be
commit 50a66bb6f1
4 changed files with 46 additions and 1 deletions
@@ -22,6 +22,7 @@ import type { StreamPhase, ToolPopupContent } from './message/types';
import { deriveMessageRole } from './message/messageRole';
import { filterVisibleParts } from './message/partUtils';
import { flattenAssistantTextParts } from '@/lib/messages/messageText';
import { isLikelyProviderAuthFailure, PROVIDER_AUTH_FAILURE_MESSAGE } from '@/lib/messages/providerAuthError';
import { FadeInOnReveal } from './message/FadeInOnReveal';
import type { TurnGroupingContext } from './hooks/useTurnGrouping';
@@ -646,6 +647,9 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
if (errorName === 'SessionRetry') {
return `Opencode failed to send a message. Retry attempt info: \n\`${detail}\``;
}
if (isLikelyProviderAuthFailure(detail)) {
return PROVIDER_AUTH_FAILURE_MESSAGE;
}
return `Opencode failed to send message with error:\n\`${detail}\``;
}, [isUser, message.info]);
@@ -0,0 +1,34 @@
export const PROVIDER_AUTH_FAILURE_MESSAGE = "Authentication failed for this provider. Please re-authenticate and retry.";
export const isLikelyProviderAuthFailure = (value: unknown): boolean => {
if (typeof value !== "string") {
return false;
}
const detail = value.toLowerCase().trim();
if (!detail) {
return false;
}
if (
detail.includes("token refresh failed") ||
detail.includes("unauthorized") ||
detail.includes("invalid token") ||
detail.includes("expired token")
) {
return true;
}
const hasOauth = detail.includes("oauth");
const hasOauthFailure =
detail.includes("failed") || detail.includes("invalid") || detail.includes("expired");
if (hasOauth && hasOauthFailure) {
return true;
}
const has401 = /\b401\b/.test(detail);
const hasAuthContext =
detail.includes("auth") || detail.includes("token") || detail.includes("unauthorized");
return has401 && hasAuthContext;
};
+5
View File
@@ -4,6 +4,7 @@ import { devtools, persist, createJSONStorage } from "zustand/middleware";
import type { Message, Part } from "@opencode-ai/sdk/v2";
import { opencodeClient } from "@/lib/opencode/client";
import { isExecutionForkMetaText } from "@/lib/messages/executionMeta";
import { isLikelyProviderAuthFailure, PROVIDER_AUTH_FAILURE_MESSAGE } from "@/lib/messages/providerAuthError";
import type { SessionMemoryState, MessageStreamLifecycle, AttachedFile } from "./types/sessionTypes";
import { MEMORY_LIMITS, getMemoryLimits, getBackgroundTrimLimit } from "./types/sessionTypes";
import {
@@ -787,6 +788,8 @@ export const useMessageStore = create<MessageStore>()(
return { abortControllers: nextControllers };
});
return;
} else if (isLikelyProviderAuthFailure(error.message)) {
errorMessage = PROVIDER_AUTH_FAILURE_MESSAGE;
} else if (error.message) {
errorMessage = error.message;
}
@@ -814,6 +817,8 @@ export const useMessageStore = create<MessageStore>()(
errorMessage = "OpenCode is restarting. Please wait a moment and try again.";
} else if (error.message?.includes("504") || error.message?.includes("Gateway")) {
errorMessage = "Gateway timeout - your message is being processed. Please wait for response.";
} else if (isLikelyProviderAuthFailure(error.message)) {
errorMessage = PROVIDER_AUTH_FAILURE_MESSAGE;
} else if (error.message) {
errorMessage = error.message;
}
@@ -52,7 +52,9 @@ export const fetchQuota = async () => {
providerName,
ok: false,
configured: true,
error: `API error: ${response.status}`
error: response.status === 401
? 'Session expired \u2014 please re-authenticate with OpenAI'
: `API error: ${response.status}`
});
}