Environment: Qwen Code 0.16.0
Context
I'm using Qwen Code with sandbox enabled (docker) and a custom MCP server (mcp-github-docker) running in a Docker Compose network. I want to pass a personal access token via ${GITHUB_PERSONAL_ACCESS_TOKEN} in the MCP server headers, with the actual token stored in ~/.qwen/.env.
Current configuration
~/.qwen/settings.json:
{
"env": {
"OLLAMA_API_KEY": "",
"VLLM_API_KEY": "",
"LMSTUDIO_API_KEY": "",
"CUSTOM_API_KEY": "any",
"QWEN_SANDBOX_IMAGE": "qwen-code-sandbox",
"SANDBOX_FLAGS": "--network mcp-network"
},
"tools": {
"sandbox": true
},
"mcpServers": {
"mcp-github": {
"httpUrl": "http://mcp-github-docker:8082",
"headers": {
"Authorization": "Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}"
},
"timeout": 60000,
"trust": true
}
}
}
~/.qwen/.env:
GITHUB_PERSONAL_ACCESS_TOKEN=github_pat_xxx
What happens
The ${GITHUB_PERSONAL_ACCESS_TOKEN} placeholder in headers is not substituted
Workaround (with side effects)
The only way to make this work is to add the token via SANDBOX_ENV:
# ~/.qwen/.env
SANDBOX_ENV=GITHUB_PERSONAL_ACCESS_TOKEN=github_pat_xxx
This has two problems:
- Secret leaks to console — every time Qwen Code starts, the full
SANDBOX_ENV: GITHUB_PERSONAL_ACCESS_TOKEN=github_pat_xxx is printed to stderr.
- Two-step workaround — I still need
export GITHUB_PERSONAL_ACCESS_TOKEN=xxx for the ${VAR} substitution to work in settings, plus SANDBOX_ENV for the sandbox container to receive it.
Root cause (analysis of source code)
In packages/cli/src/config/settings.ts, the loadSettings() function executes in this order:
preResolveHomeEnvOverrides() — loads only QWEN_HOME / QWEN_RUNTIME_DIR from .env
- Read
settings.json (all scopes)
resolveEnvVarsInObject() — substitutes ${VAR} from process.env
- Merge settings + trust check
loadEnvironment() — loads the full .env file into process.env
Step 3 (variable substitution) happens before step 5 (.env loading). So any variable defined only in a .env file is not available for ${VAR} substitution.
Expected behavior
- Variables from
~/.qwen/.env should be available for ${VAR} substitution in settings.json, including MCP server headers.
- OR: there should be documented guidance on how to properly use
.env secrets with MCP server configuration.
Related issues
- #3877 — similar problem where project-level
.env overrides ~/.qwen/.env (different root cause but same symptom: .env variables not reaching their expected consumers)
Additional notes
SANDBOX_ENV is not documented anywhere in the official documentation
- The full list of environment variables forwarded to the sandbox container is not documented
- This also affects non-sandbox mode —
${VAR} in settings.json cannot be resolved from .env files regardless of sandbox usage
Environment: Qwen Code 0.16.0
Context
I'm using Qwen Code with sandbox enabled (
docker) and a custom MCP server (mcp-github-docker) running in a Docker Compose network. I want to pass a personal access token via${GITHUB_PERSONAL_ACCESS_TOKEN}in the MCP server headers, with the actual token stored in~/.qwen/.env.Current configuration
~/.qwen/settings.json:{ "env": { "OLLAMA_API_KEY": "", "VLLM_API_KEY": "", "LMSTUDIO_API_KEY": "", "CUSTOM_API_KEY": "any", "QWEN_SANDBOX_IMAGE": "qwen-code-sandbox", "SANDBOX_FLAGS": "--network mcp-network" }, "tools": { "sandbox": true }, "mcpServers": { "mcp-github": { "httpUrl": "http://mcp-github-docker:8082", "headers": { "Authorization": "Bearer ${GITHUB_PERSONAL_ACCESS_TOKEN}" }, "timeout": 60000, "trust": true } } }~/.qwen/.env:What happens
The
${GITHUB_PERSONAL_ACCESS_TOKEN}placeholder inheadersis not substitutedWorkaround (with side effects)
The only way to make this work is to add the token via
SANDBOX_ENV:# ~/.qwen/.env SANDBOX_ENV=GITHUB_PERSONAL_ACCESS_TOKEN=github_pat_xxxThis has two problems:
SANDBOX_ENV: GITHUB_PERSONAL_ACCESS_TOKEN=github_pat_xxxis printed to stderr.export GITHUB_PERSONAL_ACCESS_TOKEN=xxxfor the${VAR}substitution to work in settings, plusSANDBOX_ENVfor the sandbox container to receive it.Root cause (analysis of source code)
In
packages/cli/src/config/settings.ts, theloadSettings()function executes in this order:preResolveHomeEnvOverrides()— loads onlyQWEN_HOME/QWEN_RUNTIME_DIRfrom.envsettings.json(all scopes)resolveEnvVarsInObject()— substitutes${VAR}fromprocess.envloadEnvironment()— loads the full.envfile intoprocess.envStep 3 (variable substitution) happens before step 5 (
.envloading). So any variable defined only in a.envfile is not available for${VAR}substitution.Expected behavior
~/.qwen/.envshould be available for${VAR}substitution insettings.json, including MCP server headers..envsecrets with MCP server configuration.Related issues
.envoverrides~/.qwen/.env(different root cause but same symptom:.envvariables not reaching their expected consumers)Additional notes
SANDBOX_ENVis not documented anywhere in the official documentation${VAR}insettings.jsoncannot be resolved from.envfiles regardless of sandbox usage