fix: avoid sync github credential reads in provider manager (#428)

* fix: avoid sync github credential reads in provider manager

* test: stabilize provider manager async credential test

* fix: avoid first-frame github provider false negative

---------

Co-authored-by: KRATOS <84986124+gnanam1990@users.noreply.github.com>
This commit is contained in:
Agent_J
2026-04-06 18:59:53 +05:30
committed by GitHub
parent 72e6a945fe
commit aff2bd87e4
5 changed files with 408 additions and 26 deletions

View File

@@ -20,6 +20,7 @@ import {
GITHUB_MODELS_HYDRATED_ENV_MARKER,
hydrateGithubModelsTokenFromSecureStorage,
readGithubModelsToken,
readGithubModelsTokenAsync,
} from '../utils/githubModelsCredentials.js'
import { isEnvTruthy } from '../utils/envUtils.js'
import { updateSettingsForSource } from '../utils/settings/settings.js'
@@ -118,25 +119,38 @@ function profileSummary(profile: ProviderProfile, isActive: boolean): string {
return `${providerKind} · ${profile.baseUrl} · ${profile.model} · ${keyInfo}${activeSuffix}`
}
function getGithubCredentialSource(
function getGithubCredentialSourceFromEnv(
processEnv: NodeJS.ProcessEnv = process.env,
): GithubCredentialSource {
if (readGithubModelsToken()?.trim()) {
return 'stored'
}
if (processEnv.GITHUB_TOKEN?.trim() || processEnv.GH_TOKEN?.trim()) {
return 'env'
}
return 'none'
}
async function resolveGithubCredentialSource(
processEnv: NodeJS.ProcessEnv = process.env,
): Promise<GithubCredentialSource> {
const envSource = getGithubCredentialSourceFromEnv(processEnv)
if (envSource !== 'none') {
return envSource
}
if (await readGithubModelsTokenAsync()) {
return 'stored'
}
return 'none'
}
function isGithubProviderAvailable(
credentialSource: GithubCredentialSource,
processEnv: NodeJS.ProcessEnv = process.env,
): boolean {
if (isEnvTruthy(processEnv.CLAUDE_CODE_USE_GITHUB)) {
return true
}
return getGithubCredentialSource(processEnv) !== 'none'
return credentialSource !== 'none'
}
function getGithubProviderModel(
@@ -164,19 +178,24 @@ function getGithubProviderSummary(
}
export function ProviderManager({ mode, onDone }: Props): React.ReactNode {
const initialGithubCredentialSource = getGithubCredentialSourceFromEnv()
const initialIsGithubActive = isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB)
const initialHasGithubCredential = initialGithubCredentialSource !== 'none'
const [profiles, setProfiles] = React.useState(() => getProviderProfiles())
const [activeProfileId, setActiveProfileId] = React.useState(
() => getActiveProviderProfile()?.id,
)
const [githubProviderAvailable, setGithubProviderAvailable] = React.useState(() =>
isGithubProviderAvailable(),
const [githubProviderAvailable, setGithubProviderAvailable] = React.useState(
() => isGithubProviderAvailable(initialGithubCredentialSource),
)
const [githubCredentialSource, setGithubCredentialSource] = React.useState<GithubCredentialSource>(
() => getGithubCredentialSource(),
)
const [isGithubActive, setIsGithubActive] = React.useState(() =>
isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB),
() => initialGithubCredentialSource,
)
const [isGithubActive, setIsGithubActive] = React.useState(() => initialIsGithubActive)
const [isGithubCredentialSourceResolved, setIsGithubCredentialSourceResolved] =
React.useState(() => initialHasGithubCredential || initialIsGithubActive)
const githubRefreshEpochRef = React.useRef(0)
const [screen, setScreen] = React.useState<Screen>(
mode === 'first-run' ? 'select-preset' : 'menu',
)
@@ -196,13 +215,48 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode {
const currentStepKey = currentStep.key
const currentValue = draft[currentStepKey]
const refreshGithubProviderState = React.useCallback((): void => {
const envCredentialSource = getGithubCredentialSourceFromEnv()
const githubActive = isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB)
const canResolveFromEnv = githubActive || envCredentialSource !== 'none'
if (canResolveFromEnv) {
githubRefreshEpochRef.current += 1
setGithubCredentialSource(envCredentialSource)
setGithubProviderAvailable(isGithubProviderAvailable(envCredentialSource))
setIsGithubActive(githubActive)
setIsGithubCredentialSourceResolved(true)
return
}
setIsGithubCredentialSourceResolved(false)
const refreshEpoch = ++githubRefreshEpochRef.current
void (async () => {
const credentialSource = await resolveGithubCredentialSource()
if (refreshEpoch !== githubRefreshEpochRef.current) {
return
}
setGithubCredentialSource(credentialSource)
setGithubProviderAvailable(isGithubProviderAvailable(credentialSource))
setIsGithubActive(isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB))
setIsGithubCredentialSourceResolved(true)
})()
}, [])
React.useEffect(() => {
refreshGithubProviderState()
return () => {
githubRefreshEpochRef.current += 1
}
}, [refreshGithubProviderState])
function refreshProfiles(): void {
const nextProfiles = getProviderProfiles()
setProfiles(nextProfiles)
setActiveProfileId(getActiveProviderProfile()?.id)
setGithubProviderAvailable(isGithubProviderAvailable())
setGithubCredentialSource(getGithubCredentialSource())
setIsGithubActive(isEnvTruthy(process.env.CLAUDE_CODE_USE_GITHUB))
refreshGithubProviderState()
}
function clearStartupProviderOverrideFromUserSettings(): string | null {
@@ -640,7 +694,11 @@ export function ProviderManager({ mode, onDone }: Props): React.ReactNode {
{statusMessage && <Text>{statusMessage}</Text>}
<Box flexDirection="column">
{profiles.length === 0 && !githubProviderAvailable ? (
<Text dimColor>No provider profiles configured yet.</Text>
isGithubCredentialSourceResolved ? (
<Text dimColor>No provider profiles configured yet.</Text>
) : (
<Text dimColor>Checking GitHub Models credentials...</Text>
)
) : (
<>
{profiles.map(profile => (