Skip to content

fix(acp): reject fractional readTextFile limits#6704

Merged
wenshao merged 3 commits into
QwenLM:mainfrom
VectorPeak:codex/acp-read-limit-integer
Jul 11, 2026
Merged

fix(acp): reject fractional readTextFile limits#6704
wenshao merged 3 commits into
QwenLM:mainfrom
VectorPeak:codex/acp-read-limit-integer

Conversation

@VectorPeak

@VectorPeak VectorPeak commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Rejects positive fractional line / limit values in the ACP readTextFile inline fallback before they reach manual line slicing.

This is intentionally scoped to the embedded/no-BridgeFileSystem fallback path. The normal stock qwen serve path already routes ACP file reads through createBridgeFileSystemAdapter(...) and WorkspaceFileSystem.readText(), which rejects malformed positive fractional windows as parse_error. This PR keeps an adapter regression test to document that existing production-path validation, and also maps ACP-wrapped FsError('parse_error') to JSON-RPC Invalid Params (-32602) so the injected and inline paths classify malformed read-window params consistently.

Why it's needed

What Problem This Solves

readTextFile.line and readTextFile.limit are discrete line-window controls. line selects a 1-based starting line and limit caps the maximum number of lines returned. Positive fractional values such as 1.5 do not have a meaningful line-window interpretation.

Before this change, the inline BridgeClient.readTextFile() fallback only handled non-positive limit values. A malformed positive fractional request could continue into manual slicing when no BridgeFileSystem was injected:

await client.readTextFile({
  path: 'lines.txt',
  sessionId: 'sess:test',
  line: 1,
  limit: 1.5,
});

A sibling malformed request with line: 1.5 could also compute a fractional start before calling sliceLineRange():

await client.readTextFile({
  path: 'lines.txt',
  sessionId: 'sess:test',
  line: 1.5,
  limit: 1,
});

The realistic application scope is narrow: custom or embedded ACP consumers that construct @qwen-code/acp-bridge / createAcpSessionBridge(...) without BridgeOptions.fileSystem, then accept raw ACP readTextFile params from a custom child. This is not a demonstrated stock-user qwen serve bug.

Changes

 if (typeof params.limit === 'number' && params.limit <= 0) {
   return { content: '' };
 }
+if (
+  typeof params.limit === 'number' &&
+  params.limit > 0 &&
+  !Number.isSafeInteger(params.limit)
+) {
+  throw RequestError.invalidParams(
+    undefined,
+    `\`limit\` must be a positive integer, got ${params.limit}`,
+  );
+}
+if (
+  typeof params.line === 'number' &&
+  params.line > 0 &&
+  !Number.isSafeInteger(params.line)
+) {
+  throw RequestError.invalidParams(
+    undefined,
+    `\`line\` must be a positive integer, got ${params.line}`,
+  );
+}

The inline fallback now rejects positive non-safe-integer line / limit values before opening the file or entering manual line slicing. Existing non-positive limit behavior is preserved.

The ACP error wrapper now maps FsError('parse_error') to JSON-RPC Invalid Params (-32602) while still preserving data.errorKind, data.status, and data.hint. Other FsError kinds continue to use the existing internal-error wrapper.

The adapter test is intentionally named as existing-path coverage: it confirms that createBridgeFileSystemAdapter() -> WorkspaceFileSystem.readText() already rejects positive fractional limit values in the production injected filesystem path.

Evidence

The new inline fallback tests cover both malformed values:

  • limit: 1.5 now fails with ACP invalid params before slicing.
  • line: 1.5 now fails with ACP invalid params before calculating a fractional start line.

The new FsError wrapping test confirms that adapter-path parse_error failures now use JSON-RPC -32602 while preserving structured error data.

The serve adapter test covers the sibling production path and confirms its pre-existing behavior: WorkspaceFileSystem.readText() rejects limit: 1.5 as parse_error.

Local validation:

