-
Notifications
You must be signed in to change notification settings - Fork 19
Fixing issues for env variables #17
Conversation
gsabran
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, some comments
| let process = Process() | ||
| // In MacOS, zsh is the default since macOS Catalina 10.15.7. We can safely assume it is available. | ||
| process.launchPath = "/bin/zsh" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we keep the initial condition? If the executable is found no need to load the env
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done!
|
|
||
| // MARK: Public | ||
|
|
||
| /// Creates a new `Transport` by launching the given executable with the specified arguments and attaching to its standard IO. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
keep?
|
|
||
| private static func loadZshEnvironment(userEnv: [String: String]? = nil) throws -> [String: String] { | ||
| // Load shell environment as base | ||
| let shellProcess = Process() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we add userEnv to the process here, does it get merged automatically when calling printenv ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think so? environment variables assigned to a process don't automatically merge with the shell's environment when running commands. I updated the code to do so, what do you think?
| // Load shell environment as base | ||
| let shellProcess = Process() | ||
| shellProcess.executableURL = URL(fileURLWithPath: "/bin/zsh") | ||
| shellProcess.arguments = ["-ilc", "printenv"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice!
| // When userEnv exists, we need to explicitly export these variables in the shell | ||
| // before running printenv so they're included in the environment output | ||
| if let userEnv = userEnv, !userEnv.isEmpty { | ||
| var exportCommands = userEnv.map { key, value in | ||
| "export \(key)=\(value.replacingOccurrences(of: "\"", with: "\\\""))" | ||
| }.joined(separator: "; ") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wouldn't shellProcess.environment = userEnv do the same?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Disclaimer: with assistance of Claude
My understanding is that setting shellProcess.environment = userEnv wouldn't have the same effect because:
We're using zsh with -il flags which loads configuration files that might override environment variables
The explicit export commands ensure the variables are definitely set within the shell session where printenv runs
This approach guarantees the user variables appear in the printenv output, regardless of how zsh loads its environment.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MCPClient/Sources/stdioTransport/DataChannel+StdioProcess.swift
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good! Two nit comments. Can you rebase before merging? I simplified the linting and as there's no pre-merge checks not rebasing would lead to issues on main.
(swiftformat --config rules.swiftformat .)
f996a6d to
8f03d26
Compare
| // before running printenv so they're included in the environment output | ||
| if let userEnv, !userEnv.isEmpty { | ||
| var exportCommands = userEnv.map { key, value in | ||
| "export \(key)=\(value.replacingOccurrences(of: "\"", with: "\\\""))" | ||
| }.joined(separator: "; ") | ||
|
|
||
| if let path = env?.split(separator: "\n").filter({ $0.starts(with: "PATH=") }).last { | ||
| return ["PATH": String(path.dropFirst("PATH=".count))] | ||
| exportCommands += "; printenv" | ||
| shellProcess.arguments = ["-ilc", exportCommands] | ||
| } else { | ||
| shellProcess.arguments = ["-ilc", "printenv"] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shellProcess.environment = userEnv.isEmpty ? ProcessInfo.processInfo.environment : userEnv
shellProcess.arguments = ["-ilc", "printenv"]There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private static func loadZshEnvironment(userEnv: [String: String]? = nil) throws -> [String: String] {
// Load shell environment as base
let shellProcess = Process()
shellProcess.executableURL = URL(fileURLWithPath: "/bin/zsh")
// Set process environment - either use userEnv if it exists and isn't empty, or use system environment
if let env = userEnv, !env.isEmpty {
shellProcess.environment = env
} else {
shellProcess.environment = ProcessInfo.processInfo.environment
}
shellProcess.arguments = ["-ilc", "printenv"]
let outputPipe = Pipe()
shellProcess.standardOutput = outputPipe
shellProcess.standardError = Pipe()
try shellProcess.run()
shellProcess.waitUntilExit()
let data = outputPipe.fileHandleForReading.readDataToEndOfFile()
guard let outputString = String(data: data, encoding: .utf8) else {
logger.error("Failed to read environment from shell.")
return ProcessInfo.processInfo.environment
}
// Parse shell environment
return outputString
.split(separator: "\n")
.reduce(into: [String: String]()) { result, line in
let components = line.split(separator: "=", maxSplits: 1)
guard components.count == 2 else { return }
result[String(components[0])] = String(components[1])
}
}
gsabran
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot!

Summary
Aims to resolve #16
Tested this with current ClientChatDemo project Using Brave search
Details