Skip to content

fix(core): parse API timeout env strictly - #5602

Merged
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/api-timeout-env-decimal
Jun 22, 2026
Merged

fix(core): parse API timeout env strictly#5602
wenshao merged 1 commit into
QwenLM:mainfrom
tt-a1i:fix/api-timeout-env-decimal

Conversation

@tt-a1i

@tt-a1i tt-a1i commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What this PR does

This PR makes QWEN_CODE_API_TIMEOUT_MS use the same strict positive-integer env parsing rule as the newer shared env parser. Decimal positive integer values still override the lower-priority timeout, while malformed values are ignored.

It also updates the timeout env override tests so fractional, scientific-notation, hex, and unsafe-integer values fall back to the existing settings timeout instead of being coerced.

Why it's needed

The previous parser used JavaScript number coercion, so values like 1.5e5, 0x2BF20, and 12345.67 were accepted as valid millisecond timeouts. That is surprising for a *_MS env var and inconsistent with the stricter env parsing now used by nearby configuration knobs.

Keeping timeout env parsing strict avoids silently changing user intent while preserving the current precedence order: model provider config wins, then env, then settings/defaults.

Reviewer Test Plan

How to verify

Reviewers can confirm that QWEN_CODE_API_TIMEOUT_MS accepts decimal positive integers such as 300000, still trims surrounding whitespace, and ignores malformed values such as 1.5e5, 0x2BF20, 12345.67, and unsafe integers.

Commands run locally:

(cd packages/core && npx vitest run src/models/modelConfigResolver.test.ts)
npx prettier --check packages/core/src/models/modelConfigResolver.ts packages/core/src/models/modelConfigResolver.test.ts
npx eslint packages/core/src/models/modelConfigResolver.ts packages/core/src/models/modelConfigResolver.test.ts
npm run build --workspace @qwen-code/qwen-code-core
npm run typecheck --workspace @qwen-code/qwen-code-core
git diff --check

Evidence (Before & After)

Before: QWEN_CODE_API_TIMEOUT_MS=1.5e5, QWEN_CODE_API_TIMEOUT_MS=0x2BF20, and QWEN_CODE_API_TIMEOUT_MS=12345.67 were accepted/coerced as env timeouts.

After: those malformed values are ignored and the lower-priority configured timeout is preserved. This is covered by the updated modelConfigResolver unit tests.

No UI/TUI evidence is attached because this is a config parsing change covered by unit tests.

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

Local Node/npm workspace on macOS. npm install completed successfully before running the focused validation commands.

Risk & Scope

  • Main risk or tradeoff: users who intentionally used scientific notation, hex, fractional, or unsafe integer timeout env values will need to switch to decimal positive integer milliseconds.
  • Not validated / out of scope: no end-to-end model request was run; the behavior is covered at the shared model config resolver layer.
  • Breaking changes / migration notes: malformed QWEN_CODE_API_TIMEOUT_MS values are now ignored instead of coerced.

Linked Issues

Fixes #5596

AI Assistance Disclosure

I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.

中文说明

What this PR does

这个 PR 让 QWEN_CODE_API_TIMEOUT_MS 使用和较新的共享 env parser 一致的严格正整数解析规则。十进制正整数仍然会覆盖低优先级 timeout,格式不合法的值会被忽略。

同时更新了 timeout env override 的测试,确保小数、科学计数法、十六进制和不安全整数会回退到已有 settings timeout,而不是被静默转换。

Why it's needed

之前的解析逻辑使用 JavaScript number coercion,因此 1.5e50x2BF2012345.67 这类值都会被当成有效毫秒 timeout。对于一个 *_MS env var 来说这比较意外,也和附近配置项现在采用的严格 env 解析不一致。

把 timeout env 解析收紧可以避免静默改变用户意图,同时保留当前优先级顺序:model provider 配置优先,其次是 env,然后是 settings/defaults。

Reviewer Test Plan

How to verify

Reviewer 可以确认 QWEN_CODE_API_TIMEOUT_MS 仍然接受 300000 这样的十进制正整数,也仍然会 trim 前后空白;同时会忽略 1.5e50x2BF2012345.67 和不安全整数。

本地已运行命令:

(cd packages/core && npx vitest run src/models/modelConfigResolver.test.ts)
npx prettier --check packages/core/src/models/modelConfigResolver.ts packages/core/src/models/modelConfigResolver.test.ts
npx eslint packages/core/src/models/modelConfigResolver.ts packages/core/src/models/modelConfigResolver.test.ts
npm run build --workspace @qwen-code/qwen-code-core
npm run typecheck --workspace @qwen-code/qwen-code-core
git diff --check

Evidence (Before & After)

Before:QWEN_CODE_API_TIMEOUT_MS=1.5e5QWEN_CODE_API_TIMEOUT_MS=0x2BF20QWEN_CODE_API_TIMEOUT_MS=12345.67 会被接受/转换成 env timeout。

After:这些格式不合法的值会被忽略,并保留低优先级的已配置 timeout。这个行为已由更新后的 modelConfigResolver 单元测试覆盖。

这里没有附 UI/TUI 证据,因为这是配置解析变更,已由单元测试覆盖。

Tested on

OS Status
🍏 macOS ✅ tested
🪟 Windows ⚠️ not tested
🐧 Linux ⚠️ not tested

Environment (optional)

本地 macOS Node/npm workspace。运行 focused validation commands 前,npm install 已成功完成。

Risk & Scope

  • Main risk or tradeoff:如果用户有意使用科学计数法、十六进制、小数或不安全整数作为 timeout env 值,需要改成十进制正整数毫秒。
  • Not validated / out of scope:没有跑端到端模型请求;行为已在共享 model config resolver 层通过测试覆盖。
  • Breaking changes / migration notes:格式不合法的 QWEN_CODE_API_TIMEOUT_MS 现在会被忽略,而不是被转换。

Linked Issues

Fixes #5596

AI Assistance Disclosure

I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.

@tt-a1i
tt-a1i marked this pull request as ready for review June 22, 2026 07:50
@wenshao

wenshao commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao

wenshao commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

✅ Verification — strict QWEN_CODE_API_TIMEOUT_MS parsing, confirmed at the resolver and on a real request

I verified this fix locally on Linux at head fd61a982 (fixes #5596). Three layers all agree, and I went one step beyond the PR's own scope by running a real end-to-end request to confirm the resolved timeout actually reaches the OpenAI client (the PR notes "no end-to-end model request was run").

Method

Piece Detail
Build head fd61a982npm install + npm run bundle → real dist/cli.js
Probe drive the real exported resolveModelConfig via tsx across the env-value matrix
E2E real dist/cli.js -p … --approval-mode yolo under tmux; a mock that hangs every /v1/chat/completions and logs how long the client kept the socket open before aborting — that elapsed time is the effective OpenAI-client timeout

1) Direct probe of resolveModelConfig22/22 (the real resolver)

settings.generationConfig.timeout = 30000 is the lower-priority fallback; QWEN_CODE_API_TIMEOUT_MS is varied:

Env value Resolved config.timeout Source
300000, 45000 the value env
300000 (whitespace) 300000 (trimmed) env
1.5e5 30000 (settings) not env ✅ — old Number() = 150000
0x2BF20 30000 (settings) not env ✅ — old Number() = 180000
12345.67 30000 (settings) not env ✅ — old Math.floor = 12345
99999999999999999999 (unsafe int) 30000 (settings) not env ✅ — old = 1e20
3000.5, +300000, 300_000 30000 (settings) not env ✅
-100, 0, not-a-number, ``, 30000 (settings) not env ✅ (unchanged)

Precedence preserved: modelProvider 60000 wins over env 900000 (source: modelProviders); a valid env applies only when modelProvider sets no timeout; a malformed env is ignored even then.

2) PR's own suite — modelConfigResolver.test.ts: 51/51 passed

3) End-to-end — the resolved timeout reaches the real OpenAI client

A mock hangs the chat request; the client aborts at exactly the effective timeout. Same dist/cli.js, only QWEN_CODE_API_TIMEOUT_MS differs:

CASE A  env=3000      (valid)   → client ABORTED after 2996ms   ← valid value honored, reaches the client
CASE B  env=3000.5    (FIX)     → NO abort within 11s           ← ignored → falls back to the 120000ms default
CASE C  env=3000.5    (no-fix*) → client ABORTED after 2994ms   ← old Number()→Math.floor coerced it to 3000

* CASE C is a counterfactual: I reverted just the parse to the old Number()/Math.floor coercion, rebuilt core + rebundled, and re-ran the same 3000.5 input. The B-vs-C contrast — measured at the real client's abort time — is decisive: with the fix a malformed *_MS value no longer silently sets a (coerced) timeout; without it, 3000.5 quietly became a 3 s timeout. (CASE A confirms valid values still flow through to new OpenAI({ timeout }).)

📝 Notes for merge (non-blocking)

  1. Behavior change is intentional and now correct for a *_MS var1.5e5 / 0x2BF20 / 12345.67 / unsafe integers are ignored (fall back to the configured/default timeout) rather than coerced; the PR documents this in Risk & Scope and it matches the stricter shared parsePositiveIntegerEnv used by nearby knobs.
  2. No silent breakage of normal use — plain decimal ms (300000), whitespace trimming, and the modelProvider > env > settings precedence are all preserved (probe §1 + §3 CASE A).

Verdict (verification side): the resolver now parses QWEN_CODE_API_TIMEOUT_MS strictly (22/22 probe + 51/51 unit), and the resolved value demonstrably drives the real OpenAI client's request timeout — with a malformed value correctly falling back instead of being coerced. Looks merge-ready from here; final call is the maintainers'.

🇨🇳 中文版(点击展开)

✅ 验证 —— QWEN_CODE_API_TIMEOUT_MS 严格解析,在 resolver 真实请求上均确认

我在 fd61a982 上本地(Linux)验证了该修复(fixes #5596)。三层结果一致,并且我比 PR 自身范围多走一步:跑了一次真实端到端请求,确认解析后的 timeout 确实到达 OpenAI 客户端(PR 自述「未运行端到端模型请求」)。

方法

部分 细节
构建 head fd61a982npm install + npm run bundle → 真实 dist/cli.js
探针 tsx 驱动真实导出的 resolveModelConfig,跨 env 取值矩阵
E2E tmux 下真实 dist/cli.js -p … --approval-mode yolo;mock 挂起所有 /v1/chat/completions,并记录客户端在中止前保持 socket 多久 —— 这个耗时生效的 OpenAI 客户端 timeout

1) 直接探针打 resolveModelConfig —— 22/22(真实 resolver)

settings.generationConfig.timeout = 30000 作为低优先级回退;变化 QWEN_CODE_API_TIMEOUT_MS:

Env 值 解析得到的 config.timeout 来源
30000045000 原值 env
300000 (空白) 300000(已 trim) env
1.5e5 30000(settings) 非 env ✅ —— Number() = 150000
0x2BF20 30000(settings) 非 env ✅ —— Number() = 180000
12345.67 30000(settings) 非 env ✅ —— Math.floor = 12345
99999999999999999999(不安全整数) 30000(settings) 非 env ✅ —— 旧 = 1e20
3000.5+300000300_000 30000(settings) 非 env ✅
-1000not-a-number、``、 30000(settings) 非 env ✅(行为不变)

优先级保留:modelProvider 60000 胜过 env 900000(source: modelProviders);仅当 modelProvider 未设 timeout 时合法 env 才生效;此时 malformed env 仍被忽略。

2) PR 自带套件 —— modelConfigResolver.test.ts:51/51 通过

3) 端到端 —— 解析后的 timeout 到达真实 OpenAI 客户端

mock 挂起 chat 请求;客户端会恰好在生效 timeout 处中止。同一 dist/cli.js,仅 QWEN_CODE_API_TIMEOUT_MS 不同:

CASE A  env=3000      (合法)    → 客户端 ABORTED 于 2996ms   ← 合法值被采用,到达客户端
CASE B  env=3000.5    (修复)    → 11s 内无中止              ← 被忽略 → 回退到 120000ms 默认
CASE C  env=3000.5    (无修复*) → 客户端 ABORTED 于 2994ms   ← 旧 Number()→Math.floor 把它转成 3000

* CASE C 是反事实:我只把解析回退为旧的 Number()/Math.floor,重编 core + 重打包,再用相同3000.5 跑一次。B 与 C 的对比 —— 在真实客户端中止时刻测得 —— 是决定性的:有修复时,malformed 的 *_MS 不再静默设置(转换后的)timeout;无修复时,3000.5 悄悄变成了 3 秒 timeout。(CASE A 确认合法值仍会流入 new OpenAI({ timeout })。)

📝 合并参考(非阻塞)

  1. 行为变更是有意的,且对 *_MS 变量更正确 —— 1.5e5 / 0x2BF20 / 12345.67 / 不安全整数被忽略(回退到已配置/默认 timeout)而非转换;PR 已在 Risk & Scope 说明,且与附近配置项采用的更严格的共享 parsePositiveIntegerEnv 一致。
  2. 不破坏正常用法 —— 纯十进制毫秒(300000)、空白 trim、以及 modelProvider > env > settings 的优先级都保留(探针 §1 + §3 CASE A)。

结论(验证视角): resolver 现在严格解析 QWEN_CODE_API_TIMEOUT_MS(探针 22/22 + 单测 51/51),且解析结果确实驱动真实 OpenAI 客户端的请求 timeout —— malformed 值正确回退而非被转换。从这里看具备合并条件;最终决定权在维护者。

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @tt-a1i — and good catch on the silent coercion bug.

Template ✓ all required headings present, bilingual body, risk/scope disclosed.

Direction — this is squarely in scope. QWEN_CODE_API_TIMEOUT_MS silently accepting 1.5e5, 0x2BF20, 12345.67, and unsafe integers is the kind of foot-gun that erodes user trust in config. Tightening *_MS parsing to "decimal positive integer or ignored" matches what nearby env knobs already do and is the right default for a millisecond-valued variable. No CHANGELOG precedent needed; this is a bug fix to config resolution, not a product surface change.

Approach — minimal and clean. The whole change is: swap Number(raw) + isFinite + Math.floor for the existing shared parsePositiveIntegerEnv helper (which already lives in packages/core/src/utils/env.ts and is used by coreToolScheduler.ts for QWEN_CODE_MAX_TOOL_CONCURRENCY). Reuse > reinvent. The diff does one thing — tighten the parser — and the test changes are the exact minimum needed to encode the new contract (collapsing the four malformed-value tests into an it.each, and flipping the fractional OAuth test from "coerced to 12345" to "ignored"). No drive-by refactors, no scope creep.

One observation (non-blocking): packages/core/src/core/tokenLimits.ts exports a near-identical parsePositiveIntegerEnvValue used by the OpenAI / Anthropic providers for *_MAX_TOKENS. That duplication predates this PR and isn't in its scope — flagging it only because someone cleaning up the env-parser story later will want to know there are two helpers. Not a reason to hold this up.

Moving on to code review + testing. 🔍

中文说明

感谢 @tt-a1i 的 PR —— 也感谢你揪出了这个静默强制转换的 bug。

模板 ✓ 所有必需标题齐全、双语正文、风险/范围都已披露。

方向 —— 完全在范围内。QWEN_CODE_API_TIMEOUT_MS 静默接受 1.5e50x2BF2012345.67 和不安全整数,正是那种会蚕食用户对配置信任的陷阱。把 *_MS 解析收紧为"十进制正整数或忽略",与附近 env 配置项的做法一致,也是毫秒型变量应有的默认值。不需要 CHANGELOG 先例;这是配置解析的 bug 修复,不是产品表面的变更。

