Environment
@liustack/modlens 3.16.4, installed as the DeepSeek Harness (dsh) plugin on Windows (dsh plugin --profile web add @liustack/modlens@3.16.4)
- Engine: an OpenAI-compatible endpoint (Moonshot/Kimi,
https://api.moonshot.cn/v1) configured in ~/.modlens/config.json
Problem
resolveProviderSettings applies the ENV_BINDINGS after the file values and overrides them unconditionally:
for (const [field, envName] of Object.entries(bindings)) {
const value = env[envName]?.trim();
if (value) {
settings[field] = value;
}
}
So any ambient OPENAI_API_KEY / OPENAI_BASE_URL in the process environment silently replaces the engine explicitly configured in config.json. This is easy to hit on Windows (user-level env vars set for other OpenAI-compatible tools) and under the dsh plugin in particular, because the plugin spawns the CLI with the full host process environment.
Result: a key meant for one endpoint is sent to the configured endpoint, and every read fails with:
openai: OpenAI-compatible API error 401: {"error":{"message":"Invalid Authentication","type":"invalid_authentication_error"}}
There is no opt-out, and the error gives no hint that the key came from the environment rather than the config file. (Same failure class as the ANTHROPIC_BASE_URL trap already documented in configure.md.)
Repro (verified)
- Configure
providers.openai in ~/.modlens/config.json with a working key for an OpenAI-compatible endpoint (e.g. Kimi).
- Set an unrelated
OPENAI_API_KEY in the process environment.
- Run a read → 401 (wrong key sent to the configured endpoint). Unset the env var → the same config succeeds. Reproduced deterministically in both directions.
Proposed fix (verified locally)
Let an explicitly configured file value win; env only fills gaps:
for (const [field, envName] of Object.entries(bindings)) {
const value = env[envName]?.trim();
if (value && !settings[field]?.trim()) {
settings[field] = value;
}
}
Alternative/additional: an opt-out such as providers.openai.ignoreEnv: true for users who want the file to be the single source of truth.
As a second line of defense on the dsh side I also patched the plugin's spawn to not forward ambient credential env vars (OPENAI_*, GEMINI_API_KEY, ANTHROPIC_*) to the CLI, so config.json stays authoritative for dsh users regardless.
Test results (after the dist/main.js patch)
- CLI reads: 4/4 success against the configured endpoint (previously failed with 401 whenever the ambient var was present).
- dsh plugin tool path (
modlens_read_image): success.
doctor continues to report the config file as the effective source.
Environment
@liustack/modlens3.16.4, installed as the DeepSeek Harness (dsh) plugin on Windows (dsh plugin --profile web add @liustack/modlens@3.16.4)https://api.moonshot.cn/v1) configured in~/.modlens/config.jsonProblem
resolveProviderSettingsapplies theENV_BINDINGSafter the file values and overrides them unconditionally:So any ambient
OPENAI_API_KEY/OPENAI_BASE_URLin the process environment silently replaces the engine explicitly configured inconfig.json. This is easy to hit on Windows (user-level env vars set for other OpenAI-compatible tools) and under the dsh plugin in particular, because the plugin spawns the CLI with the full host process environment.Result: a key meant for one endpoint is sent to the configured endpoint, and every read fails with:
There is no opt-out, and the error gives no hint that the key came from the environment rather than the config file. (Same failure class as the
ANTHROPIC_BASE_URLtrap already documented in configure.md.)Repro (verified)
providers.openaiin~/.modlens/config.jsonwith a working key for an OpenAI-compatible endpoint (e.g. Kimi).OPENAI_API_KEYin the process environment.Proposed fix (verified locally)
Let an explicitly configured file value win; env only fills gaps:
Alternative/additional: an opt-out such as
providers.openai.ignoreEnv: truefor users who want the file to be the single source of truth.As a second line of defense on the dsh side I also patched the plugin's spawn to not forward ambient credential env vars (
OPENAI_*,GEMINI_API_KEY,ANTHROPIC_*) to the CLI, soconfig.jsonstays authoritative for dsh users regardless.Test results (after the dist/main.js patch)
modlens_read_image): success.doctorcontinues to report the config file as the effective source.