Skip to content

fix(core): parse QWEN_SERVE_MCP_CLIENT_BUDGET strictly as a decimal integer - #5752

Merged
wenshao merged 3 commits into
QwenLM:mainfrom
he-yufeng:fix/mcp-budget-env-strict
Jun 24, 2026
Merged

fix(core): parse QWEN_SERVE_MCP_CLIENT_BUDGET strictly as a decimal integer#5752
wenshao merged 3 commits into
QwenLM:mainfrom
he-yufeng:fix/mcp-budget-env-strict

Conversation

@he-yufeng

Copy link
Copy Markdown
Contributor

What

readBudgetFromEnv parsed QWEN_SERVE_MCP_CLIENT_BUDGET with Number(rawBudget), so Number("0x10")=16, Number("1e2")=100 and Number("1.0")=1 all passed Number.isInteger and were silently accepted as a budget. This requires plain decimal digits (/^\d+$/), matching the other integer env vars (#5679, #5602, #5612), and keeps the existing stderr breadcrumb for invalid values.

Why

A hex / scientific / float string silently became a budget the operator never intended (e.g. QWEN_SERVE_MCP_CLIENT_BUDGET=0x10 → 16) instead of being rejected like every other malformed value. This finishes the strict-decimal integer-env parsing the codebase already adopted elsewhere.

Reviewer Test Plan

npx vitest run packages/core/src/tools/mcp-client-manager.test.ts -t readBudgetFromEnv

The added test asserts 0x10 / 1e2 / 1.0 are rejected (no budget resolved) while 16 is still accepted. It fails on the old loose Number() parse and passes with this change.

Risk

Low. Pure parsing change in one function; valid decimal budgets are unaffected and the existing stderr breadcrumb still fires for rejected values.

…nteger

readBudgetFromEnv used Number(rawBudget), so Number("0x10")=16, Number("1e2")=100
and Number("1.0")=1 all passed isInteger and were silently accepted as a budget.
Require plain decimal digits (/^\d+$/) like the other integer env vars, keeping
the existing stderr breadcrumb for invalid values.
@wenshao

wenshao commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR, @he-yufeng!

Template has the essential sections (What, Why, Reviewer Test Plan, Risk) ✓

On direction: this closes the last gap in the strict-decimal integer-env parsing series. PRs #5602, #5612, and #5679 (all merged June 22–23) established the pattern for API timeout, serve rate limit, and agent/workflow env vars. This PR brings the two MCP budget parsing paths into alignment. CHANGELOG 0.19.1 lists the prior three strict-parsing fixes — this is the natural completion. Clearly aligned.

On approach: minimal and focused — two parsing sites get the same /^\d+$/ + isSafeInteger treatment, stderr breadcrumb preserved, tests added for both paths. The createWorkspaceMcpBudget export is needed for the new test. No scope creep, no drive-by refactors. Clean.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

方向:这是严格十进制整数 env 解析系列的最后一块。#5602#5612#5679 已在 6 月 22–23 日合并,分别覆盖了 API 超时、serve 速率限制和 agent/workflow env 变量。CHANGELOG 0.19.1 记录了前三个严格解析修复——本 PR 是自然的收尾。方向明确对齐。

方案:极简且聚焦。两个解析点统一使用 /^\d+$/ + isSafeInteger,保留 stderr 提示,两条路径都加了测试。createWorkspaceMcpBudget 导出为测试所需。无范围蔓延,无顺手重构。

进入代码审查 🔍

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Code Review

The fix is correct and consistent. Both parsing sites now use the identical strict pipeline:

trim()/^\d+$/ regex → Number.isSafeInteger()> 0

This rejects 0x10 (hex), 1e2 (scientific), 1.0 (float), 0b101 (binary), negatives, and non-numeric strings — all of which the old Number() parse would silently accept. The mode-simplification in createWorkspaceMcpBudget (dropping the redundant isFinite/isInteger/> 0 guard) is safe because the strict budget gate already guarantees those properties upstream.

Key things verified:

  • Both readBudgetFromEnv (core) and createWorkspaceMcpBudget (cli) now share byte-identical validation logic — the inconsistency where the CLI path lacked trim() is fixed.
  • The mode logic correctly simplifies: once budget is guaranteed to be a positive safe integer, budget !== undefined is sufficient for the 'warn' default.
  • JSDoc updated from "silently ignored" to "rejected … and a stderr breadcrumb is written" — accurate.
  • export added to createWorkspaceMcpBudget for testability — natural visibility change.

No correctness bugs, no security concerns, no over-abstraction. Reuse check: the strict-parsing pattern already exists in parsePoolDrainMs, parseServeRateLimit, and parseAgentIntegerEnvVars — this PR correctly reuses the same idiom rather than inventing a new one.

Unit Tests

Both focused test suites pass with zero regressions:

# packages/core — mcp-client-manager.test.ts (readBudgetFromEnv filter)
 ✓ 5 passed | 97 skipped (102 total)  8ms

# packages/cli — acpAgent.test.ts (createWorkspaceMcpBudget filter)
 ✓ 12 passed | 133 skipped (145 total)  7ms

The CLI test stderr output confirms all invalid values produce the breadcrumb:

qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET='0x10' (expected positive integer); MCP budget enforcement disabled for this child.
qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET='1e2' (expected positive integer); MCP budget enforcement disabled for this child.
qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET='1.0' (expected positive integer); MCP budget enforcement disabled for this child.
qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET='0b101' (expected positive integer); MCP budget enforcement disabled for this child.
qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET='5 abc' (expected positive integer); MCP budget enforcement disabled for this child.
qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET='abc' (expected positive integer); MCP budget enforcement disabled for this child.
qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET='-5' (expected positive integer); MCP budget enforcement disabled for this child.
qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET='0' (expected positive integer); MCP budget enforcement disabled for this child.
qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET=' ' (expected positive integer); MCP budget enforcement disabled for this child.

Tmux Testing

N/A — this is a non-TUI env-parsing fix. The behavior change is purely internal (rejecting malformed env values that were previously silently accepted). Unit tests fully cover both parsing paths with comprehensive edge cases.

中文说明

代码审查

修复正确且一致。两个解析点现在使用完全相同的严格管线:

trim()/^\d+$/ 正则 → Number.isSafeInteger()> 0

拒绝了 0x10(十六进制)、1e2(科学计数法)、1.0(浮点)、0b101(二进制)、负数和非数字字符串——旧的 Number() 解析会静默接受所有这些值。createWorkspaceMcpBudget 中 mode 逻辑的简化(去掉冗余的 isFinite/isInteger/> 0 检查)是安全的,因为严格 budget 解析已在上游保证了这些属性。

关键确认:

  • readBudgetFromEnv(core)和 createWorkspaceMcpBudget(cli)现在共享完全一致的验证逻辑——CLI 路径缺少 trim() 的不一致已修复。
  • JSDoc 从 "silently ignored" 更新为 "rejected … and a stderr breadcrumb is written"——准确。
  • createWorkspaceMcpBudget 加了 export 以便测试——合理的可见性变更。

无正确性 bug,无安全隐患,无过度抽象。复用检查:严格解析模式已存在于 parsePoolDrainMsparseServeRateLimitparseAgentIntegerEnvVars——本 PR 正确复用了同一惯用模式。

单元测试

两套聚焦测试均通过,零回归:

  • core: 5/5 通过(97 跳过,共 102)
  • cli: 12/12 通过(133 跳过,共 145)

CLI 测试的 stderr 输出确认所有非法值都产生了提示信息。

Tmux 测试

不适用——这是非 TUI 的 env 解析修复。行为变化在内部(拒绝之前被静默接受的非法 env 值)。单元测试已完全覆盖两条解析路径。

Qwen Code · qwen3.7-max

@qwen-code-ci-bot

qwen-code-ci-bot commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

This is a clean finishing PR. The codebase already committed to strict decimal-env parsing in three earlier PRs (#5602, #5612, #5679); this one closes the two remaining MCP budget sites with the same proven pattern. The diff is +86 / −21 across 4 files — exactly what the goal requires, nothing more.

My independent proposal was: add a /^\d+$/ pre-check before Number(), use isSafeInteger instead of isInteger, apply to both parsing sites, add rejection tests. The PR does exactly this, plus adds trim() for whitespace tolerance and a stderr breadcrumb for operator visibility — both improvements I'd have wanted but didn't think to add. The PR exceeds my baseline.

Both parsing paths are now byte-identical in their validation logic, both have dedicated test coverage (17 test cases total covering accept and reject paths), and all tests pass. The author's follow-up commits (7ce2a88 addressing the createWorkspaceMcpBudget gap, f892053 updating the JSDoc) show excellent review responsiveness.

No reservations. Ships the fix cleanly. ✅

中文说明

这是一个干净的收尾 PR。代码库已在三个早期 PR(#5602#5612#5679)中承诺了严格十进制 env 解析;本 PR 用同样的已验证模式补上剩余的两个 MCP budget 入口。diff 为 +86 / −21,跨 4 个文件——恰好覆盖目标,没有多余内容。

我的独立方案是:在 Number() 前加 /^\d+$/ 预检查,用 isSafeInteger 替代 isInteger,应用到两个解析点,加拒绝测试。PR 完全做到了这些,还额外加了 trim() 容错空格和 stderr 操作提示——都是我会想要但没想到的改进。PR 超越了我的基线。

两条解析路径的验证逻辑完全一致,都有专项测试覆盖(共 17 个测试用例覆盖接受和拒绝路径),所有测试通过。作者的后续提交(7ce2a886 修复 createWorkspaceMcpBudget 缺口,f892053 更新 JSDoc)体现了良好的 review 响应。

无顾虑。干净地交付修复。✅

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. ✅

@doudouOUC doudouOUC 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.

[Critical] createWorkspaceMcpBudget in packages/cli/src/acp-integration/acpAgent.ts:2497-2518 still parses QWEN_SERVE_MCP_CLIENT_BUDGET with the old loose Number(rawBudget) + Number.isInteger pattern. The comment on line 2499 says "Match McpClientManager.readBudgetFromEnv's parsing exactly" — but that invariant is now broken.

With QWEN_SERVE_MCP_CLIENT_BUDGET=0x10: readBudgetFromEnv rejects it (budget=undefined), but createWorkspaceMcpBudget accepts it as 16. The two enforcement layers disagree.

Fix: extract a shared parsing helper (or reuse parsePositiveIntegerEnv from packages/core/src/utils/env.ts) and call it from both sites.

— qwen3.7-max via Qwen Code /review

// and Number('1.0')=1 all pass isInteger, so a loose parse would silently
// accept them. Only plain decimal digits should set a budget.
const trimmed = rawBudget.trim();
const parsed = Number(trimmed);

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.

[Suggestion] The inline parsing logic (trim()/^\d+$/Number.isSafeInteger> 0) is a reimplementation of the existing parsePositiveIntegerEnv utility in packages/core/src/utils/env.ts (same package). If validation rules change, all copies must be updated in lockstep.

Consider replacing the inline parse with:

const budget = parsePositiveIntegerEnv('QWEN_SERVE_MCP_CLIENT_BUDGET');

The stderr warning branch still works since undefined maps to the else path.

— qwen3.7-max via Qwen Code /review

try {
for (const bad of ['0x10', '1e2', '1.0']) {
process.env['QWEN_SERVE_MCP_CLIENT_BUDGET'] = bad;
const manager = mkManager({ config: configWithServers({}) });

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.

[Suggestion] writeSpy is created on process.stderr.write but never asserted on. The adjacent pre-existing test (line 2609) follows the same setup pattern but does assert: expect(calls.some(s => s.includes('ignoring invalid ...'))).toBe(true).

The stderr breadcrumb is the sole operator-facing signal for rejected values. Add an assertion after the rejection loop:

const calls = writeSpy.mock.calls.map((c) => String(c[0]));
expect(calls.some(s => s.includes("'0x10'"))).toBe(true);

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

✅ Local runtime verification (real qwen processes, A/B PR vs base)

I built two real bundles from this PR's head (162c38d11) — one with the fix, one with only mcp-client-manager.ts reverted to the merge-base — and exercised QWEN_SERVE_MCP_CLIENT_BUDGET parsing through real qwen runs (headless -p and the interactive TUI), plus a unit‑level A/B. The breadcrumb qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET=… is the observable: present ⇒ rejected, absent ⇒ accepted as a budget.

Runtime A/B matrix (real qwen process per cell, same env, two bundles)

QWEN_SERVE_MCP_CLIENT_BUDGET BASE (loose Number()) PR (strict /^\d+$/ + isSafeInteger)
0x10 (hex) accepted (→16) rejected ⬅ fixed
1e2 (scientific) accepted (→100) rejected ⬅ fixed
1.0 (float) accepted (→1) rejected ⬅ fixed
16.0 (float) accepted (→16) rejected ⬅ fixed
99999999999999999999 (unsafe int) accepted rejected ⬅ fixed (bonus)
abc rejected rejected control — unchanged
0 / -5 rejected rejected control — unchanged
16 / 100 (valid) accepted accepted control — unchanged

So the change is targeted: the five previously‑silently‑accepted malformed forms (hex / scientific / float / unsafe‑large) now get rejected with the operator breadcrumb, while valid decimals still work and values that were already rejected are untouched. The 99999999999999999999 row is a nice extra — base's Number.isInteger(1e20) passed, so it accepted a precision‑losing budget; the PR's Number.isSafeInteger closes that too.

Real TUI confirmation (same bundle, interactive): …=0x10 → breadcrumb on stderr (rejected); …=16 → no breadcrumb (accepted).

Unit‑level A/B (resolved value, authoritative)

packages/core/src/tools/mcp-client-manager.test.ts -t "readBudgetFromEnv rejects non-decimal"

  • PR source → ✅ passes (getMcpClientBudget() is undefined for 0x10/1e2/1.0).
  • Base source (file reverted to merge‑base) → ❌ fails: AssertionError: expected 16 to be undefined — proving base silently resolved 0x10 to a budget of 16.
  • Full mcp-client-manager.test.ts on PR: 102/102 pass (no regression).

Notes

  • This parser runs in the ACP child / standalone qwen path (the CLI --mcp-client-budget flag is separately validated in run-qwen-serve.ts and unaffected). Verified via real qwen startup where the McpClientManager constructor calls readBudgetFromEnv().
  • The existing stderr breadcrumb still fires for every rejected value (confirmed live).

Verdict

Behaves exactly as described, in real qwen processes and at the unit level, with a clean A/B against base and no regression. The fix also closes an adjacent unsafe‑integer hole. LGTM.

中文版(合并参考)

✅ 本地真实运行时验证(真实 qwen 进程,PR vs base 的 A/B)

我从本 PR 的最新 head(162c38d11)构建了两个真实 bundle——一个含修复,另一个仅把 mcp-client-manager.ts 回退到 merge-base——并通过真实的 qwen 运行(headless -p 与交互式 TUI)外加单元级 A/B,验证了 QWEN_SERVE_MCP_CLIENT_BUDGET 的解析。可观察信号是 breadcrumb qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET=…:出现=被拒绝,不出现=被当作 budget 接受。

运行时 A/B 矩阵(每格一个真实 qwen 进程,相同环境,两个 bundle)

QWEN_SERVE_MCP_CLIENT_BUDGET BASE(宽松 Number() PR(严格 /^\d+$/ + isSafeInteger
0x10(十六进制) 接受(→16) 拒绝 ⬅ 已修复
1e2(科学计数) 接受(→100) 拒绝 ⬅ 已修复
1.0(浮点) 接受(→1) 拒绝 ⬅ 已修复
16.0(浮点) 接受(→16) 拒绝 ⬅ 已修复
99999999999999999999(不安全整数) 接受 拒绝 ⬅ 已修复(额外收益)
abc 拒绝 拒绝 对照——不变
0 / -5 拒绝 拒绝 对照——不变
16 / 100(合法) 接受 接受 对照——不变

可见改动是精准的:此前被静默接受的五类畸形值(十六进制/科学计数/浮点/超大不安全整数)现在都会被拒绝并打印 breadcrumb;合法十进制仍正常工作;本就被拒绝的值不受影响。99999999999999999999 这一行是额外亮点——base 的 Number.isInteger(1e20) 为真,于是接受了一个会丢精度的 budget,而 PR 的 Number.isSafeInteger 也把它堵上了。

真实 TUI 佐证(同一 bundle,交互式):…=0x10 → stderr 出现 breadcrumb(拒绝);…=16 → 无 breadcrumb(接受)。

单元级 A/B(解析后的实际取值,权威)

packages/core/src/tools/mcp-client-manager.test.ts -t "readBudgetFromEnv rejects non-decimal"

  • PR 源码 → ✅ 通过(0x10/1e2/1.0getMcpClientBudget()undefined)。
  • Base 源码(该文件回退到 merge-base)→ ❌ 失败:AssertionError: expected 16 to be undefined——证明 base 把 0x10 静默解析成了 budget 16
  • PR 上完整跑 mcp-client-manager.test.ts102/102 通过(无回归)。

说明

  • 该解析运行在 ACP child / 独立 qwen 路径(CLI 的 --mcp-client-budget flag 在 run-qwen-serve.ts 中另有校验,不受影响)。通过真实 qwen 启动时 McpClientManager 构造函数调用 readBudgetFromEnv() 得到验证。
  • 既有的 stderr breadcrumb 对每个被拒绝的值仍会触发(已实机确认)。

结论

在真实 qwen 进程与单元层面,行为均与描述完全一致;与 base 的 A/B 干净、无回归,并顺带堵上了相邻的不安全整数漏洞。LGTM。

wenshao
wenshao previously approved these changes Jun 23, 2026

@doudouOUC doudouOUC 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.

[Critical] createWorkspaceMcpBudget in packages/cli/src/acp-integration/acpAgent.ts:2497-2518 still parses QWEN_SERVE_MCP_CLIENT_BUDGET with loose Number(rawBudget) + Number.isInteger — no regex, no trim, no isSafeInteger. The comment at line 2497 says "Match McpClientManager.readBudgetFromEnv's parsing exactly" but is now false after this PR.

The pool (acpAgent.ts) and per-client manager (readBudgetFromEnv) silently disagree: QWEN_SERVE_MCP_CLIENT_BUDGET=0x10 is rejected by the manager (no budget) but accepted by the pool (budget=16). Fix: apply the same /^\d+$/ + isSafeInteger + trim() pattern, or extract a shared utility.

Also: CLI-layer validation in serve.ts:343-345 and run-qwen-serve.ts:656-658 still uses isInteger not isSafeInteger — narrow inconsistency but the three validation sites should agree.

— qwen3.7-max via Qwen Code /review

});

it('readBudgetFromEnv rejects non-decimal budget values (hex / scientific / float)', async () => {
const writeSpy = vi.spyOn(process.stderr, 'write').mockReturnValue(true);

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.

[Suggestion] The .trim() code path added at source line 284 is not tested. Consider adding whitespace-padded values: " 16 " (should be accepted as 16) and " 0x10 " (should still be rejected after trim) to the test cases.

— qwen3.7-max via Qwen Code /review

// a plain decimal integer is still accepted.
process.env['QWEN_SERVE_MCP_CLIENT_BUDGET'] = '16';
const ok = mkManager({ config: configWithServers({}) });
expect(ok.getMcpClientBudget()).toBe(16);

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.

[Suggestion] The upgrade from isInteger to isSafeInteger tightens the upper bound, but no test exercises the boundary. Consider adding "9007199254740992" (MAX_SAFE_INTEGER + 1, passes /^\d+$/ but fails isSafeInteger) to the rejection loop, and "9007199254740991" to the acceptance assertions to bracket the boundary.

— qwen3.7-max via Qwen Code /review

The pooled path parsed QWEN_SERVE_MCP_CLIENT_BUDGET with a loose Number() +
isInteger, so 0x10, 1e2 and 1.0 silently set a budget while the manager's
readBudgetFromEnv (already strict) rejected them. Use the same /^\d+$/ +
isSafeInteger parse and emit the same stderr breadcrumb on invalid input.
Export the function and cover the parsing with unit tests.
@he-yufeng
he-yufeng dismissed stale reviews from wenshao and qwen-code-ci-bot via 7ce2a88 June 23, 2026 12:55
@he-yufeng

Copy link
Copy Markdown
Contributor Author

Good catch, thanks — fixed in 7ce2a88.

createWorkspaceMcpBudget now parses QWEN_SERVE_MCP_CLIENT_BUDGET with the same /^\d+$/ + Number.isSafeInteger check (with trim()) as McpClientManager.readBudgetFromEnv, and emits the same stderr breadcrumb on invalid input, so the pool and the manager agree on what's accepted. The follow-up isFinite / isInteger / > 0 guards are gone since the strict parse already guarantees a positive safe integer, and the comment no longer over-promises.

I exported the function and added unit tests in acpAgent.test.ts covering 0x10, 1e2, 1.0, 0b101, 5 abc, -5, 0, whitespace and unset — all rejected, plain decimals (incl. trimmed) accepted.

@wenshao

wenshao commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /review

@github-actions

Copy link
Copy Markdown
Contributor
_Qwen Code review request accepted. Review is queued in [workflow run](https://github.com/QwenLM/qwen-code/actions/runs/28029781007)._

// Match `McpClientManager.readBudgetFromEnv`'s parsing exactly.
// Use `Number(...)` + `Number.isInteger` so the pool and the manager
// Match `McpClientManager.readBudgetFromEnv`'s parsing exactly: only plain
// decimal digits set a budget. A loose `Number(...)` would silently accept

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.

[Suggestion] The comment says "Match McpClientManager.readBudgetFromEnv's parsing exactly" but this is only true for the budget value parsing. The mode handling diverges in two observable ways:

  1. readBudgetFromEnv emits a stderr breadcrumb when QWEN_SERVE_MCP_BUDGET_MODE is set to an unrecognized value (e.g. ENFORCE); createWorkspaceMcpBudget silently falls through to the default.
  2. readBudgetFromEnv emits a stderr breadcrumb when mode is enforce/warn but no budget is set (downgrading to off); createWorkspaceMcpBudget returns undefined silently.

A future maintainer reading "exactly" may skip checking readBudgetFromEnv for features to replicate here. Consider scoping the claim:

Suggested change
// decimal digits set a budget. A loose `Number(...)` would silently accept
// Match `McpClientManager.readBudgetFromEnv`'s budget-value parsing:

vi.clearAllMocks();
});

it('accepts a plain positive decimal integer', () => {

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.

[Suggestion] MODE is declared and cleaned up in afterEach but never set in any test case. The mode-resolution IIFE has four branches (enforce, warn, off, and the fallback budget !== undefined ? 'warn' : 'off') — all untested. Consider adding at least:

it('respects an explicit budget mode', () => {
  process.env[KEY] = '10';
  process.env[MODE] = 'enforce';
  // ...assert mode is 'enforce'
});

it('returns undefined for mode=off even with a budget', () => {
  process.env[KEY] = '10';
  process.env[MODE] = 'off';
  expect(createWorkspaceMcpBudget(onEvent)).toBeUndefined();
});

});

it('accepts a plain positive decimal integer', () => {
process.env[KEY] = '100';

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.

[Suggestion] The "accepts" tests only assert toBeDefined(), which verifies the constructor was reached but not what was passed to it. The WorkspaceMcpBudget mock discards constructor arguments, so a regression that passes a wrong budget value or wrong default mode would go undetected.

Compare with the core test which asserts the actual parsed value (expect(ok.getMcpClientBudget()).toBe(16)). Since WorkspaceMcpBudget is a vi.fn(), you could assert on constructor call args:

const MockBudget = vi.mocked(WorkspaceMcpBudget);
createWorkspaceMcpBudget(onEvent);
expect(MockBudget).toHaveBeenCalledWith(
  expect.objectContaining({ clientBudget: 100, mode: 'warn' }),
);

qqqys
qqqys previously approved these changes Jun 23, 2026

@qqqys qqqys 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.

Critical recheck: the loose MCP budget parsing issue is fixed on the current head. Both the core MCP manager and ACP workspace child helper now reject non-decimal / unsafe values, and I did not find a new critical blocker.

@wenshao

wenshao commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@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 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Maintainer verification — local real-binary + tmux E2E ✅

Built this PR from a clean worktree at the head (7ce2a8864), ran the focused tests, drove the real qwen --acp binary in tmux across a malformed/valid budget matrix, and ran a mutation A/B to prove the new tests actually catch the bug. The fix is correct, complete, and regression-free.

Environment

  • macOS (darwin), Node v22.22.2, npm 10.9.7
  • Worktree at PR head 7ce2a8864, fresh npm ci + npm run build

1. Build / typecheck / focused tests — all green

Check Result
npm run build (root) ✅ exit 0 (only pre-existing vscode-ide-companion lint warnings, 0 errors)
npm run typecheck (root) ✅ exit 0
core: mcp-client-manager.test.ts -t readBudgetFromEnv ✅ 5 passed
cli: acpAgent.test.ts -t createWorkspaceMcpBudget ✅ 12 passed

2. Real tmux E2E — the actual qwen --acp production path

The daemon (qwen serve) forwards a validated number / scrubs the env when spawning children, so the loose-Number() bug actually bites the direct qwen --acp path, where createWorkspaceMcpBudget() (and core's readBudgetFromEnv()) read the env string directly. I drove the real built binary over ndjson stdio: initializeqwen/status/workspace/mcp, reading the surfaced clientBudget/budgetMode and watching stderr for the breadcrumb.

QWEN_SERVE_MCP_CLIENT_BUDGET breadcrumb clientBudget budgetMode
16 16 warn ✅ accepted
100 100 warn ✅ accepted
" 42 " 42 warn ✅ accepted (trim)
0x10 ⚠️ emitted absent off rejected (was silently 16)
1e2 ⚠️ emitted absent off rejected (was 100)
1.0 ⚠️ emitted absent off rejected (was 1)
0 ⚠️ emitted absent off ✅ rejected (> 0)
-5 ⚠️ emitted absent off ✅ rejected
(unset) absent off ✅ no budget
0x10 + MODE=enforce ⚠️ emitted absent off ✅ rejected; enforce safely downgraded to off (no valid budget → never silently enforces)

The three headline values (0x10/1e2/1.0) now produce the operator breadcrumb qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET=... and resolve to no budget, instead of silently becoming 16/100/1.

3. Mutation A/B — the new tests genuinely catch the bug

Reverted only the parse condition back to the old loose Number() + isInteger in both files and re-ran the focused tests (then restored):

  • core: readBudgetFromEnv rejects non-decimal budget valuesFAILS with expected 16 to be undefined (i.e. 0x10→16 slips through the old parse).
  • cli: the 0x10 / 1e2 / 1.0 / 0b101 rejection cases → FAIL (expected {…} to be undefined), while 5 abc / abc / -5 / 0 / " " still pass (the old Number() already rejected those as NaN/≤0).

This precisely isolates what the strict /^\d+$/ + isSafeInteger adds: rejecting hex / scientific / float / binary literals that Number() silently coerces. The tests are non-vacuous.

4. Completeness — no third parser missed

A repo-wide grep confirms exactly two functions parse the env string, and the PR fixes both:

  • packages/core/src/tools/mcp-client-manager.tsreadBudgetFromEnv()
  • packages/cli/src/acp-integration/acpAgent.tscreateWorkspaceMcpBudget()

run-qwen-serve.ts only writes the var from a CLI-validated number (childEnvOverrides), and daemon-status.ts only copies an already-parsed number — neither re-parses the string. Number.isSafeInteger is also a small upgrade over the old Number.isInteger (rejects out-of-range magnitudes like 99999999999999999999).

Notes (minor, non-blocking)

  • The createWorkspaceMcpBudget path now also emits the stderr breadcrumb for invalid values (previously silent) — a welcome operator-visibility improvement that brings it in line with the manager. Slightly more than "pure parsing," but a net positive.
  • The parse logic is duplicated across core and cli, kept in sync by comments. Pre-existing pattern (not introduced here); a shared exported helper would remove the future-drift risk, but that's an optional follow-up.

Verdict

Correct, complete, well-tested, no regressions. The real binary rejects exactly the values the PR targets and accepts valid decimals. LGTM. 👍

🇨🇳 中文版(完整对应)

维护者验证 —— 本地真实二进制 + tmux 端到端 ✅

在 PR head(7ce2a8864)的干净 worktree 上构建本 PR,跑了 focused 测试,在 tmux 中驱动真实 qwen --acp 二进制跑了一组「非法/合法 budget」矩阵,并做了变异 A/B 证明新测试确实能抓到这个 bug。修复正确、完整、无回归。

环境

  • macOS(darwin),Node v22.22.2,npm 10.9.7
  • worktree 位于 PR head 7ce2a8864,全新 npm ci + npm run build

1. 构建 / 类型检查 / focused 测试 —— 全绿

检查项 结果
npm run build(根) ✅ exit 0(仅 vscode-ide-companion 既有 lint warning,0 error)
npm run typecheck(根) ✅ exit 0
core:mcp-client-manager.test.ts -t readBudgetFromEnv ✅ 5 passed
cli:acpAgent.test.ts -t createWorkspaceMcpBudget ✅ 12 passed

2. 真实 tmux 端到端 —— 真正会触发 bug 的 qwen --acp 路径

daemon(qwen serve)在 spawn child 时转发的是已校验的 number / 或直接 scrub 掉 env,所以松散 Number() bug 真正咬到的是直接 qwen --acp 路径 —— 此时 createWorkspaceMcpBudget()(以及 core 的 readBudgetFromEnv())直接读 env 字符串。我用 ndjson stdio 驱动真实构建出的二进制:initializeqwen/status/workspace/mcp,读取暴露出的 clientBudget/budgetMode,并观察 stderr 的 breadcrumb。

QWEN_SERVE_MCP_CLIENT_BUDGET breadcrumb clientBudget budgetMode
16 16 warn ✅ 接受
100 100 warn ✅ 接受
" 42 " 42 warn ✅ 接受(trim)
0x10 ⚠️ 输出 off 拒绝(旧版静默变 16)
1e2 ⚠️ 输出 off 拒绝(旧版 100)
1.0 ⚠️ 输出 off 拒绝(旧版 1)
0 ⚠️ 输出 off ✅ 拒绝(> 0)
-5 ⚠️ 输出 off ✅ 拒绝
(未设置) off ✅ 无 budget
0x10 + MODE=enforce ⚠️ 输出 off ✅ 拒绝;enforce 安全降级为 off(无有效 budget → 绝不静默执行)

三个核心值(0x10/1e2/1.0)现在会打印 operator breadcrumb qwen serve: ignoring invalid QWEN_SERVE_MCP_CLIENT_BUDGET=... 并解析为无 budget,而不是静默变成 16/100/1

3. 变异 A/B —— 新测试确实抓得到这个 bug

把两个文件的解析条件改回旧的松散 Number() + isInteger,复跑 focused 测试(随后还原):

  • core:readBudgetFromEnv rejects non-decimal budget values失败,报 expected 16 to be undefined(即 0x10→16 从旧解析漏过)。
  • cli:0x10 / 1e2 / 1.0 / 0b101 的拒绝用例 → 失败(expected {…} to be undefined),而 5 abc / abc / -5 / 0 / " " 仍通过(旧 Number() 本就把它们判为 NaN/≤0)。

这精确隔离出严格 /^\d+$/ + isSafeInteger 新增拦截的正是 Number() 会静默 coerce 的 hex / 科学计数 / 浮点 / 二进制字面量。测试非空过。

4. 完整性 —— 没有漏掉第三个 parser

全仓库 grep 确认恰好两个函数解析该 env 字符串,PR 两个都修了:

  • packages/core/src/tools/mcp-client-manager.tsreadBudgetFromEnv()
  • packages/cli/src/acp-integration/acpAgent.tscreateWorkspaceMcpBudget()

run-qwen-serve.ts 只是把 CLI 校验过的 number 写入 env(childEnvOverrides),daemon-status.ts 只是复制已解析的 number —— 都不重新解析字符串。Number.isSafeInteger 相比旧的 Number.isInteger 也是个小升级(能拒绝 99999999999999999999 这类超范围量级)。

备注(次要,不阻塞)

  • createWorkspaceMcpBudget 路径现在对非法值也会输出 stderr breadcrumb(以前是静默)—— 这是个不错的 operator 可见性改进,和 manager 对齐了。比「纯解析」略多一点,但是净收益。
  • 解析逻辑在 corecli 两处重复,靠注释保持同步。这是既有模式(非本 PR 引入);提取一个共享导出 helper 可消除未来漂移风险,属可选后续。

结论

正确、完整、测试充分、无回归。真实二进制恰好拒绝 PR 针对的那些值、接受合法十进制。LGTM。👍

@doudouOUC doudouOUC 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.

[Suggestion] Stale JSDoc at mcp-client-manager.ts:271-274 — the doc comment says invalid budget values are "silently ignored (treated as unset)", but the function emits a stderr breadcrumb for invalid values. Since this PR modifies the parsing logic in this function, the doc should be updated to match reality:

 * `QWEN_SERVE_MCP_CLIENT_BUDGET` — positive integer; non-numeric /
 *   zero / negative / NaN are rejected with a stderr warning and
 *   treated as unset.

(Not inline-commentable because the JSDoc lines are outside the diff range.)

Previous Critical (acpAgent.ts loose parsing) is resolved. No blockers remain.

— qwen3.7-max via Qwen Code /review

The doc still said invalid budget values are silently ignored, but the
function now writes a stderr breadcrumb when it rejects one. Match the
doc to the behavior.
@he-yufeng
he-yufeng dismissed stale reviews from qwen-code-ci-bot and qqqys via f892053 June 24, 2026 00:52
@he-yufeng

Copy link
Copy Markdown
Contributor Author

Good catch — updated the JSDoc in f892053 so it says invalid values are rejected and a stderr breadcrumb is written, matching what the function now does.

@wenshao

wenshao commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@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. ✅

@doudouOUC doudouOUC 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.

No issues found. LGTM! ✅

Both parsing sites now use the identical strict pipeline: trim()/^\d+$/Number.isSafeInteger()> 0. The JSDoc is updated to match the stderr breadcrumb behavior. Tests pass.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

✅ Local verification — strict-decimal budget parsing

I built this branch locally and verified it end-to-end with a real before/after harness driving the compiled output (not just the unit tests). Verdict: behaves exactly as described — safe to merge.

What I drove

Both real parsing paths, against the built dist (no mocks):

Path Entry point Observed via
core readBudgetFromEnv() new McpClientManager(…).getMcpClientBudget() (real exported class)
cli / acp createWorkspaceMcpBudget(onEvent) direct call (now exported)

Method

  1. Built core + cli on fix/mcp-budget-env-strict, ran a harness over a 15-value QWEN_SERVE_MCP_CLIENT_BUDGET matrix on the PR build.
  2. Reverted the two source files to the pre-fix baseline (162c38d11^) — confirmed to be exactly the PR minus its change (the only commits touching those files since are this PR's three) — rebuilt, and ran the same harness. To observe the cli path on the pre-fix build I added only the export keyword; the pre-fix parsing logic was left untouched.
  3. Diffed the two runs.

Before → after (real compiled output)

① Silently-coerced values — the actual bug (now rejected on both paths):

QWEN_SERVE_MCP_CLIENT_BUDGET Number() before after
0x10 16 🟥 accepted, budget=16 ✅ rejected + stderr breadcrumb
1e2 100 🟥 accepted, budget=100 ✅ rejected + stderr breadcrumb
1.0 1 🟥 accepted, budget=1 ✅ rejected + stderr breadcrumb
0b101 5 🟥 accepted, budget=5 ✅ rejected + stderr breadcrumb
0o17 15 🟥 accepted, budget=15 ✅ rejected + stderr breadcrumb

② Already-invalid values — outcome unchanged, but the cli path now emits the operator-visibility breadcrumb (core already did):

-5, 0, abc, 5 abc → rejected before and after; cli stderr breadcrumb false → true.

③ Unchanged — no regression:

16, 100, 42 (whitespace trimmed), 010 → accepted with the identical budget value before and after; "" / unset → no-op, no breadcrumb, on both builds.

Tests

  • RED on pre-fix source (validates the PR's own Reviewer Test Plan): core → expected 16 to be undefined; cli → exactly the 4 coerced cases (0x10 / 1e2 / 1.0 / 0b101) fail.
  • GREEN on PR source: all newly-added tests pass.
  • Full changed test files: mcp-client-manager.test.ts 102/102, acpAgent.test.ts 145/145.

Notes

  • .trim() preserves the prior whitespace tolerance ( 42 → 42), so padded values are not a regression.
  • isSafeInteger additionally rejects digit strings beyond Number.MAX_SAFE_INTEGER (which the old isInteger check accepted as a garbage budget) — a small bonus hardening.
  • The core and cli paths now return identical accept/reject decisions for every value tested, fulfilling the "match readBudgetFromEnv exactly" goal.

LGTM 👍

中文说明

✅ 本地验证 —— 严格十进制预算解析

我在本地构建了该分支,并用 真实的 before/after 测试脚本驱动编译产物(dist) 做了端到端验证(不仅仅依赖单元测试)。结论:行为与描述完全一致,可以安全合并。

验证了什么

两条真实的解析路径,均针对构建后的 dist(无 mock):

路径 入口 观测方式
core readBudgetFromEnv() new McpClientManager(…).getMcpClientBudget()(真实导出类)
cli / acp createWorkspaceMcpBudget(onEvent) 直接调用(本 PR 新导出)

方法

  1. fix/mcp-budget-env-strict 上构建 core + cli,对 QWEN_SERVE_MCP_CLIENT_BUDGET 的 15 个取值矩阵在 PR 构建 上跑脚本。
  2. 把两个源文件回退到修复前基线(162c38d11^)—— 已确认它 恰好等于「PR 减去本次改动」(自该基线以来只有本 PR 的三个提交动过这两个文件)—— 重新构建后用 同一套 脚本再跑一遍。为了在修复前构建上观测 cli 路径,我仅添加了 export 关键字,解析逻辑本身原样保留。
  3. 对比两次结果。

Before → after(真实编译产物)

① 被静默强转的取值 —— 真正的缺陷(修复后两条路径均拒绝):

QWEN_SERVE_MCP_CLIENT_BUDGET Number() 修复前 修复后
0x10 16 🟥 接受,budget=16 ✅ 拒绝 + stderr 提示
1e2 100 🟥 接受,budget=100 ✅ 拒绝 + stderr 提示
1.0 1 🟥 接受,budget=1 ✅ 拒绝 + stderr 提示
0b101 5 🟥 接受,budget=5 ✅ 拒绝 + stderr 提示
0o17 15 🟥 接受,budget=15 ✅ 拒绝 + stderr 提示

② 本就非法的取值 —— 结果不变,但 cli 路径现在会输出便于运维排查的 breadcrumb(core 之前已有):

-50abc5 abc → 修复前后均被拒绝;cli 的 stderr 提示由 false → true

③ 不变 —— 无回归:

16100 42 (去除空白)、010 → 修复前后均以 完全相同 的预算值被接受;"" / 未设置 → 两种构建下都是空操作、无提示。

测试

  • 修复前源码上 RED(验证 PR 自带的 Reviewer Test Plan):core → expected 16 to be undefined;cli → 恰好是 4 个被强转的用例(0x10 / 1e2 / 1.0 / 0b101)失败。
  • PR 源码上 GREEN:新增测试全部通过。
  • 改动涉及的完整测试文件mcp-client-manager.test.ts 102/102acpAgent.test.ts 145/145

备注

  • .trim() 保留了原有的空白容忍度( 42 → 42),padded 值不构成回归。
  • isSafeInteger 额外拒绝了超过 Number.MAX_SAFE_INTEGER 的数字串(旧的 isInteger 会把它当成一个垃圾预算接受)—— 顺带的小幅加固。
  • core 与 cli 两条路径现在对所有被测取值给出完全一致的接受/拒绝判定,达成了「与 readBudgetFromEnv 完全一致」的目标。

LGTM 👍

🤖 Verified locally with Claude Code (model: Opus 4.8).

@wenshao
wenshao merged commit ebf29f1 into QwenLM:main Jun 24, 2026
23 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.

5 participants