方案 —— 最小且干净。全部改动:把 Number(raw) + isFinite + Math.floor 换成已存在的共享 parsePositiveIntegerEnv 助手(已在 packages/core/src/utils/env.ts,并被 coreToolScheduler.ts 用于 QWEN_CODE_MAX_TOOL_CONCURRENCY)。复用优先。diff 只做一件事 —— 收紧解析 —— 测试变更也恰好是新契约所需的最小集(把 4 个格式非法测试合并成 it.each,并把 OAuth 小数测试从"被转成 12345"改为"被忽略")。没有顺手重构,没有范围蔓延。

一个观察(非阻塞):packages/core/src/core/tokenLimits.ts 导出了一个几乎一模一样的 parsePositiveIntegerEnvValue,被 OpenAI / Anthropic provider 用于 *_MAX_TOKENS。这种重复早于本 PR,不在其范围内 —— 只是提一下,将来有人要清理 env 解析器时该知道有两个助手。不是本 PR 阻塞点。

进入代码审查 + 测试。🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

2a. Code review

Independently, before reading the diff I would have: (1) swapped the Number(raw) + Number.isFinite + Math.floor chain in applyTimeoutEnvOverride for the existing shared parsePositiveIntegerEnv from utils/env.ts — that helper already encodes "decimal digits only, safe integer, positive, trims whitespace"; (2) updated the malformed-value tests to expect fallback to settings.generationConfig.timeout rather than coercion; (3) left the precedence (modelProvider > env > settings > default) alone.

The PR does exactly that. No surprises.

Critical blockers: none. The new code path (parsed = parsePositiveIntegerEnv(raw, 0); if (parsed > 0) …) is equivalent to the helper's contract. Math.floor is correctly gone — the helper already rejects non-integer inputs. The whitespace-trimming behavior is preserved (the helper trims internally). The sources['timeout'] attribution is still set iff the value is used.

Reuse check: pass. The helper is reused, not duplicated. Pre-existing sibling parsePositiveIntegerEnvValue in tokenLimits.ts (used by OpenAI / Anthropic *_MAX_TOKENS) is untouched and out of scope.

Clear AGENTS.md violations: none. ESM imports, strict typing, test collocated with source, file naming (modelConfigResolver.ts) unchanged, no speculative error handling.

One tiny stylistic observation (non-blocking): the new test block names the it.each parameters (_label, value) with an underscore prefix — that matches the existing convention in this test file for unused parameters, so no change needed.

2b. Real-scenario testing

Drove the real exported resolveModelConfig from packages/core/dist via tsx/Node across a 15-value env matrix + one precedence check. Same probe script, only the resolver changed between runs: BEFORE = HEAD~1 of modelConfigResolver.ts (old Number()+Math.floor parser) rebuilt via npm run build --workspace @qwen-code/qwen-code-core; AFTER = PR head fd61a982 rebuilt the same way. Ran both inside a tmux session at 120×40 with capture-pane -S -5000.

Unit tests also run from the worktree via cd packages/core && npx vitest run:

  • src/models/modelConfigResolver.test.ts51/51 passed
  • src/utils/env.test.ts12/12 passed

Before (installed build / old parser, HEAD~1 of modelConfigResolver.ts)

runner@runnervm7b5n9:~/work/qwen-code/qwen-code$ echo '=== BEFORE (old Number()+Math.floor parser — HEAD~1 of resolver) ===' && node /tmp/triage-probe.mjs
=== BEFORE (old Number()+Math.floor parser — HEAD~1 of resolver) ===
--- QWEN_CODE_API_TIMEOUT_MS probe (settings fallback = 30000) ---
Env value                             | resolved timeout | source
------------------------------------------------------------------------
decimal 300000 ("300000")             | 300000          | env
decimal 45000 ("45000")               | 45000           | env
whitespace "  300000  " ("  300000  ")| 300000          | env
scientific 1.5e5 ("1.5e5")            | 150000          | env     ← silent coercion
hex 0x2BF20 ("0x2BF20")               | 180000          | env     ← silent coercion
fractional 12345.67 ("12345.67")      | 12345           | env     ← silent coercion (Math.floor)
fractional 3000.5 ("3000.5")          | 3000            | env     ← silent coercion
explicit plus +300000 ("+300000")     | 300000          | env
underscore 300_000 ("300_000")        | 30000           | settings
negative -100 ("-100")                | 30000           | settings
zero ("0")                            | 30000           | settings
NaN string ("not-a-number")           | 30000           | settings
empty string ("")                     | 30000           | settings
spaces only ("   ")                   | 30000           | settings
unsafe int (MAX_SAFE+1) ("9007199254740992")| 9007199254740992| env     ← unsafe int passed through

