source dump of claude code
at main 196 lines 6.8 kB view raw
1import { mkdirSync, writeFileSync } from 'fs' 2import { 3 getApiKeyFromFd, 4 getOauthTokenFromFd, 5 setApiKeyFromFd, 6 setOauthTokenFromFd, 7} from '../bootstrap/state.js' 8import { logForDebugging } from './debug.js' 9import { isEnvTruthy } from './envUtils.js' 10import { errorMessage, isENOENT } from './errors.js' 11import { getFsImplementation } from './fsOperations.js' 12 13/** 14 * Well-known token file locations in CCR. The Go environment-manager creates 15 * /home/claude/.claude/remote/ and will (eventually) write these files too. 16 * Until then, this module writes them on successful FD read so subprocesses 17 * spawned inside the CCR container can find the token without inheriting 18 * the FD — which they can't: pipe FDs don't cross tmux/shell boundaries. 19 */ 20const CCR_TOKEN_DIR = '/home/claude/.claude/remote' 21export const CCR_OAUTH_TOKEN_PATH = `${CCR_TOKEN_DIR}/.oauth_token` 22export const CCR_API_KEY_PATH = `${CCR_TOKEN_DIR}/.api_key` 23export const CCR_SESSION_INGRESS_TOKEN_PATH = `${CCR_TOKEN_DIR}/.session_ingress_token` 24 25/** 26 * Best-effort write of the token to a well-known location for subprocess 27 * access. CCR-gated: outside CCR there's no /home/claude/ and no reason to 28 * put a token on disk that the FD was meant to keep off disk. 29 */ 30export function maybePersistTokenForSubprocesses( 31 path: string, 32 token: string, 33 tokenName: string, 34): void { 35 if (!isEnvTruthy(process.env.CLAUDE_CODE_REMOTE)) { 36 return 37 } 38 try { 39 // eslint-disable-next-line custom-rules/no-sync-fs -- one-shot startup write in CCR, caller is sync 40 mkdirSync(CCR_TOKEN_DIR, { recursive: true, mode: 0o700 }) 41 // eslint-disable-next-line custom-rules/no-sync-fs -- one-shot startup write in CCR, caller is sync 42 writeFileSync(path, token, { encoding: 'utf8', mode: 0o600 }) 43 logForDebugging(`Persisted ${tokenName} to ${path} for subprocess access`) 44 } catch (error) { 45 logForDebugging( 46 `Failed to persist ${tokenName} to disk (non-fatal): ${errorMessage(error)}`, 47 { level: 'error' }, 48 ) 49 } 50} 51 52/** 53 * Fallback read from a well-known file. The path only exists in CCR (env-manager 54 * creates the directory), so file-not-found is the expected outcome everywhere 55 * else — treated as "no fallback", not an error. 56 */ 57export function readTokenFromWellKnownFile( 58 path: string, 59 tokenName: string, 60): string | null { 61 try { 62 const fsOps = getFsImplementation() 63 // eslint-disable-next-line custom-rules/no-sync-fs -- fallback read for CCR subprocess path, one-shot at startup, caller is sync 64 const token = fsOps.readFileSync(path, { encoding: 'utf8' }).trim() 65 if (!token) { 66 return null 67 } 68 logForDebugging(`Read ${tokenName} from well-known file ${path}`) 69 return token 70 } catch (error) { 71 // ENOENT is the expected outcome outside CCR — stay silent. Anything 72 // else (EACCES from perm misconfig, etc.) is worth surfacing in the 73 // debug log so subprocess auth failures aren't mysterious. 74 if (!isENOENT(error)) { 75 logForDebugging( 76 `Failed to read ${tokenName} from ${path}: ${errorMessage(error)}`, 77 { level: 'debug' }, 78 ) 79 } 80 return null 81 } 82} 83 84/** 85 * Shared FD-or-well-known-file credential reader. 86 * 87 * Priority order: 88 * 1. File descriptor (legacy path) — env var points at a pipe FD passed by 89 * the Go env-manager via cmd.ExtraFiles. Pipe is drained on first read 90 * and doesn't cross exec/tmux boundaries. 91 * 2. Well-known file — written by this function on successful FD read (and 92 * eventually by the env-manager directly). Covers subprocesses that can't 93 * inherit the FD. 94 * 95 * Returns null if neither source has a credential. Cached in global state. 96 */ 97function getCredentialFromFd({ 98 envVar, 99 wellKnownPath, 100 label, 101 getCached, 102 setCached, 103}: { 104 envVar: string 105 wellKnownPath: string 106 label: string 107 getCached: () => string | null | undefined 108 setCached: (value: string | null) => void 109}): string | null { 110 const cached = getCached() 111 if (cached !== undefined) { 112 return cached 113 } 114 115 const fdEnv = process.env[envVar] 116 if (!fdEnv) { 117 // No FD env var — either we're not in CCR, or we're a subprocess whose 118 // parent stripped the (useless) FD env var. Try the well-known file. 119 const fromFile = readTokenFromWellKnownFile(wellKnownPath, label) 120 setCached(fromFile) 121 return fromFile 122 } 123 124 const fd = parseInt(fdEnv, 10) 125 if (Number.isNaN(fd)) { 126 logForDebugging( 127 `${envVar} must be a valid file descriptor number, got: ${fdEnv}`, 128 { level: 'error' }, 129 ) 130 setCached(null) 131 return null 132 } 133 134 try { 135 // Use /dev/fd on macOS/BSD, /proc/self/fd on Linux 136 const fsOps = getFsImplementation() 137 const fdPath = 138 process.platform === 'darwin' || process.platform === 'freebsd' 139 ? `/dev/fd/${fd}` 140 : `/proc/self/fd/${fd}` 141 142 // eslint-disable-next-line custom-rules/no-sync-fs -- legacy FD path, read once at startup, caller is sync 143 const token = fsOps.readFileSync(fdPath, { encoding: 'utf8' }).trim() 144 if (!token) { 145 logForDebugging(`File descriptor contained empty ${label}`, { 146 level: 'error', 147 }) 148 setCached(null) 149 return null 150 } 151 logForDebugging(`Successfully read ${label} from file descriptor ${fd}`) 152 setCached(token) 153 maybePersistTokenForSubprocesses(wellKnownPath, token, label) 154 return token 155 } catch (error) { 156 logForDebugging( 157 `Failed to read ${label} from file descriptor ${fd}: ${errorMessage(error)}`, 158 { level: 'error' }, 159 ) 160 // FD env var was set but read failed — typically a subprocess that 161 // inherited the env var but not the FD (ENXIO). Try the well-known file. 162 const fromFile = readTokenFromWellKnownFile(wellKnownPath, label) 163 setCached(fromFile) 164 return fromFile 165 } 166} 167 168/** 169 * Get the CCR-injected OAuth token. See getCredentialFromFd for FD-vs-disk 170 * rationale. Env var: CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR. 171 * Well-known file: /home/claude/.claude/remote/.oauth_token. 172 */ 173export function getOAuthTokenFromFileDescriptor(): string | null { 174 return getCredentialFromFd({ 175 envVar: 'CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR', 176 wellKnownPath: CCR_OAUTH_TOKEN_PATH, 177 label: 'OAuth token', 178 getCached: getOauthTokenFromFd, 179 setCached: setOauthTokenFromFd, 180 }) 181} 182 183/** 184 * Get the CCR-injected API key. See getCredentialFromFd for FD-vs-disk 185 * rationale. Env var: CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR. 186 * Well-known file: /home/claude/.claude/remote/.api_key. 187 */ 188export function getApiKeyFromFileDescriptor(): string | null { 189 return getCredentialFromFd({ 190 envVar: 'CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR', 191 wellKnownPath: CCR_API_KEY_PATH, 192 label: 'API key', 193 getCached: getApiKeyFromFd, 194 setCached: setApiKeyFromFd, 195 }) 196}