npm --workspace packages/acp-bridge run test -- bridgeClient.test.ts
✓ src/bridgeClient.test.ts (61 tests)

npm --workspace packages/cli run test -- src/serve/bridge-file-system-adapter.test.ts
✓ src/serve/bridge-file-system-adapter.test.ts (19 tests)

npm --workspace packages/acp-bridge run typecheck
✓ tsc --noEmit

npm --workspace packages/cli run typecheck
✓ tsc --noEmit

git diff --check
✓ no whitespace errors

Possible call chain / impact

Embedded fallback path changed by this PR:

custom / embedded ACP host
  -> createAcpSessionBridge({ ..., fileSystem: undefined })
  -> BridgeClient constructed without BridgeFileSystem
  -> custom ACP child sends raw readTextFile params
  -> BridgeClient.readTextFile inline fallback
  -> line / limit validation
  -> sliceLineRange(content, start, end)

Stock qwen serve read-window validation remains owned by WorkspaceFileSystem.readText():

ACP readTextFile request
  -> BridgeClient.readTextFile()
  -> createBridgeFileSystemAdapter().readText()
  -> WorkspaceFileSystem.readText()
  -> existing parse_error for malformed numeric windows
  -> preserveFsErrorOverAcp()
  -> JSON-RPC Invalid Params (-32602) with errorKind: parse_error

This PR does not change non-positive limit handling, WorkspaceFileSystem.readText() validation, or lower-level readTextRange() slicing helpers.

Reviewer Test Plan

How to verify

A reviewer can verify this with the focused tests:

npm --workspace packages/acp-bridge run test -- bridgeClient.test.ts
npm --workspace packages/cli run test -- src/serve/bridge-file-system-adapter.test.ts
npm --workspace packages/acp-bridge run typecheck
npm --workspace packages/cli run typecheck

The important behavior is that malformed positive fractional line / limit values no longer reach inline manual slicing when no BridgeFileSystem is injected. The serve adapter test documents the existing production-path WorkspaceFileSystem parse_error behavior, and the bridge client test confirms that ACP-wrapped parse_error now maps to Invalid Params.

Evidence (Before & After)

Before: BridgeClient.readTextFile() only checked limit <= 0, so positive fractional limit or line values could flow into sliceLineRange() through the inline fallback.

After: BridgeClient.readTextFile() rejects positive non-safe-integer line / limit values before reading/slicing. The adapter-path test name now makes clear that it covers the existing WorkspaceFileSystem.readText() validation path, not the new inline guard. The ACP error wrapper also maps parse_error to -32602, aligning malformed read-window params with RequestError.invalidParams().

Local validation on Windows:

npm --workspace packages/acp-bridge run test -- bridgeClient.test.ts
✓ src/bridgeClient.test.ts (61 tests)

npm --workspace packages/cli run test -- src/serve/bridge-file-system-adapter.test.ts
✓ src/serve/bridge-file-system-adapter.test.ts (19 tests)

npm --workspace packages/acp-bridge run typecheck
✓ tsc --noEmit

npm --workspace packages/cli run typecheck
✓ tsc --noEmit

git diff --check
✓ no whitespace errors

Tested on

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

Environment (optional)

Windows local validation used the repository npm workspace scripts.

Risk & Scope

  • Main risk or tradeoff: malformed positive fractional line / limit values now fail earlier in the ACP inline fallback instead of being interpreted by manual slicing.
  • Application scope: this is embedded ACP fallback hardening for custom/no-BridgeFileSystem consumers. It is not a demonstrated stock-user qwen serve bug.
  • Error-code scope: FsError('parse_error') now maps to JSON-RPC -32602; other FsError kinds keep the existing -32603 behavior.
  • Not changed: this PR does not change stock qwen serve validation, WorkspaceFileSystem.readText() behavior, non-positive limit behavior, or lower-level readTextRange() helpers.

Linked Issues

N/A

中文说明

What this PR does