--- Precedence: modelProvider 60000 vs env 900000 ---
resolved timeout: 60000 | source: modelProviders

After (this PR — strict parsePositiveIntegerEnv)

runner@runnervm7b5n9:~/work/qwen-code/qwen-code$ echo '=== AFTER (PR head fd61a982 — strict parser) ===' && node /tmp/triage-probe.mjs
=== AFTER (PR head fd61a982 — strict parser) ===
--- QWEN_CODE_API_TIMEOUT_MS probe (settings fallback = 30000) ---
Env value                             | resolved timeout | source
------------------------------------------------------------------------
decimal 300000 ("300000")             | 300000          | env     ✓ unchanged
decimal 45000 ("45000")               | 45000           | env     ✓ unchanged
whitespace "  300000  " ("  300000  ")| 300000          | env     ✓ trim preserved
scientific 1.5e5 ("1.5e5")            | 30000           | settings  ← FIX
hex 0x2BF20 ("0x2BF20")               | 30000           | settings  ← FIX
fractional 12345.67 ("12345.67")      | 30000           | settings  ← FIX
fractional 3000.5 ("3000.5")          | 30000           | settings  ← FIX
explicit plus +300000 ("+300000")     | 30000           | settings  (tighter, acceptable)
underscore 300_000 ("300_000")        | 30000           | settings  (unchanged)
negative -100 ("-100")                | 30000           | settings  (unchanged)
zero ("0")                            | 30000           | settings  (unchanged)
NaN string ("not-a-number")           | 30000           | settings  (unchanged)
empty string ("")                     | 30000           | settings  (unchanged)
spaces only ("   ")                   | 30000           | settings  (unchanged)
unsafe int (MAX_SAFE+1) ("9007199254740992")| 30000           | settings  ← FIX

--- Precedence: modelProvider 60000 vs env 900000 ---
resolved timeout: 60000 | source: modelProviders   ✓ unchanged

The four cells that flipped are exactly the four the PR claims to fix. Normal decimal values, whitespace trimming, and the modelProvider > env > settings precedence all behave identically. Tightening on +300000 (explicit plus) is a side effect of the stricter regex (^\d+$) — defensible for a *_MS var and called out in the PR's Risk & Scope.

中文说明

2a. 代码审查

在阅读 diff 之前,我自己会这样做:(1) 把 applyTimeoutEnvOverride 里的 Number(raw) + Number.isFinite + Math.floor 换成 utils/env.ts 中已存在的共享 parsePositiveIntegerEnv —— 该助手已经表达了"只接受十进制数字、安全整数、正值、内部 trim"的契约;(2) 更新 malformed 测试,期望回退到 settings.generationConfig.timeout 而非强制转换;(3) 不动优先级(modelProvider > env > settings > default)。

PR 完全就是这样做的。没有意外。

严重阻塞点: 无。新代码路径(parsed = parsePositiveIntegerEnv(raw, 0); if (parsed > 0) …)与助手的契约等价。Math.floor 被正确移除 —— 助手已经拒绝了非整数输入。空白 trim 行为保留(助手内部 trim)。只有当值被采用时才会设置 sources['timeout'] 归因。

复用检查: 通过。复用了现有助手,没有重复。预先存在的兄弟 parsePositiveIntegerEnvValue(位于 tokenLimits.ts,被 OpenAI/Anthropic 的 *_MAX_TOKENS 使用)未动且不在范围内。

清晰的 AGENTS.md 违规: 无。ESM 导入、严格类型、测试与源码并列、文件命名(modelConfigResolver.ts)未改、没有投机性的错误处理。

