Check the selected OpenCodeReview provider credential

Avoid marking a configured provider ready because another provider or an unrelated environment variable has a key; retain unverified status when its credential cannot be determined.

Checks: native readiness, configuration and catalog tests; real OCR missing-credential reproduction.
This commit is contained in:
Ryder Sun committed 2026-11-07 18:50:52 +08:00
1 parent 14c23653ae
commit 8f0fe4d01b
2 files changed
+46

No files matched your search

@@ -96,6 +96,26 @@ function jsonContainsCredential(filename) {
}
}
function openCodeReviewSelectedCredential(home, env, credentialSource) {
let config
try {
const contents = readCredentialFile(path.join(home, '.opencodereview', 'config.json'))
if (!contents) return null
config = JSON.parse(contents)
} catch { return { state: 'unknown', source: 'unverified' } }
const provider = typeof config?.provider === 'string' ? config.provider.trim() : ''
if (!provider) return null
const nativeKey = provider === 'openai' || provider === 'openai-responses' ? 'OPENAI_API_KEY'
: provider === 'anthropic' ? 'ANTHROPIC_API_KEY' : ''
const selected = config.providers?.[provider] || (!nativeKey && config.custom_providers?.[provider])
const configured = credentialLiteral(selected?.api_key)
const command = typeof selected?.api_key_cmd === 'string' && selected.api_key_cmd.trim()
const inherited = !command && nativeKey && credentialLiteral(env[nativeKey])
return configured || inherited
? { state: 'ready', source: inherited && credentialSource === 'native-shell' ? 'native-shell' : 'native-credential' }
: { state: 'unknown', source: 'unverified' }
}
function openCodeAuthContainsCredential(filename) {
try {
const contents = readCredentialFile(filename)
@@ -707,6 +727,10 @@ function nativeCredentialState(kind, options = {}) {
const env = options.env || process.env
const configuredUserHome = (options.platform || process.platform) === 'win32' ? env.USERPROFILE : env.HOME
const effectiveUserHome = typeof configuredUserHome === 'string' && configuredUserHome.trim() ? configuredUserHome.trim() : home
if (kind === 'opencodereview') {
const selected = openCodeReviewSelectedCredential(effectiveUserHome, env, options.credentialSource)
if (selected) return selected
}
const codexHome = env.CODEX_HOME || path.join(home, '.codex')
const claudeHome = env.CLAUDE_CONFIG_DIR || path.join(home, '.claude')
const geminiHome = env.GEMINI_CLI_HOME
@@ -52,6 +52,28 @@ test('OpenCodeReview readiness reads the selected native home', t => {
assert.equal(nativeCredentialState('opencodereview', { home, env: {} }).state, 'unknown')
})
test('OpenCodeReview does not treat another provider credential as the selected provider', t => {
const home = fs.mkdtempSync(path.join(os.tmpdir(), 'meldwork-ocr-provider-'))
t.after(() => fs.rmSync(home, { recursive: true, force: true }))
const root = path.join(home, '.opencodereview')
fs.mkdirSync(root)
const file = path.join(root, 'config.json')
const config = { provider: 'selected-gateway', model: 'test', custom_providers: {
'selected-gateway': { url: 'https://example.invalid/v1', protocol: 'openai' },
'other-gateway': { url: 'https://example.invalid/v1', protocol: 'openai', api_key: 'synthetic-other' },
} }
fs.writeFileSync(file, JSON.stringify(config))
assert.equal(nativeCredentialState('opencodereview', { home, env: { OPENAI_API_KEY: 'synthetic-unrelated' } }).state, 'unknown')
config.custom_providers['selected-gateway'].api_key = 'synthetic-selected'
fs.writeFileSync(file, JSON.stringify(config))
assert.equal(nativeCredentialState('opencodereview', { home, env: {} }).state, 'ready')
config.provider = 'openai'
config.custom_providers.openai = { api_key: 'synthetic-shadow' }
fs.writeFileSync(file, JSON.stringify(config))
assert.equal(nativeCredentialState('opencodereview', { home, env: {} }).state, 'unknown')
assert.equal(nativeCredentialState('opencodereview', { home, env: { OPENAI_API_KEY: 'synthetic-openai' } }).state, 'ready')
})
test('Hermes receives native Bedrock and Vertex environment for discovery and execution', async () => {
const env = { AWS_PROFILE: 'research', AWS_REGION: 'us-east-1', AWS_ACCESS_KEY_ID: 'native-access',
AWS_SECRET_ACCESS_KEY: 'native-secret', GOOGLE_APPLICATION_CREDENTIALS: '/native/google.json',