这个 PR 会在 ACP readTextFile 的 inline fallback 路径里提前拒绝正的小数 line / limit,避免它进入手写的行切片逻辑。

这个改动刻意限定在 embedded / no-BridgeFileSystem fallback 路径。正常 stock qwen serve 路径已经会通过 createBridgeFileSystemAdapter(...)WorkspaceFileSystem.readText() 处理 ACP 文件读取,并将格式错误的正小数窗口拒绝为 parse_error。本 PR 保留 adapter 回归测试只是为了记录这个既有生产路径 validation;同时会把 ACP 包装后的 FsError('parse_error') 映射到 JSON-RPC Invalid Params (-32602),让 injected path 和 inline path 对 malformed read-window params 的分类保持一致。

Why it's needed

What Problem This Solves

readTextFile.linereadTextFile.limit 都是离散的行窗口控制。line 表示 1-based 起始行,limit 表示最多返回多少行。像 1.5 这样的正小数没有合理的行窗口语义。

在这个改动之前,inline BridgeClient.readTextFile() fallback 只处理了非正数 limit。当没有注入 BridgeFileSystem 时,格式错误的正小数请求仍可能进入手写切片:

await client.readTextFile({
  path: 'lines.txt',
  sessionId: 'sess:test',
  line: 1,
  limit: 1.5,
});

同类的 line: 1.5 也会在调用 sliceLineRange() 前算出小数 start

await client.readTextFile({
  path: 'lines.txt',
  sessionId: 'sess:test',
  line: 1.5,
  limit: 1,
});

真实应用范围很窄:自定义或 embedded ACP consumer 使用 @qwen-code/acp-bridge / createAcpSessionBridge(...),但没有传入 BridgeOptions.fileSystem,并且接受自定义 child 发来的 raw ACP readTextFile 参数。这不是已经证明存在的 stock-user qwen serve bug。

Changes

 if (typeof params.limit === 'number' && params.limit <= 0) {
   return { content: '' };
 }
+if (
+  typeof params.limit === 'number' &&
+  params.limit > 0 &&
+  !Number.isSafeInteger(params.limit)
+) {
+  throw RequestError.invalidParams(
+    undefined,
+    `\`limit\` must be a positive integer, got ${params.limit}`,
+  );
+}
+if (
+  typeof params.line === 'number' &&
+  params.line > 0 &&
+  !Number.isSafeInteger(params.line)
+) {
+  throw RequestError.invalidParams(
+    undefined,
+    `\`line\` must be a positive integer, got ${params.line}`,
+  );
+}

inline fallback 现在会在打开文件或进入手写行切片逻辑之前拒绝正数但非 safe integer 的 line / limit。现有非正数 limit 行为保持不变。

ACP error wrapper 现在会把 FsError('parse_error') 映射到 JSON-RPC Invalid Params (-32602),同时继续保留 data.errorKinddata.statusdata.hint。其他 FsError kind 继续沿用既有 internal-error wrapper。

adapter 测试也刻意改成既有路径覆盖:它确认 createBridgeFileSystemAdapter() -> WorkspaceFileSystem.readText() 在生产注入文件系统路径中本来就会拒绝正小数 limit

Evidence

新增 inline fallback 测试覆盖两类 malformed 值:

  • limit: 1.5 现在会在切片前以 ACP invalid params 失败。
  • line: 1.5 现在会在计算小数起始行前以 ACP invalid params 失败。

新增 FsError wrapping 测试确认 adapter-path parse_error failure 现在会使用 JSON-RPC -32602,并保留结构化 error data。

serve adapter 测试覆盖 sibling 生产路径,并确认其既有行为:WorkspaceFileSystem.readText() 会把 limit: 1.5 拒绝为 parse_error

本地验证:

npm --workspace packages/acp-bridge run test -- bridgeClient.test.ts
✓ src/bridgeClient.test.ts (61 tests)

npm --workspace packages/cli run test -- src/serve/bridge-file-system-adapter.test.ts
✓ src/serve/bridge-file-system-adapter.test.ts (19 tests)

npm --workspace packages/acp-bridge run typecheck
✓ tsc --noEmit

npm --workspace packages/cli run typecheck
✓ tsc --noEmit

git diff --check
✓ no whitespace errors

Possible call chain / impact

本 PR 修改的 embedded fallback 路径:

custom / embedded ACP host
  -> createAcpSessionBridge({ ..., fileSystem: undefined })
  -> BridgeClient constructed without BridgeFileSystem
  -> custom ACP child sends raw readTextFile params
  -> BridgeClient.readTextFile inline fallback
  -> line / limit validation
  -> sliceLineRange(content, start, end)

stock qwen serve 的 read-window validation 仍由 WorkspaceFileSystem.readText() 负责:

ACP readTextFile request
  -> BridgeClient.readTextFile()
  -> createBridgeFileSystemAdapter().readText()
  -> WorkspaceFileSystem.readText()
  -> existing parse_error for malformed numeric windows
  -> preserveFsErrorOverAcp()
  -> JSON-RPC Invalid Params (-32602) with errorKind: parse_error

本 PR 不改变非正数 limit 行为、不改变 WorkspaceFileSystem.readText() 校验,也不改动更底层的 readTextRange() 切片 helper。

Reviewer Test Plan

How to verify

Reviewer 可以通过聚焦测试验证:

npm --workspace packages/acp-bridge run test -- bridgeClient.test.ts
npm --workspace packages/cli run test -- src/serve/bridge-file-system-adapter.test.ts
npm --workspace packages/acp-bridge run typecheck
npm --workspace packages/cli run typecheck

核心行为是:当没有注入 BridgeFileSystem 时,格式错误的正小数 line / limit 不再进入 inline 手写切片逻辑。serve adapter 测试则用于记录既有生产路径 WorkspaceFileSystem parse_error 行为;bridge client 测试确认 ACP 包装后的 parse_error 现在会映射到 Invalid Params。

Evidence (Before & After)

Before:BridgeClient.readTextFile() 只检查 limit <= 0,所以正小数 limitline 可以通过 inline fallback 进入 sliceLineRange()

After:BridgeClient.readTextFile() 会在读文件/切片之前拒绝正的非 safe integer line / limit。adapter 路径测试名也已明确它覆盖的是既有 WorkspaceFileSystem.readText() validation 路径,而不是新的 inline guard。ACP error wrapper 也会把 parse_error 映射到 -32602,与 RequestError.invalidParams() 对 malformed read-window params 的分类保持一致。

Windows 本地验证:

npm --workspace packages/acp-bridge run test -- bridgeClient.test.ts
✓ src/bridgeClient.test.ts (61 tests)

npm --workspace packages/cli run test -- src/serve/bridge-file-system-adapter.test.ts
✓ src/serve/bridge-file-system-adapter.test.ts (19 tests)

npm --workspace packages/acp-bridge run typecheck
✓ tsc --noEmit

npm --workspace packages/cli run typecheck
✓ tsc --noEmit

git diff --check
✓ no whitespace errors

Tested on

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

Environment (optional)

Windows 本地验证使用仓库 npm workspace scripts。

Risk & Scope

  • Main risk or tradeoff: malformed 的正小数 line / limit 现在会在 ACP inline fallback 中更早失败,而不是被手写切片逻辑解释。
  • Application scope: 这是针对 custom/no-BridgeFileSystem consumer 的 embedded ACP fallback hardening,不是已经证明存在的 stock-user qwen serve bug。
  • Error-code scope: FsError('parse_error') 现在映射到 JSON-RPC -32602;其他 FsError kind 继续沿用既有 -32603 行为。
  • Not changed: 本 PR 不改变 stock qwen serve validation、不改变 WorkspaceFileSystem.readText() 行为、不改变非正数 limit 行为,也不改变底层 readTextRange() helper。

Linked Issues

N/A

Co-authored-by: chatgpt-codex-connector[bot] <199175422+chatgpt-codex-connector[bot]@users.noreply.github.com>

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

This PR addresses a real code-level inconsistency — sliceLineRange with a fractional endLine (e.g. want = 1.5) iterates the newline-walk loop one extra time, silently returning an additional line. The fix is minimal and correct.

However, no reproduction demonstrates this has actually happened in practice. The production serve adapter (BridgeFileSystemWorkspaceFileSystem.readText()) already rejects fractional limits, so the gap only exists in the inline fallback path (tests + Mode A + channels + IDE companion). The PR body provides a theoretical scenario ("a request like { limit: 1.5 }") but no evidence a real client has ever sent such a value.

Could you provide a before/after reproduction or describe a concrete scenario where a fractional limit reaches this path? Without one, this theoretical concern is better tracked as an issue first.

中文说明

这个 PR 解决的是一个真实存在的代码不一致——sliceLineRange 在小数 endLine(如 want = 1.5)下会多迭代一次循环,静默返回多余的一行。修复本身很小且正确。

然而,没有复现证明这实际发生过。生产 serve adapter(BridgeFileSystemWorkspaceFileSystem.readText())已经会拒绝小数 limit,因此这个缺口只存在于 inline fallback 路径(tests + Mode A + channels + IDE companion)。PR 正文只提供了一个理论场景("{ limit: 1.5 }"),没有证据表明真实客户端曾发送过这样的值。

能否提供一个 before/after 复现,或描述一个具体的场景来说明小数 limit 会到达这条路径?没有复现的话,这个理论性顾虑更适合作为 issue 先跟踪。

Qwen Code · qwen3.7-max

@VectorPeak

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I agree with the framing here: this should not be presented as a main qwen serve production-path bug.

After re-checking the reachable paths, the value of this PR is narrower than the original wording suggested. The main qwen serve path wires BridgeFileSystem through createBridgeFileSystemAdapter(...), and WorkspaceFileSystem.readText() already rejects fractional positive limit values as parse_error. So a normal stock qwen serve client should not reach this inline fallback for limit: 1.5.

The remaining value is better described as embedded ACP fallback hardening. The reachable scenario is:

custom / embedded ACP host
  -> createAcpSessionBridge({ ..., fileSystem: undefined })
  -> BridgeClient constructed without BridgeFileSystem
  -> custom ACP child sends raw fs/read_text_file / readTextFile with limit: 1.5
  -> BridgeClient.readTextFile inline fallback
  -> sliceLineRange() receives a fractional end line before this PR

That is a real code path, but it is a narrow one. It requires a direct or embedded @qwen-code/acp-bridge / Mode A-style consumer that omits BridgeOptions.fileSystem, not the usual daemon route.

For context, this split is consistent with the project history:

So I think the honest conclusion is: this PR has some implementation value as low-risk fallback parity / robustness hardening, but its application value is weak. Accepting it as a small defensive cleanup seems reasonable; rejecting it or asking to track it as an issue first is also reasonable.

If you prefer, I can either narrow the PR body to this embedded-fallback framing, or close this PR and open an issue that records the fallback inconsistency without implying a demonstrated stock-user bug.

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

Reviewed — no blockers. Suggestions are inline.

