fix(auth): Improve OAuth credential detection and startup warning

- Enhanced getClaudeAuthIndicators() to return detailed check information
  including file paths checked and specific error details for debugging
- Added debug logging to server startup credential detection for easier
  troubleshooting in Docker environments
- Show paths that were checked in the warning message to help users debug
  mount issues
- Added support for CLAUDE_CODE_OAUTH_TOKEN environment variable
- Return authType in verify-claude-auth response to distinguish between
  OAuth and CLI authentication methods
- Updated UI to show specific success messages for Claude Code subscription
  vs generic CLI auth
- Added Docker troubleshooting tips to sandbox risk dialog
- Added comprehensive unit tests for OAuth credential detection scenarios

Closes #721

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Kacper
2026-02-02 17:35:03 +01:00
parent ebc7987988
commit aad3ff2cdf
9 changed files with 1028 additions and 36 deletions

View File

@@ -121,21 +121,89 @@ const BOX_CONTENT_WIDTH = 67;
// The Claude Agent SDK can use either ANTHROPIC_API_KEY or Claude Code CLI authentication
(async () => {
const hasAnthropicKey = !!process.env.ANTHROPIC_API_KEY;
const hasEnvOAuthToken = !!process.env.CLAUDE_CODE_OAUTH_TOKEN;
logger.debug('[CREDENTIAL_CHECK] Starting credential detection...');
logger.debug('[CREDENTIAL_CHECK] Environment variables:', {
hasAnthropicKey,
hasEnvOAuthToken,
});
if (hasAnthropicKey) {
logger.info('✓ ANTHROPIC_API_KEY detected');
return;
}
if (hasEnvOAuthToken) {
logger.info('✓ CLAUDE_CODE_OAUTH_TOKEN detected');
return;
}
// Check for Claude Code CLI authentication
// Store indicators outside the try block so we can use them in the warning message
let cliAuthIndicators: Awaited<ReturnType<typeof getClaudeAuthIndicators>> | null = null;
try {
const indicators = await getClaudeAuthIndicators();
cliAuthIndicators = await getClaudeAuthIndicators();
const indicators = cliAuthIndicators;
// Log detailed credential detection results
logger.debug('[CREDENTIAL_CHECK] Claude CLI auth indicators:', {
hasCredentialsFile: indicators.hasCredentialsFile,
hasSettingsFile: indicators.hasSettingsFile,
hasStatsCacheWithActivity: indicators.hasStatsCacheWithActivity,
hasProjectsSessions: indicators.hasProjectsSessions,
credentials: indicators.credentials,
});
logger.debug('[CREDENTIAL_CHECK] File check details:', {
settingsFile: {
path: indicators.checks.settingsFile.path,
exists: indicators.checks.settingsFile.exists,
readable: indicators.checks.settingsFile.readable,
error: indicators.checks.settingsFile.error,
},
statsCache: {
path: indicators.checks.statsCache.path,
exists: indicators.checks.statsCache.exists,
readable: indicators.checks.statsCache.readable,
hasDailyActivity: indicators.checks.statsCache.hasDailyActivity,
error: indicators.checks.statsCache.error,
},
projectsDir: {
path: indicators.checks.projectsDir.path,
exists: indicators.checks.projectsDir.exists,
readable: indicators.checks.projectsDir.readable,
entryCount: indicators.checks.projectsDir.entryCount,
error: indicators.checks.projectsDir.error,
},
credentialFiles: indicators.checks.credentialFiles.map((cf) => ({
path: cf.path,
exists: cf.exists,
readable: cf.readable,
error: cf.error,
})),
});
const hasCliAuth =
indicators.hasStatsCacheWithActivity ||
(indicators.hasSettingsFile && indicators.hasProjectsSessions) ||
(indicators.hasCredentialsFile &&
(indicators.credentials?.hasOAuthToken || indicators.credentials?.hasApiKey));
logger.debug('[CREDENTIAL_CHECK] Auth determination:', {
hasCliAuth,
reason: hasCliAuth
? indicators.hasStatsCacheWithActivity
? 'stats cache with activity'
: indicators.hasSettingsFile && indicators.hasProjectsSessions
? 'settings file + project sessions'
: indicators.credentials?.hasOAuthToken
? 'credentials file with OAuth token'
: 'credentials file with API key'
: 'no valid credentials found',
});
if (hasCliAuth) {
logger.info('✓ Claude Code CLI authentication detected');
return;
@@ -145,7 +213,7 @@ const BOX_CONTENT_WIDTH = 67;
logger.warn('Error checking for Claude Code CLI authentication:', error);
}
// No authentication found - show warning
// No authentication found - show warning with paths that were checked
const wHeader = '⚠️ WARNING: No Claude authentication configured'.padEnd(BOX_CONTENT_WIDTH);
const w1 = 'The Claude Agent SDK requires authentication to function.'.padEnd(BOX_CONTENT_WIDTH);
const w2 = 'Options:'.padEnd(BOX_CONTENT_WIDTH);
@@ -158,6 +226,33 @@ const BOX_CONTENT_WIDTH = 67;
BOX_CONTENT_WIDTH
);
// Build paths checked summary from the indicators (if available)
let pathsCheckedInfo = '';
if (cliAuthIndicators) {
const pathsChecked: string[] = [];
// Collect paths that were checked
if (cliAuthIndicators.checks.settingsFile.path) {
pathsChecked.push(`Settings: ${cliAuthIndicators.checks.settingsFile.path}`);
}
if (cliAuthIndicators.checks.statsCache.path) {
pathsChecked.push(`Stats cache: ${cliAuthIndicators.checks.statsCache.path}`);
}
if (cliAuthIndicators.checks.projectsDir.path) {
pathsChecked.push(`Projects dir: ${cliAuthIndicators.checks.projectsDir.path}`);
}
for (const credFile of cliAuthIndicators.checks.credentialFiles) {
pathsChecked.push(`Credentials: ${credFile.path}`);
}
if (pathsChecked.length > 0) {
pathsCheckedInfo = `
║ ║
${'Paths checked:'.padEnd(BOX_CONTENT_WIDTH)}
${pathsChecked.map((p) => `${p.substring(0, BOX_CONTENT_WIDTH - 2).padEnd(BOX_CONTENT_WIDTH - 2)}`).join('\n')}`;
}
}
logger.warn(`
╔═════════════════════════════════════════════════════════════════════╗
${wHeader}
@@ -169,7 +264,7 @@ const BOX_CONTENT_WIDTH = 67;
${w3}
${w4}
${w5}
${w6}
${w6}${pathsCheckedInfo}
║ ║
╚═════════════════════════════════════════════════════════════════════╝
`);

View File

@@ -320,9 +320,28 @@ export function createVerifyClaudeAuthHandler() {
authMethod,
});
// Determine specific auth type for success messages
let authType: 'oauth' | 'api_key' | 'cli' | undefined;
if (authenticated) {
if (authMethod === 'api_key') {
authType = 'api_key';
} else if (authMethod === 'cli') {
// Check if CLI auth is via OAuth (Claude Code subscription) or generic CLI
// OAuth tokens are stored in the credentials file by the Claude CLI
const { getClaudeAuthIndicators } = await import('@automaker/platform');
const indicators = await getClaudeAuthIndicators();
if (indicators.credentials?.hasOAuthToken) {
authType = 'oauth';
} else {
authType = 'cli';
}
}
}
res.json({
success: true,
authenticated,
authType,
error: errorMessage || undefined,
});
} catch (error) {