What happened?
Summary
In qwencode-sdk-java 0.0.3-alpha, environment variables passed via
TransportOptions.setEnv(Map<String, String>) are silently discarded.
The CLI subprocess only inherits the JVM's own System.getenv() and never
sees the user-provided entries.
This contradicts the official documentation, which states:
Q: Can I customize the runtime environment of the CLI process?
A: Yes, use the setEnv() method in TransportOptions to pass
environment variables to the CLI process.
— https://qwenlm.github.io/qwen-code-docs/zh/developers/sdk-java/
Affected version
com.alibaba:qwencode-sdk:0.0.3-alpha
- Reproduced on Linux (Java 17, qwen-code CLI 0.14.x), but the bug is
platform-independent — it lives entirely in the SDK's Java code.
Reproduction
TransportOptions options = new TransportOptions()
.setPathToQwenExecutable("/usr/bin/env") // print env and exit
.setOtherOptions(Arrays.asList("MY_CUSTOM_VAR")) // grep target
.setEnv(Collections.singletonMap("MY_CUSTOM_VAR", "hello"));
new ProcessTransport(options); // observe stderr/stdout of the subprocess
MY_CUSTOM_VAR will not appear in the subprocess environment.
A more end-to-end repro: set any variable via setEnv(), then inspect /proc/<child_pid>/environ of the spawned qwen process — the variable is absent.
Root cause (source walk)
The SDK builds and merges the env map but never wires it into ProcessBuilder. Three files involved:
TransportOptions.java — setEnv() is a plain setter, just stores the map on a field.
TransportOptionsAdapter.java#addDefaultTransportOptions() — merges System.getenv() with the user's map and writes it back:
Map<String, String> env = new HashMap<>(System.getenv());
Optional.ofNullable(transportOptions.getEnv()).ifPresent(env::putAll);
transportOptions.setEnv(env);
But this merged map has no other reader anywhere in the SDK. buildCommandArgs() never touches getEnv() either (no --env flags are emitted).
ProcessTransport.java#start() — constructs ProcessBuilder without ever calling environment().putAll(...):
ProcessBuilder processBuilder = new ProcessBuilder(commandArgs)
.redirectOutput(Redirect.PIPE)
.redirectInput(Redirect.PIPE)
.redirectError(Redirect.PIPE)
.redirectErrorStream(false)
.directory(new File(transportOptionsAdapter.getCwd()));
process = processBuilder.start();
Because of this, the subprocess inherits the JVM's own environment (default ProcessBuilder behavior) and the user-supplied entries from setEnv() are dropped on the floor.
Suggested fix
Apply the env map inside ProcessTransport#start(), e.g.:
ProcessBuilder processBuilder = new ProcessBuilder(commandArgs)
.redirectOutput(Redirect.PIPE)
.redirectInput(Redirect.PIPE)
.redirectError(Redirect.PIPE)
.redirectErrorStream(false)
.directory(new File(transportOptionsAdapter.getCwd()));
Map<String, String> env =
transportOptionsAdapter.getHandledTransportOptions().getEnv();
if (env != null) {
Map<String, String> pbEnv = processBuilder.environment();
pbEnv.clear(); // env already contains System.getenv() merged in
pbEnv.putAll(env);
}
process = processBuilder.start();
(addDefaultTransportOptions already merges System.getenv() into the returned map, so a clear() + putAll() is safe and avoids double-merging.)
Impact
Any downstream integration relying on setEnv() to control the CLI process (e.g. QWEN_SANDBOX, BUILD_SANDBOX, custom config paths, proxies, language overrides) silently fails. Workarounds today require either (a) exporting the variable on the JVM process itself (systemd Environment=, shell export, container -e) so it gets inherited, or (b) patching the qwen-code TS side with hardcoded fallbacks, both of which are awkward for multi-tenant integrations.
What did you expect to happen?
.
Client information
.
Login information
No response
Anything else we need to know?
No response
What happened?
Summary
In
qwencode-sdk-java 0.0.3-alpha, environment variables passed viaTransportOptions.setEnv(Map<String, String>)are silently discarded.The CLI subprocess only inherits the JVM's own
System.getenv()and neversees the user-provided entries.
This contradicts the official documentation, which states:
Affected version
com.alibaba:qwencode-sdk:0.0.3-alphaplatform-independent — it lives entirely in the SDK's Java code.
Reproduction
MY_CUSTOM_VAR will not appear in the subprocess environment.
A more end-to-end repro: set any variable via setEnv(), then inspect /proc/<child_pid>/environ of the spawned qwen process — the variable is absent.
Root cause (source walk)
The SDK builds and merges the env map but never wires it into ProcessBuilder. Three files involved:
TransportOptions.java — setEnv() is a plain setter, just stores the map on a field.
TransportOptionsAdapter.java#addDefaultTransportOptions() — merges System.getenv() with the user's map and writes it back:
Map<String, String> env = new HashMap<>(System.getenv());
Optional.ofNullable(transportOptions.getEnv()).ifPresent(env::putAll);
transportOptions.setEnv(env);
But this merged map has no other reader anywhere in the SDK. buildCommandArgs() never touches getEnv() either (no --env flags are emitted).
ProcessTransport.java#start() — constructs ProcessBuilder without ever calling environment().putAll(...):
ProcessBuilder processBuilder = new ProcessBuilder(commandArgs)
.redirectOutput(Redirect.PIPE)
.redirectInput(Redirect.PIPE)
.redirectError(Redirect.PIPE)
.redirectErrorStream(false)
.directory(new File(transportOptionsAdapter.getCwd()));
process = processBuilder.start();
Because of this, the subprocess inherits the JVM's own environment (default ProcessBuilder behavior) and the user-supplied entries from setEnv() are dropped on the floor.
Suggested fix
Apply the env map inside ProcessTransport#start(), e.g.:
ProcessBuilder processBuilder = new ProcessBuilder(commandArgs)
.redirectOutput(Redirect.PIPE)
.redirectInput(Redirect.PIPE)
.redirectError(Redirect.PIPE)
.redirectErrorStream(false)
.directory(new File(transportOptionsAdapter.getCwd()));
Map<String, String> env =
transportOptionsAdapter.getHandledTransportOptions().getEnv();
if (env != null) {
Map<String, String> pbEnv = processBuilder.environment();
pbEnv.clear(); // env already contains System.getenv() merged in
pbEnv.putAll(env);
}
process = processBuilder.start();
(addDefaultTransportOptions already merges System.getenv() into the returned map, so a clear() + putAll() is safe and avoids double-merging.)
Impact
Any downstream integration relying on setEnv() to control the CLI process (e.g. QWEN_SANDBOX, BUILD_SANDBOX, custom config paths, proxies, language overrides) silently fails. Workarounds today require either (a) exporting the variable on the JVM process itself (systemd Environment=, shell export, container -e) so it gets inherited, or (b) patching the qwen-code TS side with hardcoded fallbacks, both of which are awkward for multi-tenant integrations.
What did you expect to happen?
.
Client information
.
Login information
No response
Anything else we need to know?
No response