Skip to content

[qwencode-sdk-java 0.0.3-alpha] TransportOptions.setEnv() is silently dropped — env never reaches the CLI subprocess #3536

@qumy1997

Description

@qumy1997

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    status/needs-triageIssue needs to be triaged and labeledtype/bugSomething isn't working as expected

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions