source dump of claude code
at main 166 lines 5.1 kB view raw
1import { mkdir, readdir, readFile, writeFile } from 'fs/promises' 2import { join } from 'path' 3import { getSessionId } from '../bootstrap/state.js' 4import { logForDebugging } from './debug.js' 5import { getClaudeConfigHomeDir } from './envUtils.js' 6import { errorMessage, getErrnoCode } from './errors.js' 7import { getPlatform } from './platform.js' 8 9// Cache states: 10// undefined = not yet loaded (need to check disk) 11// null = checked disk, no files exist (don't check again) 12// string = loaded and cached (use cached value) 13let sessionEnvScript: string | null | undefined = undefined 14 15export async function getSessionEnvDirPath(): Promise<string> { 16 const sessionEnvDir = join( 17 getClaudeConfigHomeDir(), 18 'session-env', 19 getSessionId(), 20 ) 21 await mkdir(sessionEnvDir, { recursive: true }) 22 return sessionEnvDir 23} 24 25export async function getHookEnvFilePath( 26 hookEvent: 'Setup' | 'SessionStart' | 'CwdChanged' | 'FileChanged', 27 hookIndex: number, 28): Promise<string> { 29 const prefix = hookEvent.toLowerCase() 30 return join(await getSessionEnvDirPath(), `${prefix}-hook-${hookIndex}.sh`) 31} 32 33export async function clearCwdEnvFiles(): Promise<void> { 34 try { 35 const dir = await getSessionEnvDirPath() 36 const files = await readdir(dir) 37 await Promise.all( 38 files 39 .filter( 40 f => 41 (f.startsWith('filechanged-hook-') || 42 f.startsWith('cwdchanged-hook-')) && 43 HOOK_ENV_REGEX.test(f), 44 ) 45 .map(f => writeFile(join(dir, f), '')), 46 ) 47 } catch (e: unknown) { 48 const code = getErrnoCode(e) 49 if (code !== 'ENOENT') { 50 logForDebugging(`Failed to clear cwd env files: ${errorMessage(e)}`) 51 } 52 } 53} 54 55export function invalidateSessionEnvCache(): void { 56 logForDebugging('Invalidating session environment cache') 57 sessionEnvScript = undefined 58} 59 60export async function getSessionEnvironmentScript(): Promise<string | null> { 61 if (getPlatform() === 'windows') { 62 logForDebugging('Session environment not yet supported on Windows') 63 return null 64 } 65 66 if (sessionEnvScript !== undefined) { 67 return sessionEnvScript 68 } 69 70 const scripts: string[] = [] 71 72 // Check for CLAUDE_ENV_FILE passed from parent process (e.g., HFI trajectory runner) 73 // This allows venv/conda activation to persist across shell commands 74 const envFile = process.env.CLAUDE_ENV_FILE 75 if (envFile) { 76 try { 77 const envScript = (await readFile(envFile, 'utf8')).trim() 78 if (envScript) { 79 scripts.push(envScript) 80 logForDebugging( 81 `Session environment loaded from CLAUDE_ENV_FILE: ${envFile} (${envScript.length} chars)`, 82 ) 83 } 84 } catch (e: unknown) { 85 const code = getErrnoCode(e) 86 if (code !== 'ENOENT') { 87 logForDebugging(`Failed to read CLAUDE_ENV_FILE: ${errorMessage(e)}`) 88 } 89 } 90 } 91 92 // Load hook environment files from session directory 93 const sessionEnvDir = await getSessionEnvDirPath() 94 try { 95 const files = await readdir(sessionEnvDir) 96 // We are sorting the hook env files by the order in which they are listed 97 // in the settings.json file so that the resulting env is deterministic 98 const hookFiles = files 99 .filter(f => HOOK_ENV_REGEX.test(f)) 100 .sort(sortHookEnvFiles) 101 102 for (const file of hookFiles) { 103 const filePath = join(sessionEnvDir, file) 104 try { 105 const content = (await readFile(filePath, 'utf8')).trim() 106 if (content) { 107 scripts.push(content) 108 } 109 } catch (e: unknown) { 110 const code = getErrnoCode(e) 111 if (code !== 'ENOENT') { 112 logForDebugging( 113 `Failed to read hook file ${filePath}: ${errorMessage(e)}`, 114 ) 115 } 116 } 117 } 118 119 if (hookFiles.length > 0) { 120 logForDebugging( 121 `Session environment loaded from ${hookFiles.length} hook file(s)`, 122 ) 123 } 124 } catch (e: unknown) { 125 const code = getErrnoCode(e) 126 if (code !== 'ENOENT') { 127 logForDebugging( 128 `Failed to load session environment from hooks: ${errorMessage(e)}`, 129 ) 130 } 131 } 132 133 if (scripts.length === 0) { 134 logForDebugging('No session environment scripts found') 135 sessionEnvScript = null 136 return sessionEnvScript 137 } 138 139 sessionEnvScript = scripts.join('\n') 140 logForDebugging( 141 `Session environment script ready (${sessionEnvScript.length} chars total)`, 142 ) 143 return sessionEnvScript 144} 145 146const HOOK_ENV_PRIORITY: Record<string, number> = { 147 setup: 0, 148 sessionstart: 1, 149 cwdchanged: 2, 150 filechanged: 3, 151} 152const HOOK_ENV_REGEX = 153 /^(setup|sessionstart|cwdchanged|filechanged)-hook-(\d+)\.sh$/ 154 155function sortHookEnvFiles(a: string, b: string): number { 156 const aMatch = a.match(HOOK_ENV_REGEX) 157 const bMatch = b.match(HOOK_ENV_REGEX) 158 const aType = aMatch?.[1] || '' 159 const bType = bMatch?.[1] || '' 160 if (aType !== bType) { 161 return (HOOK_ENV_PRIORITY[aType] ?? 99) - (HOOK_ENV_PRIORITY[bType] ?? 99) 162 } 163 const aIndex = parseInt(aMatch?.[2] || '0', 10) 164 const bIndex = parseInt(bMatch?.[2] || '0', 10) 165 return aIndex - bIndex 166}