一个小的风格观察(非阻塞):新测试用 (_label, value) 带下划线前缀命名 it.each 参数 —— 与本测试文件中"未使用参数"的现有约定一致,无需修改。

2b. 真实场景测试

使用 tsx/Node 驱动真实导出的 resolveModelConfig(来自 packages/core/dist),跨 15 个 env 值矩阵 + 1 个优先级检查。同一个探针脚本,两次运行之间只换了 resolver:BEFORE = modelConfigResolver.tsHEAD~1(旧的 Number()+Math.floor 解析器),通过 npm run build --workspace @qwen-code/qwen-code-core 重编;AFTER = PR head fd61a982 同样的方式重编。两次运行都在 120×40 的 tmux 会话中执行,使用 capture-pane -S -5000

单元测试也在 worktree 中通过 cd packages/core && npx vitest run 跑过:

  • src/models/modelConfigResolver.test.ts51/51 通过
  • src/utils/env.test.ts12/12 通过

(Before/After 输出见上方代码块 —— 翻转的 4 个格子正好是 PR 声称修复的 4 个。普通十进制值、空白 trim、modelProvider > env > settings 优先级均保持不变。对 +300000(显式加号)的收紧是更严格正则(^\d+$)的副作用 —— 对 *_MS 变量是合理的,且已在 PR 的 Risk & Scope 中说明。)

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Stepping back: this is exactly the kind of fix I want to see land. The bug is real (silently coercing 1.5e5 / 0x2BF20 / 12345.67 / unsafe integers into a millisecond timeout is the kind of foot-gun users can't easily diagnose), the fix is the minimum change needed (swap one parser, flip four test assertions), and the evidence actually shows what it claims — both at the unit layer (51/51 + 12/12) and at the real-resolver probe layer where I can see each malformed value flip from "coerced, source=env" to "ignored, source=settings".

My independent proposal from Stage 2a was "reuse parsePositiveIntegerEnv, update malformed-value assertions, leave precedence alone" — the PR does exactly that, nothing more. No drive-by refactors, no new helpers, no scope creep. The one side-effect tightening (+300000 no longer accepted) is a defensible consequence of the stricter ^\d+$ regex and is already called out in the PR's Risk & Scope.

The pre-existing near-duplicate parsePositiveIntegerEnvValue in tokenLimits.ts (used for *_MAX_TOKENS in the OpenAI / Anthropic providers) is a real but unrelated wart — flagging it once more for whoever cleans up the env-parser story next, not for this PR.

Merge-ready from my side. Approving. ✅

中文说明

退一步看:这正是我希望看到合并的那类修复。bug 真实存在(把 1.5e5 / 0x2BF20 / 12345.67 / 不安全整数静默强制转换为毫秒 timeout,正是用户难以诊断的陷阱),修复也是所需的最小改动(换一个解析器、翻转 4 个测试断言),并且证据确实证明了它的声明 —— 既在单测层(51/51 + 12/12),也在我能看到每个 malformed 值从"被转换,source=env"翻转为"被忽略,source=settings"的真实 resolver 探针层。

我在 Stage 2a 的独立方案是"复用 parsePositiveIntegerEnv、更新 malformed 断言、不动优先级" —— PR 恰好就是这样做的,没有更多。没有顺手重构、没有新增助手、没有范围蔓延。唯一的收紧副作用(+300000 不再被接受)是更严格的 ^\d+$ 正则带来的合理结果,且已在 PR 的 Risk & Scope 中说明。

tokenLimits.ts 中预先存在的近似重复 parsePositiveIntegerEnvValue(被 OpenAI / Anthropic provider 的 *_MAX_TOKENS 使用)是真实但不相关的瑕疵 —— 再为下一位要清理 env 解析器的人标记一次,不纳入本 PR。

从我这边看可以合并。批准。✅

Qwen Code · qwen3.7-max

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@wenshao
wenshao merged commit 9f63bcf into QwenLM:main Jun 22, 2026
37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(core): API timeout env accepts non-decimal values

3 participants