if (typeof params.limit === 'number' && params.limit <= 0) {
return { content: '' };
}
if (

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 new guard validates limit but params.line has no equivalent check. A fractional line (e.g. 1.5) flows into const start = startLine > 0 ? startLine - 1 : 0, producing start = 0.5, which feeds sliceLineRange with a fractional index. Meanwhile workspace-file-system.ts validates both line and limit with isSafeInteger. Consider adding a parallel guard for params.line here for symmetry.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, addressed in the latest push. I added the symmetric positive-integer guard for line in the inline fallback, so fractional line values now fail before reaching sliceLineRange(), matching the limit validation shape.

Comment thread packages/acp-bridge/src/bridgeClient.ts Outdated
) {
throw RequestError.invalidParams(
undefined,
'`limit` must be a positive 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] The error message omits the received value. The sister validation in workspace-file-system.ts includes it: `limit must be a positive integer, got ${opts.limit}`. Including the actual value helps debugging at a glance.

Suggested change
'`limit` must be a positive integer.',
'`limit` must be a positive integer, got ${params.limit}',

The existing test assertion (toContain(' + "" + limit + "" + must be a positive integer')) still passes.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, done. The inline fallback now includes the received value in both invalid-params messages: line and limit report the malformed value that was rejected.

expect(response.content).toBe('x\ny\n');
});

it('propagates parse_error for fractional positive limits', async () => {

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] This test goes through adapter.readText()WorkspaceFileSystem.readText(), which has its own pre-existing isSafeInteger guard. The new BridgeClient.readTextFile() guard (the inline fallback path) is never reached here. The test name is technically accurate, but a reader skimming the PR may assume it validates the new code. Consider renaming to clarify, e.g. 'readText surfaces workspace-file-system parse_error for fractional positive limits'.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, renamed. The adapter test now explicitly says it surfaces the existing WorkspaceFileSystem.readText() parse_error behavior, so it should no longer look like coverage for the new inline fallback guard.

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

Reviewed — no blockers. Suggestions are inline.

typeof params.limit === 'number' &&
params.limit > 0 &&
!Number.isSafeInteger(params.limit)
) {

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 new inline guard correctly throws RequestError.invalidParams() (JSON-RPC -32602, Invalid Params). However, the adapter path (production qwen serve) wraps the same WorkspaceFileSystem parse_error as RequestError(-32603) (Internal Error) via preserveFsErrorOverAcp(). The same client error produces different JSON-RPC codes depending on the path: -32602 inline vs -32603 in production.

Consider having preserveFsErrorOverAcp map parse_error kind to -32602 (Invalid Params), or adding a fractional-limit guard in the adapter before wfs.readText so the error is thrown before the -32603 wrapping.

— qwen3.7-max via Qwen Code /review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, addressed. I updated preserveFsErrorOverAcp() so FsError('parse_error') maps to JSON-RPC Invalid Params (-32602) while preserving the existing data.errorKind, data.status, and data.hint fields. Other filesystem error kinds still use the existing internal-error mapping.

Reject positive fractional line and limit values in the
BridgeClient readTextFile inline fallback before they reach manual
line slicing. Include the received value in the invalid params
message, and keep the serve adapter regression test labeled as
WorkspaceFileSystem path coverage.

Also map FsError(parse_error) through the ACP wrapper as JSON-RPC
Invalid Params so injected and inline read-window validation classify
malformed params consistently while preserving errorKind/status/hint.

Co-authored-by: qwen-code-ci-bot <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
@VectorPeak

Copy link
Copy Markdown
Contributor Author

Thanks for the review. I pushed 46c170b with the requested follow-ups:

  • Added the symmetric positive-fractional line guard in the inline fallback.
  • Included the received line / limit value in invalid-params messages.
  • Renamed the adapter test so it clearly documents the existing WorkspaceFileSystem.readText() path rather than the new inline guard.
  • Updated preserveFsErrorOverAcp() so FsError('parse_error') maps to JSON-RPC Invalid Params (-32602) while preserving data.errorKind, data.status, and data.hint.

Local validation:

pm --workspace packages/acp-bridge run test -- bridgeClient.test.ts

pm --workspace packages/cli run test -- src/serve/bridge-file-system-adapter.test.ts

pm --workspace packages/acp-bridge run typecheck

pm --workspace packages/cli run typecheck

  • git diff --check

Remote CI has started on the latest commit; ubuntu Node 22 is still pending at the moment.

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

No issues found. LGTM! ✅

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

🧪 Maintainer local verification — ✅ LGTM (macOS)

Built and tested this PR locally as a merge reference. All author-stated checks reproduce green, plus additional lint/whitespace/independent-behavior checks pass. This fills the macOS coverage that the PR description marked "⚠️ not tested locally" (it was previously validated only on Windows).

Environment: macOS (Darwin 24.6.0) · Node v22.23.1 · npm 10.9.8 · branch pr-6704 (6e2aed46c)

local verification checks

1) Author-stated checks — reproduced on macOS

