fix(core): parse max output token env strictly - #5491
Conversation
|
@qwen-code /triage |
|
Thanks for the PR! Template looks good ✓ — all required sections present, bilingual, linked issue. On direction: this is a clean bug fix — On approach: scope is minimal and correct. One shared Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ — 所有必填章节齐全,双语,关联了 issue。 方向:这是一个干净的 bug 修复 — 方案:范围最小且正确。在 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal: I'd add a strict parser for the env value — reject anything that isn't a clean positive integer, fall back to the capped default otherwise. Extract it to The PR does exactly this and slightly exceeds my proposal by also adding No blockers. The Typecheck and lint both clean on the changed files. TestingBefore/After parsing behaviorReproduced the bug and verified the fix by running the old
Unit testsAll 95 tests pass, including 4 new tests covering malformed env values ( 中文说明代码审查独立方案:我会给 env 值加严格解析 — 不是干净的正整数就拒绝,回退到 capped default。抽到 PR 正好这么做的,而且比我多了一层 没有阻塞项。 Typecheck 和 lint 均通过。 测试并排对比了旧 95 个单测全部通过,包括 4 个新测试覆盖非法值( — Qwen Code · qwen3.7-max |
|
This is a textbook bug fix: real problem, minimal scope, correct implementation, good tests. The before/after table tells the whole story — My independent proposal was to swap 95 tests pass, typecheck clean, lint clean. Ships it. 中文说明这是一个教科书级的 bug 修复:真实问题、最小范围、正确实现、测试到位。 Before/After 表格说明了一切 — 我的独立方案是把 95 个测试通过,typecheck 通过,lint 通过。可以合。 — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
✅ Verification report — local real-world testing (LGTM, one non-blocking follow-up)I verified this PR locally on Linux (Node 22.22.2) with unit tests, a before/after regression check, an edge-case battery against the shipped bundle, and a live What the bug wasBoth content generators parsed Verification performed1. Affected suites — 154/154 pass ( 2. Before/after (the new tests are load-bearing). I reverted only the helper body to the old
So the new tests genuinely catch the bug rather than passing incidentally. 3. Edge-case battery against the shipped, exported helper — 20/20. Each input compared to what old
4. Live ( 5. Static + CI.
|
| env | generators max_tokens |
escalation 8K→64K | |
|---|---|---|---|
| unset | 8000 | enabled | baseline |
1.5 (malformed) |
8000 (now correct) | suppressed | ⚠ differs from unset |
9000 (valid) |
9000 | suppressed (intended) | ok |
Net: a malformed value still silently disables the truncation-recovery retry, so it does not fully "fall back to existing default behavior" as the PR intends. This is pre-existing and strictly better than before (which was max_tokens=1 and no escalation), so not a blocker — but for completeness consider using the same helper there:
parsePositiveIntegerEnvValue(process.env['QWEN_CODE_MAX_OUTPUT_TOKENS']) !== undefinedMinor notes
- No direct unit test for
parsePositiveIntegerEnvValue. It's covered transitively by 4 generator cases (1.5/2k/abc/9000); the interesting edges (unsafe integers,1e5, leading+) aren't. A few direct unit tests intokenLimits.test.tswould lock in theisSafeInteger/regex behavior. (I verified those edges myself — 20/20.) - Behavior change for the changelog:
+9000and out-of-safe-range values that oldparseIntaccepted are now rejected (→ capped default). Reasonable, but worth a one-line migration note beyond the1.5/2kexamples already given.
Verdict
LGTM — approve / merge. Correct, minimal, well-targeted fix; regression tests proven to catch the bug; verified end-to-end on the real bundle and live CLI; all checks green. Recommend the geminiChat.ts consistency tweak as a fast follow-up (this PR or another).
🇨🇳 中文版(点击展开)
✅ 验证报告 —— 本地真实测试(建议合并,附一个非阻断后续项)
作为维护者,我在 Linux(Node 22.22.2) 上完成了验证:单测、前后对照回归、针对构建产物的边界用例电池,以及 用真实 CLI 在 tmux 里打到 mock OpenAI 服务器、抓取真实出网 max_tokens 的端到端测试。核心修复 正确、测试到位、端到端确认有效。同时发现一处 既有的、非阻断的一致性缺口,建议作为后续项处理(见下)。
漏洞本身
两条生成路径此前用 parseInt(envVal, 10) 解析 QWEN_CODE_MAX_OUTPUT_TOKENS,会半截解析:1.5→1、2k→2,导致非法值悄悄变成极小的 token 预算。本 PR 新增严格的 parsePositiveIntegerEnvValue(/^\d+$/ + Number.isSafeInteger + >0),两条路径都改走它,非法值回退到 capped default(8000)。
已完成的验证
- 受影响测试套件 154/154 通过(
tokenLimits59、openai provider/default26、anthropic69),含 4 个新增用例。 - 前后对照(证明新测试有效):仅把 helper 改回旧
parseInt行为重跑 → 非法值用例 失败(OpenAI/Anthropic 都得到max_tokens: 1,来自1.5),合法值(9000)仍通过。说明新测试确实能捕获该 bug。 - 针对构建产物中导出的 helper 跑边界电池 20/20:每个输入与旧
parseInt对比,8 个输入产生分歧且全是改进(1.5/2k/1e5/100abc/+9000→拒绝;超出安全整数范围的大数→拒绝;MAX_SAFE_INTEGER边界值精确保留)。 tmux实机 E2E(真实打包 CLI → mock OpenAI,抓真实出网max_tokens):交替合法/非法值,证明出网值精确跟随每个 env:
ENV=1.5 (非法) → max_tokens=8000 ✓
ENV=9000 (合法) → max_tokens=9000 ✓
ENV=2k (非法) → max_tokens=8000 ✓
ENV=16000 (合法) → max_tokens=16000 ✓
ENV 未设 (默认) → max_tokens=8000 ✓
- 静态检查 + CI:
eslint、prettier --check、git diff --check均干净;tsc(core)0 错误;GitHub CI 全绿。
⚠️ 非阻断后续项 —— 升级(escalation)判定与严格解析不一致
本 PR 改了两条生成路径,但 没有 改 geminiChat.ts:1983,那里仍用裸的存在性判断:
!!process.env['QWEN_CODE_MAX_OUTPUT_TOKENS'] // 任意非空字符串(含 "1.5")都为 truehasUserMaxTokensOverride 会 关闭 8K→64K 的 MAX_TOKENS 升级重试(geminiChat.ts:2384-2388)。因此本 PR 后,非法值 出现分歧:
| env | 生成路径 max_tokens |
8K→64K 升级 |
|---|---|---|
| 未设 | 8000 | 启用 |
1.5(非法) |
8000(现已正确) | 被抑制 ⚠ 与“未设”不一致 |
9000(合法) |
9000 | 被抑制(符合预期) |
即:非法值仍会悄悄关掉截断恢复重试,未能完全“回退到既有默认行为”。该问题 属既有问题,且明显优于改动前(之前是 max_tokens=1 且无升级),因此 不阻断合并;但为完整起见,建议那里也复用同一 helper:parsePositiveIntegerEnvValue(...) !== undefined。
次要建议
parsePositiveIntegerEnvValue无直接单测:仅通过 4 个生成器用例间接覆盖;有意思的边界(不安全大整数、1e5、前导+)没覆盖到。建议在tokenLimits.test.ts加几条直接单测锁定isSafeInteger/正则行为(这些我已自测 20/20)。- changelog 行为变更:旧
parseInt接受的+9000、超安全范围的大数,现在会被拒绝(回退默认)。合理,但建议在迁移说明里除1.5/2k外补一句。
结论
建议合并(approve / merge):修复正确、最小、定位精准;回归测试已证明能捕获漏洞;已在真实构建产物与实机 CLI 上端到端验证;各项检查全绿。建议把 geminiChat.ts 的一致性微调作为快速后续项(本 PR 或另开)。
What this PR does
Parses
QWEN_CODE_MAX_OUTPUT_TOKENSas a strict positive integer for both OpenAI-compatible and Anthropic content generators, so malformed values no longer get accepted throughparseIntpartial parsing.Why it's needed
Before this change, values like
1.5and2kwere treated as1and2. That can silently override the intended capped default with a much smaller token budget. The env override should only apply when the value is a whole positive integer; malformed values should fall back to the existing default behavior.Reviewer Test Plan
How to verify
Set
QWEN_CODE_MAX_OUTPUT_TOKENSto malformed values such as1.5,2k, orabcand confirm OpenAI-compatible and Anthropic requests keep the capped default8000. Set it to a valid value such as9000and confirm both paths honor the override.Evidence (Before & After)
N/A. This is a non-UI parsing fix covered by unit tests.
Tested on
Environment (optional)
Local Node/npm workspace on macOS.
Risk & Scope
QWEN_CODE_MAX_OUTPUT_TOKENSvalues will now get the capped default instead of a partial parse.Linked Issues
Fixes #5490
中文说明
What this PR does
这个 PR 让 OpenAI-compatible 和 Anthropic 两条生成路径都严格按正整数解析
QWEN_CODE_MAX_OUTPUT_TOKENS,不再让parseInt接受半截数字。Why it's needed
改动前,
1.5会被当成1,2k会被当成2。这会悄悄覆盖原本的 8000 capped default,导致输出 token 预算异常变小。这个环境变量只有在值是完整正整数时才应该生效;格式不对时应该回退到现有默认行为。Reviewer Test Plan
How to verify
把
QWEN_CODE_MAX_OUTPUT_TOKENS设成1.5、2k或abc这类非法值,确认 OpenAI-compatible 和 Anthropic 请求仍使用 capped default8000。再设成9000这类合法值,确认两条路径都正常使用覆盖值。Evidence (Before & After)
N/A。这是非 UI 解析修复,已用单测覆盖。
Tested on
Environment (optional)
macOS 本地 Node/npm workspace。
Risk & Scope
parseInt而被意外接受的半截数字。QWEN_CODE_MAX_OUTPUT_TOKENS,现在会回退到 capped default,不再使用半截解析出来的数字。Linked Issues
Fixes #5490
AI Assistance Disclosure
I used Codex to review the changes, sanity-check the implementation against existing patterns, and help spot potential edge cases.