diff --git a/src/ccs.ts b/src/ccs.ts index 1d8c5ad7..8832e6d2 100644 --- a/src/ccs.ts +++ b/src/ccs.ts @@ -1359,8 +1359,20 @@ async function main(): Promise { console.error(info(`Global env: ${envNames}`)); } - // Explicitly inject effective settings env vars so stale ANTHROPIC_* - // values from prior sessions cannot leak into the active profile. + // For Claude target launches that already pass `--settings`, keep runtime + // env limited to CCS-managed flags so nested Team/subagent sessions do not + // inherit profile-specific ANTHROPIC_* routing from the parent process. + const claudeRuntimeEnvVars: NodeJS.ProcessEnv = { + ...globalEnv, + ...(inheritedClaudeConfigDir ? { CLAUDE_CONFIG_DIR: inheritedClaudeConfigDir } : {}), + ...webSearchEnv, + ...imageAnalysisEnv, + ...(browserRuntimeEnv || {}), + CCS_PROFILE_TYPE: 'settings', + CCS_STRIP_INHERITED_ANTHROPIC_ENV: '1', + }; + + // Non-Claude targets still need effective credentials injected directly. const envVars: NodeJS.ProcessEnv = { ...globalEnv, ...settingsEnv, @@ -1472,7 +1484,7 @@ async function main(): Promise { settingsPath: expandedSettingsPath, }); - execClaude(claudeCli, launchArgs, { ...envVars, ...traceEnv }); + execClaude(claudeCli, launchArgs, { ...claudeRuntimeEnvVars, ...traceEnv }); } else if (profileInfo.type === 'account') { // NEW FLOW: Account-based profile (work, personal) // All platforms: Use instance isolation with CLAUDE_CONFIG_DIR diff --git a/tests/unit/targets/settings-profile-browser-launch.test.ts b/tests/unit/targets/settings-profile-browser-launch.test.ts index 1a343f62..df9a2f22 100644 --- a/tests/unit/targets/settings-profile-browser-launch.test.ts +++ b/tests/unit/targets/settings-profile-browser-launch.test.ts @@ -135,6 +135,12 @@ printf "%s\n" "$@" > "${claudeArgsLogPath}" printf "port=%s\n" "$CCS_BROWSER_DEVTOOLS_PORT" printf "httpUrl=%s\n" "$CCS_BROWSER_DEVTOOLS_HTTP_URL" printf "wsUrl=%s\n" "$CCS_BROWSER_DEVTOOLS_WS_URL" + printf "stripAnthropic=%s\n" "$CCS_STRIP_INHERITED_ANTHROPIC_ENV" + printf "anthropicBaseUrl=%s\n" "$ANTHROPIC_BASE_URL" + printf "anthropicAuthToken=%s\n" "$ANTHROPIC_AUTH_TOKEN" + printf "anthropicApiKey=%s\n" "$ANTHROPIC_API_KEY" + printf "anthropicModel=%s\n" "$ANTHROPIC_MODEL" + printf "anthropicSonnet=%s\n" "$ANTHROPIC_DEFAULT_SONNET_MODEL" } > "${claudeEnvLogPath}" exit 0 `, @@ -179,6 +185,44 @@ exit 0 expect(fs.existsSync(claudeArgsLogPath)).toBe(true); }); + it('passes selective Anthropic env stripping to settings-profile Claude launches while preserving model defaults', () => { + if (process.platform === 'win32') return; + + fs.writeFileSync( + settingsPath, + JSON.stringify( + { + env: { + ANTHROPIC_BASE_URL: 'https://api.z.ai/api/anthropic', + ANTHROPIC_AUTH_TOKEN: 'profile-token', + ANTHROPIC_MODEL: 'gpt-5.4', + ANTHROPIC_DEFAULT_SONNET_MODEL: 'gpt-5.4', + }, + }, + null, + 2 + ) + '\n' + ); + + const result = runCcs(['glm', 'smoke'], { + ...baseEnv, + ANTHROPIC_BASE_URL: 'http://127.0.0.1:8317/api/provider/codex', + ANTHROPIC_AUTH_TOKEN: 'parent-routing-token', + ANTHROPIC_API_KEY: 'parent-api-key', + ANTHROPIC_MODEL: 'gpt-5.4', + ANTHROPIC_DEFAULT_SONNET_MODEL: 'gpt-5.4', + }); + + expect(result.status).toBe(0); + const launchedEnv = fs.readFileSync(claudeEnvLogPath, 'utf8'); + expect(launchedEnv).toContain('stripAnthropic=1'); + expect(launchedEnv).toContain('anthropicBaseUrl='); + expect(launchedEnv).toContain('anthropicAuthToken='); + expect(launchedEnv).toContain('anthropicApiKey='); + expect(launchedEnv).toContain('anthropicModel=gpt-5.4'); + expect(launchedEnv).toContain('anthropicSonnet=gpt-5.4'); + }); + it('does not auto-enable browser reuse for settings-profile launches from env overrides alone', async () => { if (process.platform === 'win32') return;