Check Result
npm -w packages/acp-bridge run test -- bridgeClient.test.ts 61 passed
npm -w packages/cli run test -- src/serve/bridge-file-system-adapter.test.ts 19 passed
npm -w packages/acp-bridge run typecheck (tsc --noEmit) ✅ clean
npm -w packages/cli run typecheck (tsc --noEmit) ✅ clean
git diff --check (whitespace, 3 changed files) ✅ no errors
eslint (bridgeClient.ts + 2 test files) ✅ clean

Note for repro: the workspace tests import @qwen-code/qwen-code-core, so npm run build --workspace=packages/core is required first — otherwise vitest fails with "Failed to resolve entry for package @qwen-code/qwen-code-core".

2) Independent before/after behavior check

Beyond re-running the author's tests, I wrote a fresh 13-assertion spec (not copied from the PR) and ran the same spec against both the pre-PR main source and the PR source — only the code under test differs. This exercises the real BridgeClient.readTextFile() inline fallback (no BridgeFileSystem injected).

before / after behavior

  • Before (main): malformed positive fractional windows were silently accepted and produced surprising truncated slices — limit:2.7 → 3 lines, limit:0.5 → 1 line, limit:1.5 → 2 lines, line:1.5 → shifted start. An injected FsError('parse_error') mapped to -32603 (internal error).
  • After (PR): all of these fail fast with RequestError code -32602 (Invalid Params) and a clear `limit`/`line` must be a positive integer message; parse_error now maps to -32602.
  • Preserved / no regression: limit ≤ 0 → { content: '' }, valid integer windows still slice correctly, non-positive line is still tolerated, and non-parse_error FsError kinds keep -32603. The change is narrowly scoped.

3) Scope confirmation

I confirmed the PR's own framing: this is embedded/no-BridgeFileSystem fallback hardening, not a stock qwen serve bug. The production path (createBridgeFileSystemAdapter → WorkspaceFileSystem.readText) already rejects fractional windows as parse_error — the CLI adapter test (19 passed) documents that. The -32602 mapping now makes the injected and inline paths classify malformed read-window params consistently.

Verdict

Verified on macOS — recommend merge. 93 focused tests + typecheck + lint pass, an independent before/after check demonstrates the intended behavior change, and no regressions to existing behavior were observed.

🇨🇳 中文版本(点击展开)

🧪 Maintainer 本地验证 — ✅ 建议合并(macOS)

作为合并参考,我在本地构建并测试了本 PR。作者列出的全部检查都复现通过,另外补充的 lint / 空白字符 / 独立行为验证也全部通过。 本次验证补齐了 PR 描述中标记为 "⚠️ not tested locally"macOS 覆盖(此前仅在 Windows 上验证过)。

环境: macOS (Darwin 24.6.0) · Node v22.23.1 · npm 10.9.8 · 分支 pr-67046e2aed46c

1)作者列出的检查 — 在 macOS 复现

检查项 结果
npm -w packages/acp-bridge run test -- bridgeClient.test.ts 61 通过
npm -w packages/cli run test -- src/serve/bridge-file-system-adapter.test.ts 19 通过
npm -w packages/acp-bridge run typechecktsc --noEmit ✅ 无报错
npm -w packages/cli run typechecktsc --noEmit ✅ 无报错
git diff --check(3 个改动文件的空白检查) ✅ 无错误
eslint(bridgeClient.ts + 2 个测试文件) ✅ 无报错

