source dump of claude code
at main 146 lines 5.2 kB view raw
1/** 2 * Shared utilities for spawning teammates across different backends. 3 */ 4 5import { 6 getChromeFlagOverride, 7 getFlagSettingsPath, 8 getInlinePlugins, 9 getMainLoopModelOverride, 10 getSessionBypassPermissionsMode, 11} from '../../bootstrap/state.js' 12import { quote } from '../bash/shellQuote.js' 13import { isInBundledMode } from '../bundledMode.js' 14import type { PermissionMode } from '../permissions/PermissionMode.js' 15import { getTeammateModeFromSnapshot } from './backends/teammateModeSnapshot.js' 16import { TEAMMATE_COMMAND_ENV_VAR } from './constants.js' 17 18/** 19 * Gets the command to use for spawning teammate processes. 20 * Uses TEAMMATE_COMMAND_ENV_VAR if set, otherwise falls back to the 21 * current process executable path. 22 */ 23export function getTeammateCommand(): string { 24 if (process.env[TEAMMATE_COMMAND_ENV_VAR]) { 25 return process.env[TEAMMATE_COMMAND_ENV_VAR] 26 } 27 return isInBundledMode() ? process.execPath : process.argv[1]! 28} 29 30/** 31 * Builds CLI flags to propagate from the current session to spawned teammates. 32 * This ensures teammates inherit important settings like permission mode, 33 * model selection, and plugin configuration from their parent. 34 * 35 * @param options.planModeRequired - If true, don't inherit bypass permissions (plan mode takes precedence) 36 * @param options.permissionMode - Permission mode to propagate 37 */ 38export function buildInheritedCliFlags(options?: { 39 planModeRequired?: boolean 40 permissionMode?: PermissionMode 41}): string { 42 const flags: string[] = [] 43 const { planModeRequired, permissionMode } = options || {} 44 45 // Propagate permission mode to teammates, but NOT if plan mode is required 46 // Plan mode takes precedence over bypass permissions for safety 47 if (planModeRequired) { 48 // Don't inherit bypass permissions when plan mode is required 49 } else if ( 50 permissionMode === 'bypassPermissions' || 51 getSessionBypassPermissionsMode() 52 ) { 53 flags.push('--dangerously-skip-permissions') 54 } else if (permissionMode === 'acceptEdits') { 55 flags.push('--permission-mode acceptEdits') 56 } 57 58 // Propagate --model if explicitly set via CLI 59 const modelOverride = getMainLoopModelOverride() 60 if (modelOverride) { 61 flags.push(`--model ${quote([modelOverride])}`) 62 } 63 64 // Propagate --settings if set via CLI 65 const settingsPath = getFlagSettingsPath() 66 if (settingsPath) { 67 flags.push(`--settings ${quote([settingsPath])}`) 68 } 69 70 // Propagate --plugin-dir for each inline plugin 71 const inlinePlugins = getInlinePlugins() 72 for (const pluginDir of inlinePlugins) { 73 flags.push(`--plugin-dir ${quote([pluginDir])}`) 74 } 75 76 // Propagate --teammate-mode so tmux teammates use the same mode as leader 77 const sessionMode = getTeammateModeFromSnapshot() 78 flags.push(`--teammate-mode ${sessionMode}`) 79 80 // Propagate --chrome / --no-chrome if explicitly set on the CLI 81 const chromeFlagOverride = getChromeFlagOverride() 82 if (chromeFlagOverride === true) { 83 flags.push('--chrome') 84 } else if (chromeFlagOverride === false) { 85 flags.push('--no-chrome') 86 } 87 88 return flags.join(' ') 89} 90 91/** 92 * Environment variables that must be explicitly forwarded to tmux-spawned 93 * teammates. Tmux may start a new login shell that doesn't inherit the 94 * parent's env, so we forward any that are set in the current process. 95 */ 96const TEAMMATE_ENV_VARS = [ 97 // API provider selection — without these, teammates default to firstParty 98 // and send requests to the wrong endpoint (GitHub issue #23561) 99 'CLAUDE_CODE_USE_BEDROCK', 100 'CLAUDE_CODE_USE_VERTEX', 101 'CLAUDE_CODE_USE_FOUNDRY', 102 // Custom API endpoint 103 'ANTHROPIC_BASE_URL', 104 // Config directory override 105 'CLAUDE_CONFIG_DIR', 106 // CCR marker — teammates need this for CCR-aware code paths. Auth finds 107 // its own way via /home/claude/.claude/remote/.oauth_token regardless; 108 // the FD env var wouldn't help (pipe FDs don't cross tmux). 109 'CLAUDE_CODE_REMOTE', 110 // Auto-memory gate (memdir/paths.ts) checks REMOTE && !MEMORY_DIR to 111 // disable memory on ephemeral CCR filesystems. Forwarding REMOTE alone 112 // would flip teammates to memory-off when the parent has it on. 113 'CLAUDE_CODE_REMOTE_MEMORY_DIR', 114 // Upstream proxy — the parent's MITM relay is reachable from teammates 115 // (same container network). Forward the proxy vars so teammates route 116 // customer-configured upstream traffic through the relay for credential 117 // injection. Without these, teammates bypass the proxy entirely. 118 'HTTPS_PROXY', 119 'https_proxy', 120 'HTTP_PROXY', 121 'http_proxy', 122 'NO_PROXY', 123 'no_proxy', 124 'SSL_CERT_FILE', 125 'NODE_EXTRA_CA_CERTS', 126 'REQUESTS_CA_BUNDLE', 127 'CURL_CA_BUNDLE', 128] as const 129 130/** 131 * Builds the `env KEY=VALUE ...` string for teammate spawn commands. 132 * Always includes CLAUDECODE=1 and CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1, 133 * plus any provider/config env vars that are set in the current process. 134 */ 135export function buildInheritedEnvVars(): string { 136 const envVars = ['CLAUDECODE=1', 'CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1'] 137 138 for (const key of TEAMMATE_ENV_VARS) { 139 const value = process.env[key] 140 if (value !== undefined && value !== '') { 141 envVars.push(`${key}=${quote([value])}`) 142 } 143 } 144 145 return envVars.join(' ') 146}