import Foundation struct CodexExecutableResolution: Sendable { let executable: String let loginPATH: [String]? } typealias CodexExecutableResolver = @Sendable ( _ environment: [String: String], _ executable: String) -> CodexExecutableResolution? private func executableIsScript(_ path: String) -> Bool { guard let handle = FileHandle(forReadingAtPath: path) else { return true } defer { try? handle.close() } return (try? handle.read(upToCount: 2)) == Data("#!".utf8) } func resolveCodexExecutableForRPC( environment: [String: String], executable: String, captureLoginPATH: () -> [String]?) -> CodexExecutableResolution? { if let override = environment["CODEX_CLI_PATH"], FileManager.default.isExecutableFile(atPath: override) { // Native overrides can launch directly. Script overrides can depend on a // login-shell PATH through `#!/usr/bin/env node` or helper commands. let loginPATH = executableIsScript(override) ? captureLoginPATH() : nil return CodexExecutableResolution(executable: override, loginPATH: loginPATH) } // Capture before the general resolver to preserve login-shell PATH precedence // over bundled fallbacks and to make `/usr/bin/env node` launchers usable. let loginPATH = captureLoginPATH() guard let resolved = BinaryLocator.resolveCodexBinary(env: environment, loginPATH: loginPATH) ?? TTYCommandRunner.which(executable) else { return nil } return CodexExecutableResolution(executable: resolved, loginPATH: loginPATH) } let defaultCodexExecutableResolver: CodexExecutableResolver = { environment, executable in // Resolve the login-shell PATH at the actual RPC launch boundary. Warming it // from UsageFetcher.init was both racy for env-based launchers and unsafe for // short-lived CLI hosts because the fire-and-forget probe could outlive them. resolveCodexExecutableForRPC( environment: environment, executable: executable, captureLoginPATH: { LoginShellPathCache.shared.currentOrCapture(shell: environment["SHELL"]) }) }