复现提示:workspace 测试会 import @qwen-code/qwen-code-core,因此需要先执行 npm run build --workspace=packages/core,否则 vitest 会报 "Failed to resolve entry for package @qwen-code/qwen-code-core"

2)独立的 before/after 行为验证

除了复跑作者的测试,我另外写了一份全新的 13 条断言 spec(不是从 PR 拷贝的),并把同一份 spec 分别跑在 pre-PR 的 main 源码和本 PR 源码上——只有被测代码不同。它真正走了 BridgeClient.readTextFile() 的 inline fallback(没有注入 BridgeFileSystem)。

  • Before(main): 格式错误的正小数窗口被静默接受,产生了令人意外的截断切片——limit:2.7 → 3 行limit:0.5 → 1 行limit:1.5 → 2 行line:1.5 → 起始行偏移;注入的 FsError('parse_error') 映射到 -32603(internal error)。
  • After(PR): 上述情况都会以 RequestError、错误码 -32602(Invalid Params) 快速失败,并带有清晰的 `limit`/`line` must be a positive integer 提示;parse_error 现在也映射到 -32602
  • 保持不变 / 无回归: limit ≤ 0 → { content: '' }、合法整数窗口仍能正确切片、非正数 line 仍被容忍、非 parse_errorFsError 仍为 -32603。改动范围收敛

3)范围确认

我确认了 PR 自身的定位:这是针对 embedded / 无 BridgeFileSystem fallback 的加固,而非 stock qwen serve 的 bug。生产路径(createBridgeFileSystemAdapter → WorkspaceFileSystem.readText)本来就会把小数窗口拒绝为 parse_error——CLI adapter 测试(19 通过)记录了这一点。-32602 的映射让 injected path 和 inline path 对 malformed read-window 参数的分类保持一致

结论

已在 macOS 验证 — 建议合并。 93 个聚焦测试 + typecheck + lint 全部通过,独立的 before/after 验证展示了预期的行为变化,且未观察到对既有行为的回归。

Verification performed locally on macOS by the maintainer. Screenshots hosted on the pr-assets/pr-6704-verify branch.

@wenshao

wenshao commented Jul 11, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@wenshao
wenshao added this pull request to the merge queue Jul 11, 2026
Merged via the queue into QwenLM:main with commit 79a80bc Jul 11, 2026
39 checks passed

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

Reviewed — no blockers. Suggestions are inline.

if (typeof params.limit === 'number' && params.limit <= 0) {
return { content: '' };
}
if (

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 guard condition params.limit > 0 && !Number.isSafeInteger(params.limit) has a NaN-shaped hole. NaN > 0 evaluates to false, so NaN falls through both the limit <= 0 early-return (since NaN <= 0 is also false) and this new guard, reaching sliceLineRange with NaN parameters. The sister validation in workspace-file-system.ts:456 uses !Number.isSafeInteger(opts.limit) || opts.limit < 1, which catches NaN because !Number.isSafeInteger(NaN) is true. Adopting the same pattern here would close the gap and align the two paths:

Suggested change
if (
if (typeof params.limit === 'number' && !Number.isSafeInteger(params.limit)) {
if (params.limit < 1) {
return { content: '' };
}
throw RequestError.invalidParams(
undefined,
`\`limit\` must be a positive integer, got ${params.limit}`,
);
}

— qwen3.7-max via Qwen Code /review

if (
typeof params.line === 'number' &&
params.line > 0 &&
!Number.isSafeInteger(params.line)

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 limit <= 0 early-return at line 1880 runs before this line validation, so a request like { line: 1.5, limit: 0 } silently returns { content: '' } without flagging the fractional line. Moving the line guard before the limit <= 0 check — or at least running both validations before any early-return — would ensure malformed parameters are always reported regardless of ordering.

— qwen3.7-max via Qwen Code /review

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.

4 participants