From 13d13dab516332bc17345dc77afd44ae48bdd2aa Mon Sep 17 00:00:00 2001 From: kaitranntt Date: Tue, 2 Dec 2025 15:55:10 -0500 Subject: [PATCH] fix(auth): prevent default profile from using stale glm env vars Issue #37: When running `ccs` (default profile), users were unknowingly using GLM API instead of their Claude subscription because: 1. config.json had `default: '~/.claude/settings.json'` entry 2. ProfileDetector treated this as settings-based profile 3. CCS passed `--settings ~/.claude/settings.json` flag to Claude 4. Any stale ANTHROPIC_* env vars in that file got applied Fix (3-pronged approach): - Remove `default` entry from new config.json template - Add migration to remove existing `default: ~/.claude/settings.json` - Add ProfileDetector safety net: if default points to ~/.claude/settings.json, treat as pass-through to Claude's native auth (no --settings flag) Now `ccs` (no profile) correctly uses Claude's native OAuth authentication without loading potentially polluted env vars from previous sessions. Closes #37 --- scripts/postinstall.js | 25 +++++++++++++++++++++---- src/auth/profile-detector.ts | 13 ++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/scripts/postinstall.js b/scripts/postinstall.js index a2466ff9..c761c920 100755 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -125,12 +125,14 @@ function createConfigFiles() { // runs `ccs gemini` or `ccs codex` for first time (requires OAuth auth first) const configPath = path.join(ccsDir, 'config.json'); if (!fs.existsSync(configPath)) { + // NOTE: No 'default' entry - when no profile specified, CCS passes through + // to Claude's native auth without --settings flag. This prevents env var + // pollution from affecting the default profile. const config = { profiles: { glm: '~/.ccs/glm.settings.json', glmt: '~/.ccs/glmt.settings.json', - kimi: '~/.ccs/kimi.settings.json', - default: '~/.claude/settings.json' + kimi: '~/.ccs/kimi.settings.json' } }; @@ -141,23 +143,38 @@ function createConfigFiles() { console.log('[OK] Created config: ~/.ccs/config.json'); } else { - // Update existing config with glmt if missing (migration for v3.x users) + // Update existing config (migration for older versions) const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); // Ensure profiles object exists if (!config.profiles) { config.profiles = {}; } let configUpdated = false; + + // Migration: Add glmt if missing (v3.x) if (!config.profiles.glmt) { config.profiles.glmt = '~/.ccs/glmt.settings.json'; configUpdated = true; } + + // Migration: Remove 'default' entry pointing to ~/.claude/settings.json (v5.4.0) + // This entry caused the default profile to pass --settings flag, which could + // pick up stale env vars (ANTHROPIC_BASE_URL) from previous profile sessions. + // Fix: Let CCS pass through to Claude's native auth without --settings flag. + if (config.profiles.default === '~/.claude/settings.json') { + delete config.profiles.default; + configUpdated = true; + console.log('[OK] Removed legacy default profile (now uses native Claude auth)'); + } + // NOTE: gemini/codex profiles added on-demand, not during migration if (configUpdated) { const tmpPath = `${configPath}.tmp`; fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', 'utf8'); fs.renameSync(tmpPath, configPath); - console.log('[OK] Updated config with glmt profile'); + if (!config.profiles.glmt) { + console.log('[OK] Updated config with glmt profile'); + } } else { console.log('[OK] Config exists: ~/.ccs/config.json (preserved)'); } diff --git a/src/auth/profile-detector.ts b/src/auth/profile-detector.ts index 2886090d..eab38008 100644 --- a/src/auth/profile-detector.ts +++ b/src/auth/profile-detector.ts @@ -178,10 +178,21 @@ class ProfileDetector { const config = this.readConfig(); if (config.profiles && config.profiles['default']) { + const settingsPath = config.profiles['default']; + // Safety net: If default points to ~/.claude/settings.json, treat as pass-through + // to avoid loading stale env vars from previous profile sessions (issue #37). + // The ~/.claude/settings.json is Claude's native config - let Claude handle it. + if (settingsPath.includes('.claude') && settingsPath.endsWith('settings.json')) { + return { + type: 'default', + name: 'default', + message: 'Using native Claude auth (no custom env vars)', + }; + } return { type: 'settings', name: 'default', - settingsPath: config.profiles['default'], + settingsPath, }; }