Skip to content

feat(web-shell): share HTML artifacts through managed hosting - #10024

Closed
qqqys wants to merge 20 commits into
QwenLM:mainfrom
qqqys:feat/artifact-share
Closed

qqqys wants to merge 20 commits into
QwenLM:mainfrom
qqqys:feat/artifact-share

Conversation

@qqqys

@qqqys qqqys commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds managed public sharing for available HTML artifacts in Web Shell. The Share action opens a guided provider flow with Cloudflare first, Vercel second, and Netlify third; each provider exposes the same Prepare → Authorize → Connect → Ready progress, a compact default view, and an expandable details section for users who want to see which official CLI, dedicated project, and workspace metadata are involved.

Qwen Code installs a missing provider CLI into its own managed tool location, opens the provider's browser authorization flow, connects a dedicated public project, and persists only the identifiers needed to reuse that project. Provider credentials and tokens remain under the official CLI's management and are not written to workspace settings. Setup and publishing are cancellable from the dialog, including while an authorization command, upload, public-link probe, or retry delay is in progress.

The feature is enabled by default and can be turned off under Settings → Features. When disabled, the Share action is hidden and daemon routes reject setup or publishing. Publication metadata includes the provider target, artifact identity, content hash, link, and time; unchanged content reuses the existing link instead of deploying again, while the dialog offers an explicit republish action. A new or changed artifact is deployed and the returned link is checked with bounded retries before the UI presents it as publicly accessible.

All daemon routes stay within the resolved, trusted workspace runtime. Web Shell actions use the selected workspace's qualified client rather than falling back to the process-primary workspace, and filesystem failures retain their structured status and remediation hint. Cloudflare and Vercel project creation tolerate provider-side eventual consistency and reuse a pending project name on retry, avoiding duplicate projects after a late CLI error.

Why it's needed

An HTML artifact is currently useful only to someone who can reach the daemon that produced it, so sharing it means downloading and manually uploading the file elsewhere. This change turns that manual workflow into one clear action while keeping hosting ownership with the user's provider account, making the public nature of the result explicit, and avoiding exposure of CLI credentials to the browser or workspace configuration.

Reviewer Test Plan

How to verify

Open a session containing an available HTML artifact and confirm its card offers Share beside Download and Open. Confirm non-HTML and unavailable artifacts do not expose Share. In Settings → Features, turn Artifact sharing off and confirm the action disappears; restore it and confirm the action returns.

Open Share and confirm Cloudflare is selected by default, followed by Vercel and Netlify. For an unconfigured provider, start setup and confirm the official browser authorization flow opens, the progress moves through authorization and project connection, and Stop immediately returns control without leaving the dialog or a workspace-wide setup/publish lock stuck. Expand Details and confirm it describes the CLI, dedicated target, saved identifiers, automatic installation boundary, and credential-storage boundary.

After a provider is ready, publish an HTML artifact and confirm the returned HTTPS link opens without daemon access. Reopen Share for unchanged content and confirm it shows the existing link with Open current and Republish instead of silently deploying again. Change the file and confirm the state becomes updated and offers Publish new version. If the provider accepts the deploy but the URL does not become public, confirm the dialog reports a provider-specific failure rather than presenting the link as successful.

Local verification on the exact submitted head:

npm run build                                      passed
npm run typecheck                                  passed
npm run lint                                       passed
CLI artifact routes + workspace settings           82 passed
Core HostPublisher                                 29 passed
TypeScript SDK DaemonClient                       351 passed
WebUI workspace actions                            19 passed
Web Shell App + artifact/settings DOM             589 passed
Netlify disconnect/cancel probe                     passed

The Netlify probe destroyed a request while the fake CLI login was deliberately left pending; the child signal aborted and a second setup request completed within one second, confirming cancellation releases the independent Netlify setup lock. The selected-workspace action path was also independently checked against the exact submitted head.

Evidence (Before & After)

Before — HTML artifacts offered Download and Open only:

before artifact card

After — the artifact card exposes Share:

after artifact card

Current managed-provider dialog captured from the submitted head on macOS:

managed artifact sharing dialog

Tested on

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

Environment (optional)

macOS local Web Shell served from the submitted source with real persisted provider status for the screenshot; Vitest and repository build/type/lint commands used the workspace toolchain. Provider CLI behavior and cancellation paths are deterministic in tests; no new live deployment was created solely for this review pass.

Risk & Scope

  • Main risk or tradeoff: the flow installs official hosting CLIs in Qwen Code's managed tool location and creates a dedicated public provider project; anything published through it is intentionally accessible to anyone holding the link.
  • Not validated / out of scope: Windows and Linux were not exercised locally, custom domains and private/password-protected links are not part of this change, and provider dashboards remain the place to delete projects or manage billing and access policies.
  • Breaking changes / migration notes: none. Sharing is additive and defaults on; users who do not want it can disable Artifact sharing in workspace or user settings.

Linked Issues

None.

中文说明

这个 PR 做了什么

为 Web Shell 中状态可用的 HTML Artifact 增加托管式公开分享。点击「分享」会打开统一的平台引导流程,顺序是 Cloudflare、Vercel、Netlify;每个平台都使用「准备 → 授权 → 连接 → 可用」进度,默认界面保持简洁,同时提供可展开的「详细信息」,让需要了解底层过程的用户看到所用官方 CLI、专用项目和工作区保存信息。

Qwen Code 会把缺失的平台 CLI 安装到自身管理的工具目录,打开平台官方的浏览器授权流程,连接一个专用公开项目,并且只持久化复用该项目所需的标识。平台凭据和 token 仍由官方 CLI 管理,不会写入工作区设置。配置与发布都可以在对话框中中止,包括正在执行授权命令、上传、公开链接探测或重试等待时。

该功能默认开启,可在「设置 → 功能」中关闭。关闭后,「分享」操作会隐藏,daemon 路由也会拒绝配置或发布。发布记录包含平台目标、Artifact 标识、内容哈希、链接和时间;内容未变时会复用已有链接,不会静默重复部署,同时对话框提供显式「重新发布」。Artifact 新增或内容变化后才会重新部署,并在把链接展示为公开可用前进行有界重试检查。

所有 daemon 路由都限制在解析出的受信工作区运行时中。Web Shell 操作通过当前选中工作区的 qualified client 执行,不会回退到进程主工作区;文件系统错误会保留结构化状态和修复提示。Cloudflare 与 Vercel 的项目创建流程能够容忍平台侧最终一致性,并在重试时复用待确认的项目名,避免 CLI 延迟报错后重复创建项目。

为什么需要它

目前 HTML Artifact 只有能访问生成它的 daemon 的人才能查看,因此分享给别人时必须先下载再手动上传。这个改动把手工流程收敛为一个清晰动作,同时让托管所有权继续留在用户自己的平台账号中,明确提示结果会公开访问,并避免把 CLI 凭据暴露给浏览器或工作区配置。

审阅者测试计划

如何验证

打开一个包含可用 HTML Artifact 的会话,确认卡片在「下载」「打开」旁提供「分享」。确认非 HTML 或状态不可用的 Artifact 不显示「分享」。进入「设置 → 功能」,关闭「Artifact 分享」后确认操作消失;重新开启后确认操作恢复。

打开「分享」,确认默认选择 Cloudflare,后面依次是 Vercel 和 Netlify。对未配置平台开始配置,确认会打开官方浏览器授权流程,进度经过授权和项目连接;点击「中止」应立即恢复控制,不会卡住对话框,也不会残留工作区级配置/发布锁。展开「详细信息」,确认其中说明 CLI、专用发布目标、保存的标识、自动安装边界和凭据存储边界。

平台就绪后发布 HTML Artifact,确认返回的 HTTPS 链接在无法访问 daemon 的环境下也能打开。对未变化的内容重新打开「分享」,确认界面显示已有链接,并提供「打开当前版本」和「重新发布」,而不是直接重复部署。修改文件后,确认状态变为「已更新」并提供「发布新版本」。如果平台接受部署但 URL 始终未公开,确认对话框报告平台专属错误,不把该链接展示为成功结果。

在精确提交 head 上完成的本地验证:

npm run build                                      通过
npm run typecheck                                  通过
npm run lint                                       通过
CLI Artifact 路由 + 工作区设置                      82 项通过
Core HostPublisher                                 29 项通过
TypeScript SDK DaemonClient                       351 项通过
WebUI 工作区 actions                               19 项通过
Web Shell App + Artifact/设置 DOM                  589 项通过
Netlify 断连/取消探针                                通过

Netlify 探针让假 CLI 登录保持挂起并销毁客户端请求;子进程信号随即中止,第二个配置请求在一秒内完成,证明取消会释放独立的 Netlify 配置锁。当前选中工作区的 action 路径也在精确提交 head 上做了独立复核。

证据(改动前后)

改动前——HTML Artifact 只有「下载」和「打开」:

改动前 Artifact 卡片

改动后——Artifact 卡片提供「分享」:

改动后 Artifact 卡片

在 macOS 上从当前提交 head 捕获的托管平台对话框:

托管式 Artifact 分享对话框

测试环境

操作系统 状态
🍏 macOS ✅
🪟 Windows ⚠️
🐧 Linux ⚠️

运行环境(可选)

macOS 本地 Web Shell 由当前提交源码启动,截图使用真实保存的平台状态;Vitest 与仓库 build/type/lint 命令使用工作区工具链。本轮审阅没有为了截图额外创建新的线上部署,平台 CLI 行为和取消路径由确定性测试覆盖。

风险与范围

  • 主要风险或取舍:流程会把官方托管 CLI 安装到 Qwen Code 管理的工具目录,并创建专用公开平台项目;通过它发布的内容会有意允许任何拿到链接的人访问。
  • 未验证 / 范围之外:本地没有验证 Windows 和 Linux;自定义域名、私有链接和密码保护不属于本次改动;删除项目以及管理计费、访问策略仍在对应平台控制台中完成。
  • 破坏性变更 / 迁移说明:无。分享是新增能力且默认开启;不需要该能力的用户可以在工作区或用户设置中关闭「Artifact 分享」。

关联 Issue

无。

qqqys and others added 6 commits August 19, 2026 19:32
Adds a Share action to HTML artifact cards, next to Download and Open. It
uploads the artifact and returns a link that can be sent to someone who cannot
reach the workspace or the daemon.

Sharing reuses the artifact publisher that already exists in core rather than
introducing a second publishing path: the browser asks the daemon to publish a
workspace file, and the daemon constructs OssPublisher and answers with the
URL. Nothing is uploaded from the browser. Only the OSS backend is wired up
here — the local backend produces an unshareable file:// URL, and the host
backend runs a user-supplied command whose semantics need more design before a
dialog can drive it.

The dialog asks for an endpoint, a bucket, and an AccessKey pair, seeded from
any existing artifact.oss settings so a configured user does not retype them.
Nothing it collects is written to disk and no settings key is added: the values
live in the running daemon and are forgotten when it exits, which the dialog
states plainly. Credentials in the daemon environment remain the way to make one
survive a restart, and are reported in the dialog when present. Nothing uploads
until the user presses Upload.

A workspace read returns at most 256 KB and rejects a larger request outright,
so the route walks the file window by window instead of asking for the whole
document at once, which failed on any page above that size.

A public-read object ACL is overridden by a bucket that blocks public access,
and the upload still reports success, so the daemon probes the returned URL and
reports whether the link is actually reachable instead of handing over one that
403s.

Co-Authored-By: Claude Opus 5 <[email protected]>
The OSS default domain answers HTML with a forced-download header, so a link
built from it downloads the page instead of opening it. The dialog gains a
public domain field, seeded from any configured value, whose hint says exactly
that: leave it empty and browsers download, bind a custom domain to the bucket
and the link opens. The upload still goes through the endpoint — only the
returned link uses this domain.

The credential and storage notes shrink to the smaller type size; they explain
the defaults rather than needing to be read on the way to the button.

Co-Authored-By: Claude Opus 5 <[email protected]>
# Conflicts:
#	packages/sdk-typescript/scripts/build.js
#	packages/web-shell/client/App.test.tsx
#	packages/web-shell/client/components/artifacts/ArtifactPanel.tsx
#	packages/web-shell/client/components/artifacts/TurnOutputs.tsx
#	packages/web-shell/client/e2e/utils/mockDaemon.ts
@qqqys qqqys added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Aug 25, 2026
@qqqys

qqqys commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔄 Takeover re-armed: the round counter starts a fresh window (previous rounds no longer count toward the cap); management continues.

中文说明

🔄 已重新武装:轮次计数开启新窗口(此前轮次不再计入上限),托管继续。

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

PR #10024 — Review Report

⚠️ Infrastructure note: The full review pipeline could not run. qwen review fetch-pr failed with getaddrinfo() thread failed to start — a Windows-specific git network issue preventing git fetch from GitHub. gh CLI works fine (the PR data and diff were obtained that way), but without a worktree, no build, typecheck, or test verification was possible. Findings below are based on manual diff review only, not verified against a live build.

Target: PR #10024 — feat(web-shell): share HTML artifacts through managed hosting
Author: qqqys
Head: b16bdcd9777c7d6ca823211178e247acc19caa11 (same head as superseded #9474)
Size: +9448/−49 across 32 files (5,067 production lines, 4,328 test lines)
State: OPEN, no existing reviews


Summary

This PR adds a managed provider flow for sharing HTML artifacts through Cloudflare, Vercel, and Netlify. The implementation is structurally sound — it extends the existing HostPublisher instead of building a parallel publisher, uses trusted-runtime gates, scrubs dangerous env vars before spawning provider CLIs, enforces HTTPS-only URLs, and provides 73 new tests with good coverage of cancellation, lock release, and security invariants.

No Critical blockers found. Three non-blocking observations below.


Findings

1. (Suggestion) — Dead explicit route registration shadows the parameterized one

The explicit /workspace/artifact/netlify/setup route is registered in both registerWorkspaceArtifactPublishRoutes and registerWorkspaceQualifiedArtifactPublishRoutes, followed by the parameterized /:provider/setup route that already dispatches netlify to the same handler. Express matches the first-registered route, so the parameterized route's netlify branch is unreachable. Either the explicit routes are redundant (if the parameterized one is the canonical path) or the parameterized route should exclude netlify from its provider check. Already noted in the triage — worth cleaning up before merge.

2. (Suggestion) — npm install --global installs to the user's global prefix, not "Qwen Code's managed tool location"

The PR description says provider CLIs are installed "into Qwen Code's managed tool location." The code uses npm install --global --cache <qwen-cache-dir> <package>. The --global flag installs to the user's global npm prefix (e.g. /usr/local/lib/node_modules on Linux), not to Qwen's managed location. The --cache flag only redirects the npm cache. This matters for two reasons:

  • On machines where the global prefix is root-owned (common Linux setups), the install will fail with EACCES — the PR description's claim is misleading.
  • For supply-chain auditing, the distinction between "installed under Qwen's directory" and "installed to the user's global tree" is significant.

The PR could either install to a managed directory (using --prefix instead of --global) or honestly describe the current behavior.

3. (Suggestion) — Machine-specific absolute paths in workspace settings are not portable

The Netlify connect flow persists process.execPath (e.g. /usr/local/bin/node) and the CLI entry path (e.g. /usr/local/lib/node_modules/netlify-cli/bin/run.js) into artifact.host.uploadCommand at workspace scope. If a user commits their workspace settings file, these paths will not resolve on any other machine. This is a documentation/awareness issue at minimum — the settings system should either warn about non-portable values or the route should reconstruct the path at runtime rather than storing it.


Existing observations from CI triage (still standing)

The CI triage bot (stages 1-3) already conducted a thorough review and raised three issues I'll confirm are still relevant:

  1. The three explicit route registrations are dead weight (confirmed — see finding 1 above).
  2. The npm install --global vs "managed tool location" discrepancy (confirmed — see finding 2 above).
  3. Machine-specific absolute paths in workspace settings (confirmed — see finding 3 above).

The triage also deferred to the maintainer on the product-direction question (default-on public hosting with auto-installed third-party CLIs) and the size sign-off (5,067 production lines across core + six packages). These remain open questions that the code review cannot resolve.


What was not reviewed

  • Build, typecheck, lint: The worktree could not be created (git fetch failure), so npm run build && npm run typecheck was not run.
  • Test execution: None of the 73 new tests were run against the working tree.
  • Live provider behavior: All provider CLI interactions are mocked in tests, consistent with the triage findings.

Review complete: pr-10024 — COMMENT, not posted (0 Critical, 3 Suggestion)

@qqqys

qqqys commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

@qwen-code-dev-bot

qwen-code-dev-bot commented Aug 25, 2026 •

Copy link
Copy Markdown
Collaborator

⚠️ AutoFix round 6 ended without publishing a report — view run.

中文说明

⚠️ AutoFix 第 6 轮结束但未发布报告 —— 查看运行。

@github-actions

Copy link
Copy Markdown
Contributor

Qwen Code review request accepted. Review is queued in workflow run.

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

Review of PR #10024 — feat(web-shell): share HTML artifacts through managed hosting

Reviewed at: bba81a37948a37b4998cfad7d2cbcf33594cbb62
Infrastructure note: git fetch is blocked on this Windows machine (getaddrinfo() thread failed to start), so no worktree, build, typecheck, or test verification was possible. Findings are based on the gh pr diff output only.


Context

This PR has already been reviewed by:

  • doudouOUC — COMMENT (no Criticals, 3 Suggestions)
  • CI triage stages 1-3 — deferred to maintainer on product direction (default-on public hosting with auto-installed third-party CLIs) and size (~5,067 production lines across core + six packages)

The head has moved from b16bdcd9777 (the commit reviewed by doudouOUC) to bba81a3794 (merge with upstream/main). The merge brought no changes to the artifact-share files — the diff delta is entirely upstream content (live-host package, goal checkpoint changes, channel TLS).

AutoFix round 1/100 is still running — no report posted yet.


Existing findings (confirmed standing)

All three Suggestions from the doudouOUC review remain valid at the current head:

  1. Dead explicit route registrations — The explicit /workspace/artifact/netlify/setup routes in both route sets are registered before the parameterized /:provider/setup route. Express matches the first-registered route, so the parameterized route's netlify branch is unreachable. The behavior is identical (both dispatch to handleSetup), so this is dead code, not a bug.

  2. npm install --global installs to the user's global prefix, not "Qwen Code's managed tool location" — The code uses npm install --global --cache <qwen-cache-dir> <package>. The --global flag writes to the user's global npm prefix, not to Qwen's managed directory. The --cache flag only redirects the npm cache. On machines with root-owned global prefixes (common Linux), this will fail with EACCES.

  3. Machine-specific absolute paths in workspace settings — The Netlify connect flow persists process.execPath and CLI entry paths into artifact.host.uploadCommand at workspace scope. These paths are not portable if the workspace settings file is committed.


New observation: CI test failures

The test suite is failing on all three platforms at the current head:

Platform Status
Test (ubuntu-latest, Node 22.x) fail
Test (windows-latest, Node 22.x) fail
Test (macos-latest, Node 22.x) fail

The existing reviews did not capture this because the failures may have appeared after the merge with upstream/main. These could be pre-existing (the merge brought in a large upstream diff including a new packages/live-host package) or related to the PR changes. The failures should be investigated before merge.


Additional observations

  • Cancellation architecture is well-designed. The requestAbortScope + withCommandSignal pattern propagates request cancellation through AbortSignal.any to child processes, and the withSetupLock/withPublishLock serialization uses promise chaining that self-heals on rejection. The resetArtifactNetlifySetupStateForTesting export correctly clears the module-level state.
  • Env var scrubbing is thorough. providerEnv deletes NODE_OPTIONS, NODE_PATH, LD_PRELOAD, DYLD_INSERT_LIBRARIES, DYLD_LIBRARY_PATH, and provider-specific vars (CLOUDFLARE_ACCOUNT_ID, VERCEL_ORG_ID, VERCEL_PROJECT_ID).
  • Publication record validation is defense-in-depth. URLs are validated for HTTPS-only, no embedded credentials, and length limits in both publicationRecords() (settings read path) and validatePublishedUrl/parsePublishedUrl (publisher output path).
  • The readWholeFile function correctly handles the >16 MB rejection path by checking sizeBytes on the first window read, bailing early without reading the full file.
  • Test coverage is comprehensive. The windowReader helper and adaptTestRunner mock decorator are well-designed abstractions that make the 44 route tests (2,780 lines) readable and deterministic.

Verdict

No new Criticals or Suggestions beyond what the existing reviews already identified. The three Suggestions from doudouOUC remain standing. The CI test failures on all platforms should be investigated before merge. The product-direction and size questions remain deferred to the maintainer.

Review complete: pr-10024 — no new findings

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 1/100 轮)。改动内容与我反驳保留之处如下:

Address-review summary — PR #10024 (same-run verification repair round)

Round commit: 1f6c187741f0eebe4b15f1839fee67daed48bad5 on feat/artifact-share, added on top of the preserved rejected commit 73381f8ad5 (no history rewrite). Net diff: 1 file, +23 / −0 (test-only).

Repaired rejection: tests failed in packages/cli

The previous commit was rejected because the gate's vitest run --changed origin/main in packages/cli reported 13 unhandled rejections: TypeError: this.subagentManager.getAvailableModelGrades is not a function, thrown from AgentTool.updateDescriptionAndSchema (agent.ts:1034) via the catch path of refreshSubagents (agent.ts:887), attributed to src/ui/AppContainer.test.tsx.

Root cause (reproduced, not guessed): AppContainer.test.tsx's beforeEach installs a Partial<SubagentManager> mock that provides listSubagents / addChangeListener / loadSubagent / createSubagent but not getAvailableModelGrades — the method upstream commit d44030a4c0 ("feat(core): add model grade selection for subagent spawn") made AgentTool.updateDescriptionAndSchema() call. That commit updated core's own mocks (agent.test.ts, agent-headless.test.ts) but missed this cli test. AppContainer render tests invoke the real Config.initialize() from the mount effect, whose warmAll({ strict }) instantiates every lazy tool factory — including AgentTool constructed against the incomplete mock. The constructor fires refreshSubagents() without awaiting it; updateDescriptionAndSchema() throws inside the try, the catch calls it again (agent.ts:887) and throws again, so the promise rejects unhandled. On the Linux lane dangerouslyIgnoreUnhandledErrors is false, so the unhandled rejections fail the run. I reproduced the exact error (identical stack) with a temporary probe driving the real Config.initialize() with the same mock shape. Timing decides where a rejection lands — it only counts when it settles while a vitest worker has the file in flight, which is why a standalone run of the file can look clean while the gate run counted 13 — but the mock defect is deterministic, so the fix is unconditional.

Fix: added getAvailableModelGrades: vi.fn().mockReturnValue(new Map()) to the mock — the same pattern agent.test.ts uses. With it, the refreshSubagents() chain completes cleanly (an empty grades map simply deletes the model schema property).

Witness: new regression test boots AgentTool from the SubagentManager mock without unhandled rejections in the State Initialization block: it runs the real mockConfig.initialize(), asserts AgentTool was genuinely instantiated (ensureTool(ToolNames.AGENT) is defined — proof the exercised path ran), and asserts no unhandled rejection fired meanwhile.

Mutation probe: temporarily removing the new mock method makes the witness FAIL with the gate's exact message (this.subagentManager.getAvailableModelGrades is not a function); restoring it returns the witness and the full 158-test file to green.

Feedback items not acted on this round

  • All three review Suggestions ([rv:5020361708], [rv:5022059786]) were handled in the previous round: two implemented (dead explicit route registrations removed; provider CLIs now install into a Qwen-managed --prefix location), one deferred to follow-up and re-recorded in deferred-findings.json (machine-specific absolute paths persisted by the Netlify connect flow).
  • Informational issue comments: [ic:5411719702] web-shell visual preview regenerates on the next push; [ic:5411721346] serve A/B already passed; [ic:5414340912] review-fallback notice — no action.
  • The remaining failed checks in the feedback (macOS/Windows Test, Live Host, Java 21) were CANCELLED, not independently red; they re-run on push.

Verification

All commands actually run this round, on the committed tree. The test legs ran in a CI-mirrored clean environment (env -i with CI=true, fresh HOME, no harness variables), matching the gate's own env -i clean-child launch.

  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed (no ESLint errors)
  • npx prettier --check packages/cli/src/ui/AppContainer.test.tsx — passed
  • Focused vitest (cli, changed file): src/ui/AppContainer.test.tsx — 158 passed (was 157; +1 is the new witness)
  • Mutation probe: removed the mock method → witness FAILED with the gate's exact TypeError; restored → witness green again
  • Gate command per touched package, vitest run --changed origin/main --passWithNoTests:
    • packages/cli — 671 files passed, 20658 passed | 90 skipped, 0 errors (the identical command rejected the previous commit with 13 unhandled rejections)
    • packages/core — 183 files passed | 1 skipped
    • packages/web-shell — 107 files passed
    • packages/webui — 15 files passed
    • packages/sdk-typescript — 14 files passed
    • packages/vscode-ide-companion — passed (--passWithNoTests, no affected tests)
  • Settings schema: no settings source changed this round, so npm run generate:settings-schema was not needed.

No inline review comments existed this round (rc.json empty), so resolved-comments.txt and comment-replies.json are omitted. The deferred finding is recorded in deferred-findings.json.

中文说明

处理评审总结 — PR #10024(同轮验证修复回合)

本轮提交:feat/artifact-share 分支上的 1f6c187741f0eebe4b15f1839fee67daed48bad5,追加保留的被拒提交 73381f8ad5 之上(未改写历史)。净差异:1 个文件,+23 / −0(纯测试改动)。

已修复的拒绝项:tests failed in packages/cli

上一个提交被拒的原因:门在 packages/cli 中执行 vitest run --changed origin/main 时报告了 13 个未处理的 rejection:TypeError: this.subagentManager.getAvailableModelGrades is not a function,从 AgentTool.updateDescriptionAndSchema(agent.ts:1034)经 refreshSubagents 的 catch 路径(agent.ts:887)抛出,归属于 src/ui/AppContainer.test.tsx。

根因(已复现,非猜测): AppContainer.test.tsx 的 beforeEach 安装的 Partial<SubagentManager> mock 提供了 listSubagents / addChangeListener / loadSubagent / createSubagent,但缺少 getAvailableModelGrades —— 这正是上游提交 d44030a4c0("feat(core): add model grade selection for subagent spawn")让 AgentTool.updateDescriptionAndSchema() 开始调用的方法。该提交更新了 core 自己的 mock(agent.test.ts、agent-headless.test.ts),却遗漏了这个 cli 测试。AppContainer 的渲染测试会在 mount effect 中调用真实的 Config.initialize(),其 warmAll({ strict }) 会实例化所有惰性工具工厂 —— 包括用这个不完整 mock 构造的 AgentTool。构造函数以 fire-and-forget 方式触发 refreshSubagents();updateDescriptionAndSchema() 在 try 中抛出,catch 再次调用它(agent.ts:887)又再次抛出,于是该 promise 以未处理 rejection 的形式落空。在 Linux 通道上 dangerouslyIgnoreUnhandledErrors 为 false,因此未处理的 rejection 直接导致运行失败。我用临时探针以相同的 mock 形状驱动真实的 Config.initialize(),复现出了完全一致的错误(栈也相同)。时机决定 rejection 落在哪里 —— 只有当它在 vitest worker 正在执行该文件期间落定才会被计数,这正是该文件单独运行时看起来干净、而门的运行却计到 13 个的原因 —— 但 mock 缺陷是确定性的,因此修复是无条件的。

修复: 在 mock 中补上 getAvailableModelGrades: vi.fn().mockReturnValue(new Map()) —— 与 agent.test.ts 使用的模式一致。补上之后,refreshSubagents() 链条正常完成(空的 grades 映射只会删除 model schema 属性)。

见证测试: 在 State Initialization 块中新增回归测试 boots AgentTool from the SubagentManager mock without unhandled rejections:它运行真实的 mockConfig.initialize(),断言 AgentTool 确实被实例化(ensureTool(ToolNames.AGENT) 有定义 —— 证明被检验的路径确实执行了),并断言期间没有出现未处理的 rejection。

变异探针: 临时移除新增的 mock 方法,见证测试即以门输出中完全一致的信息(this.subagentManager.getAvailableModelGrades is not a function)失败;还原后见证测试与全部 158 个测试的文件恢复绿色。

本回合未处理的反馈项

  • 三条评审建议([rv:5020361708]、[rv:5022059786])已在上一回合处理:两条已实现(删除了死代码的显式路由注册;provider CLI 现在安装到 Qwen 受管的 --prefix 位置),一条延迟到后续处理并重新记录在 deferred-findings.json(Netlify connect 流程持久化的机器相关绝对路径)。
  • 信息类 issue 评论:[ic:5411719702] web-shell 可视化预览将在下次推送后重新生成;[ic:5411721346] serve A/B 已通过;[ic:5414340912] review 回退通知 —— 无需处理。
  • 反馈中其余失败检查(macOS/Windows Test、Live Host、Java 21)均为 CANCELLED,并非独立失败;推送后会重新运行。

验证

本回合在已提交的树上实际运行的所有命令。测试腿在与门一致的 CI 等效洁净环境中执行(env -i、CI=true、全新 HOME、无 harness 变量),与门自身的 env -i 洁净子进程启动方式一致。

  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过(无 ESLint 错误)
  • npx prettier --check packages/cli/src/ui/AppContainer.test.tsx — 通过
  • vitest 聚焦(cli,改动文件):src/ui/AppContainer.test.tsx — 158 通过(原为 157;+1 为新见证测试)
  • 变异探针:移除 mock 方法 → 见证测试以门输出完全一致的 TypeError 失败;还原 → 见证测试恢复绿色
  • 每个涉及包的门命令 vitest run --changed origin/main --passWithNoTests:
    • packages/cli — 671 个文件通过,20658 通过 | 90 跳过,0 个错误(同一条命令在上一个提交上曾因 13 个未处理 rejection 拒绝该轮)
    • packages/core — 183 个文件通过 | 1 跳过
    • packages/web-shell — 107 个文件通过
    • packages/webui — 15 个文件通过
    • packages/sdk-typescript — 14 个文件通过
    • packages/vscode-ide-companion — 通过(--passWithNoTests,无受影响测试)
  • 设置 schema:本轮未改动任何设置源,因此无需运行 npm run generate:settings-schema。

本轮没有任何行内评审评论(rc.json 为空),因此省略 resolved-comments.txt 与 comment-replies.json。延迟处理的发现记录在 deferred-findings.json。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

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

PR #10024 — Review Report

Reviewed at: 1f6c187741f0eebe4b15f1839fee67daed48bad5 (current head, 2 new commits since prior review at bba81a37948)
Infrastructure note: git fetch is blocked on this Windows machine (getaddrinfo() thread failed to start), so no worktree, build, typecheck, or test verification was possible. Findings are based on gh pr diff output and raw file content from GitHub API.


Prior review status

The two new commits (73381f8a, 1f6c1877) have addressed 2 of 3 Suggestions from the doudouOUC review:

  • ✅ Suggestion #1 (dead explicit routes): Fixed — the explicit /workspace/artifact/netlify/setup and /workspaces/:workspace/artifact/netlify/setup routes were removed from both route registrations. Only the parameterized /:provider/setup route remains.
  • ✅ Suggestion #2 (npm --global prefix): Fixed — readGlobalNpmPrefix() was removed. The npm install --global command now includes --prefix artifactCliToolPrefix() which points to QwenDir/artifact-hosting/tools — Qwen's managed tool location. Tests updated to use TEST_ARTIFACT_TOOL_PREFIX.
  • ❌ Suggestion #3 (machine-specific absolute paths): Still standing — netlifyUploadCommand() (line 1610) still stores process.execPath and the provider entry path in artifact.host.uploadCommand at workspace scope. These paths are not portable if the workspace settings file is committed.

New findings

Critical

C1. Global setup queue blocks all workspaces (workspace-artifact-publish.ts:85-86)

setupQueue is a module-level promise chain that serializes ALL provider setup operations across ALL providers and ALL workspaces:

let setupQueue: Promise<unknown> = Promise.resolve();
function withSetupLock<T>(operation: () => Promise<T>): Promise<T> {
  const run = setupQueue.then(operation, operation);
  setupQueue = run.then(() => undefined, () => undefined);
  return run;
}

If workspace A's Netlify login or sites:create hangs, every other workspace's provider setup is blocked until timeout. Compare with publishQueues (line 83), which is correctly scoped per-workspace via withPublishLock. Should be a Map<string, Promise<unknown>> keyed by workspaceCwd.

C2. Stale Netlify-only data attributes (ShareArtifactDialog.tsx)

The generic multi-provider dialog still carries data-share-netlify-progress, data-share-netlify-step, and data-share-netlify-action attributes. Any test or E2E selector relying on these to distinguish Cloudflare/Vercel from Netlify will fire on the wrong provider. Should be renamed to data-share-provider-*.

High

H1. Netlify popup blocker undetected (ShareArtifactDialog.tsx)

The Netlify authorization path opens a window via window.open('', ...) but never checks if the result is null (popup blocked). The popup-blocked error message is only shown for non-Netlify providers. A blocked Netlify auth window would leave the user stuck on "Authorization pending" with no error.

H2. Wrong ARIA role for provider selector (ShareArtifactDialog.tsx)

Provider selection buttons use aria-pressed (toggle button semantics) but should use role="radiogroup" / role="radio" with aria-checked. Screen readers will announce "pressed" state, which is confusing for a single-selection list.

H3. deployDirectory uses fragile absolute path heuristic for Cloudflare/Vercel (workspace-artifact-publish.ts:~2480)

For Cloudflare and Vercel, deployDirectory() picks the first absolute path from the argument array as the deployment directory:

const candidates = provider === 'netlify'
  ? optionValues(args, '--dir')
  : args.filter((arg) => path.isAbsolute(arg));

If any future parameter value (e.g., --project-name or --branch) happens to be an absolute path, the function silently picks the wrong directory. Netlify correctly uses optionValues(args, '--dir') — Cloudflare/Vercel should follow the same pattern.

Medium

M1. defaultCheckPublicUrl follows redirects without limit (workspace-artifact-publish.ts:~2610)

fetch(url, { redirect: 'follow' }) has no maxRedirects cap. The 2-second timeout mitigates infinite loops, but adding maxRedirects: 5 is cheap defense-in-depth against an SSRF vector if the URL from CLI output is attacker-controlled.

M2. Publish catch chain swallows original error (workspace-artifact-publish.ts:~2950)

.catch(() => {
  abortScope.signal.throwIfAborted();
  assertRuntimeOpen(runtime);
  throw new SetupError(502, `${selectedProvider}_publish_failed`, ...);
});

The original error variable is discarded and replaced with a generic "Try again" message. Should log or attach as cause.

M3. Copied URL feedback never clears (ShareArtifactDialog.tsx)

copiedUrl state is set after copy but never cleared. The copy button permanently shows a checkmark icon after the first copy, which is misleading as the user navigates the dialog.

Low

L1. loginTickets Map not cleaned up on workspace lifecycle — Expired tickets only purged on access. Minor memory leak in long-running daemon sessions.

L2. providerEnv overrides effectiveEnv's PATH with process.env.PATH — Behavior is equivalent in practice, but the construction is confusing.

L3. installProvider runs with full process.env — The npm install command for provider CLIs uses the unbounded process environment, not the scrubbed providerEnv. Workspace-effective npm_config_* variables could redirect the npm registry. Mitigated by hardcoded package names.


CI status

Platform Status
Test (ubuntu-latest, Node 22.x) FAILURE
Test (windows-latest, Node 22.x) IN_PROGRESS
Test (macos-latest, Node 22.x) QUEUED

The Ubuntu test failure (confirmed by prior review) persists. The review-pr workflow is also in progress (likely the AutoFix round).


Verdict

2 Criticals, 3 Highs, 3 Mediums, 3 Lows found. C1 (global setup queue) and C2 (stale Netlify data attributes) should be addressed before merge. The standing Suggestion #3 from the prior review (machine-specific paths in settings) also remains open.

Review complete: pr-10024 — COMMENT

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix ran out of time before finishing (timeout (2700000ms)) (attempt 2/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:
Qwen failed during address-review: timeout (2700000ms).

See the Qwen Autofix agent step logs for model/tool output.

中文说明

🤖 AutoFix 在完成前耗尽了时间(timeout (2700000ms))(第 2/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/32911202808


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔀 Base updated: red check(s) [Test (ubuntu-latest, Node 22.x)] pass on current main — merged current main via update-branch; CI will re-run.

中文说明

🔀 已更新 base:红色检查 [Test (ubuntu-latest, Node 22.x)] 在当前 main 上通过 —— 已通过 update-branch 合入当前 main,CI 将重新运行。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 5/100). What changed, and what I pushed back on: · 已处理最新评审反馈(第 5/100 轮)。改动内容与我反驳保留之处如下:

Address-review summary — PR #10024 (same-run verification repair)

This round ran as a same-run verification repair under the workflow's budget warning: the rejected commit is preserved unchanged, and one follow-up commit fixes exactly the deterministic rejection — tests failed in packages/core. No other feedback work was performed this round; every non-resolved finding keeps its per-thread reply in comment-replies.json.

Diagnosis

The gate's packages/core run failed 9 tests in src/core/client.telemetrySwap.test.ts with TypeError: this.config.getToolRegistry is not a function (reproduced locally: 9 failed | 1 passed, identical to the gate output).

Evidence trail:

  • This PR does not touch packages/core/src/core/ at all (git diff origin/main...HEAD for that directory is empty); both client.ts and the test file are byte-identical to origin/main.
  • The failing call was introduced on main by 4167d4f68e ("feat(telemetry): Emit context usage span attribute (feat(telemetry): Emit context usage span attribute #10016)"): GeminiClient.initialize() now calls restoreLoadedSkillsFromHistory() on both resume paths, which does this.config.getToolRegistry().getTool(ToolNames.SKILL). The test (added earlier by c798674678) hand-rolls a partial Config mock that never gained getToolRegistry, so every resume-path test crashes.
  • The failure is latent on the base branch and surfaced for this PR only because vitest run --changed origin/main selects the test through the dependency graph of the PR's packages/core changes. It reproduces at the merge base for the same reason the gate sees it; the fix belongs on this branch because the gate re-runs the same command here.

Change this round

packages/core/src/core/client.telemetrySwap.test.ts (+3 lines): the partial mock in makeEnv() gains getToolRegistry: () => ({ getTool: () => undefined }), matching the standard partial-mock pattern used across core tests. The test suite exercises telemetry-swap transactions, not skill restoration, so a registry with no tools is the faithful stub; the production call optional-chains the missing tool and no-ops.

Witness/mutation evidence: the very run that reproduced the defect (pre-fix tree, identical otherwise — the working-tree diff is exactly these 3 lines) failed 9/10 tests with the gate's exact TypeError; with the mock line added all 10 pass. Removing the line returns the file to red, so the fix is load-bearing and minimal.

Feedback not acted on this round

Everything below stays open with a per-thread reply (comment-replies.json, 174 entries) — deferred, not dropped:

  • Criticals deferred to the next autofix round: R1-2, R1-4/R1-5, R2-8, R2-10, R2-11, R2-12, R3-1, R3-2, R4-3, R4-4, R4-5, R4-6, R4-7, R4-8.
  • Needs a maintainer's decision (thread left open with the explicit question): R2-32 — where daemon-managed share state should be persisted if workspace scope is restricted.
  • Suggestions deferred under the budget warning: R1-6…R1-31, R1-34…R1-40, plus the five standalone test findings (authorization-URL negative tests, Vercel-reuse persist assertion, retry-delay assertion, per-workspace ticket keying, POST trust-boundary tests).
  • Maintainer review [rv:5023944258] (@doudouOUC) (review-body findings, no inline threads): C1 matches R1-14 (deferred); C2, H1–H3, M1–M3, L1–L3 deferred under the same budget posture; Suggestion 如何自定义密钥文件 .env可能与其他文件冲突 #3 (machine-specific absolute paths persisted in artifact.host.uploadCommand) is re-recorded in deferred-findings.json, carried over from round 1.
  • Resolved by the preserved rejected commit (verified still present at HEAD, listed in resolved-comments.txt): R3-3 (flaky-test diagnosability + assertLinkBoundary fail-open + witness), R1-1/R2-1/R2-7/R4-1/R4-2 (the providerEnv scrubbing/pinning cluster), R1-3 (makeSitePublic gated to the managed dedicated site), R1-32/R1-33 (entrypoint witness rewritten post-request with a non-emptiness guard).

No conflict resolution was needed (--conflict false).

Verification

All commands actually run this round (gate-shaped suites run under env -i CI=true HOME=<fresh>, mirroring the gate):

  • npx vitest run src/core/client.telemetrySwap.test.ts (packages/core) — before fix: 9 failed | 1 passed (reproduction/mutation probe); after fix: 10 passed
  • npm run build — passed
  • npm run typecheck — passed
  • npm run lint — passed
  • npx prettier --check packages/core/src/core/client.telemetrySwap.test.ts — passed
  • Gate-shaped npx vitest run --changed origin/main --passWithNoTests in packages/core — 9578 passed | 8 skipped | 0 failed (a first run in the harness's own environment showed 7 failures in skill-manager/subagent-manager tests; traced to this agent harness's exported QWEN_HOME/SANDBOX variables, exactly as the prior round documented — they pass in the clean environment the gate uses)
  • Gate-shaped npx vitest run --changed origin/main --passWithNoTests in packages/cli — 674 test files passed, exit 0 (an earlier run executed concurrently with two other suites hit two 15 s timeouts in server-default-bridge-wiring.test.ts from CPU contention; the file passes 7/7 in isolation and the full solo rerun is green)
  • Gate-shaped npx vitest run --config vitest.config.ts --changed origin/main --passWithNoTests in packages/web-shell — 107 test files passed, exit 0
  • Gate-shaped npx vitest run --changed origin/main --passWithNoTests in packages/sdk-typescript — 14 test files passed, exit 0
中文说明

处理评审意见总结 — PR #10024(同轮验证修复)

本轮以同轮验证修复模式运行,且处于工作流的预算警告之下:被拒绝的提交原样保留,仅追加一次后续提交修复确定性拒绝本身——tests failed in packages/core。本轮不做任何其他反馈处理;每条未解决的发现都在 comment-replies.json 中保留了对应线程的回复。

诊断

门禁的 packages/core 运行中 src/core/client.telemetrySwap.test.ts 有 9 个测试失败,报错 TypeError: this.config.getToolRegistry is not a function(本地复现:9 失败 | 1 通过,与门禁输出一致)。

证据链:

  • 本 PR 完全没有触碰 packages/core/src/core/(该目录相对 origin/main 的 git diff 为空);client.ts 与该测试文件均与 origin/main 逐字节一致。
  • 失败的调用由 main 上的 4167d4f68e("feat(telemetry): Emit context usage span attribute (feat(telemetry): Emit context usage span attribute #10016)")引入:GeminiClient.initialize() 现在会在两条 resume 路径上调用 restoreLoadedSkillsFromHistory(),其中执行 this.config.getToolRegistry().getTool(ToolNames.SKILL)。该测试(更早由 c798674678 添加)手写的部分 Config mock 从未补上 getToolRegistry,于是所有走 resume 路径的测试全部崩溃。
  • 该失败是基线分支上的潜伏问题,只是因为 vitest run --changed origin/main 通过本 PR packages/core 改动的依赖图选中了该测试,才在本 PR 上暴露。它在合并基点上同样会复现,原因与门禁所见相同;修复落在本分支是因为门禁在此处重跑同一条命令。

本轮改动

packages/core/src/core/client.telemetrySwap.test.ts(+3 行):makeEnv() 中的部分 mock 增加 getToolRegistry: () => ({ getTool: () => undefined }),与 core 测试中通用的部分 mock 模式一致。该测试套件验证的是遥测交换事务,而非技能恢复,因此一个不含任何工具的 registry 是忠实的桩;生产调用对缺失的 tool 使用可选链,自然成为空操作。

见证/变异证据:复现缺陷的那次运行(修复前的树,其余完全一致——工作区 diff 恰好就是这 3 行)以门禁完全相同的 TypeError 失败 9/10 个测试;加上 mock 行后 10 个全部通过。移除该行文件重新变红,证明该修复既是关键最小改动。

本轮未处理的反馈

以下全部保持开放,并在各自线程中回复(comment-replies.json,共 174 条)——延后而非丢弃:

  • 延后到下一个 autofix 轮次的 Critical:R1-2、R1-4/R1-5、R2-8、R2-10、R2-11、R2-12、R3-1、R3-2、R4-3、R4-4、R4-5、R4-6、R4-7、R4-8。
  • 需要维护者决策(线程保持开放并附明确问题):R2-32——若限制工作区作用域,守护进程托管的共享状态应持久化到哪里。
  • 因预算警告延后的建议类:R1-6…R1-31、R1-34…R1-40,外加五项独立测试发现(授权 URL 负向测试、Vercel 复用持久化断言、重试延时断言、按工作区的票据键控、POST 信任边界测试)。
  • 维护者评审 [rv:5023944258](@doudouOUC)(评审正文发现,无行内线程):C1 与 R1-14 相同(延后);C2、H1–H3、M1–M3、L1–L3 按同样的预算策略延后;建议 如何自定义密钥文件 .env可能与其他文件冲突 #3(持久化进 artifact.host.uploadCommand 的机器相关绝对路径)重新记录在 deferred-findings.json 中,承接第 1 轮。
  • 已由被保留的拒绝提交解决(已验证仍存在于 HEAD,列于 resolved-comments.txt):R3-3(flaky 测试可诊断性 + assertLinkBoundary 放行加固 + 见证)、R1-1/R2-1/R2-7/R4-1/R4-2(providerEnv 清除/钉回簇)、R1-3(makeSitePublic 限定于托管专用站点)、R1-32/R1-33(入口见证改写为请求后断言并附非空保证)。

本轮无需解决冲突(--conflict false)。

验证

本轮实际运行的全部命令(门禁形态套件均在 env -i CI=true HOME=<全新> 下运行,与门禁一致):

  • npx vitest run src/core/client.telemetrySwap.test.ts(packages/core)——修复前:9 失败 | 1 通过(复现/变异探针);修复后:10 通过
  • npm run build — 通过
  • npm run typecheck — 通过
  • npm run lint — 通过
  • npx prettier --check packages/core/src/core/client.telemetrySwap.test.ts — 通过
  • 门禁形态 npx vitest run --changed origin/main --passWithNoTests(packages/core)——9578 通过 | 8 跳过 | 0 失败(首次在本代理自身环境运行时,skill-manager/subagent-manager 出现 7 个失败;已追溯到本代理运行环境导出的 QWEN_HOME/SANDBOX 变量,与上一轮记录的情况完全一致——在门禁所用的干净环境中全部通过)
  • 门禁形态 npx vitest run --changed origin/main --passWithNoTests(packages/cli)——674 个测试文件全部通过,退出码 0(更早一次与另外两个套件并发执行时,server-default-bridge-wiring.test.ts 因 CPU 争抢出现两个 15 秒超时;该文件单独运行 7/7 通过,完整单跑重测为绿色)
  • 门禁形态 npx vitest run --config vitest.config.ts --changed origin/main --passWithNoTests(packages/web-shell)——107 个测试文件全部通过,退出码 0
  • 门禁形态 npx vitest run --changed origin/main --passWithNoTests(packages/sdk-typescript)——14 个测试文件全部通过,退出码 0

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human. · 有空请复审;第 100 轮后本 bot 停止并将 PR 交给人工。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix stopped: this counting window now contains 3 agent time-budget exhaustions (pushed rounds in between included; this round itself may have failed differently). That is 3 full agent runs that pushed nothing. A human should split or reduce the PR (or raise the agent time budget AND its step backstop together), then comment @qwen-code /retry to re-arm. Until then future scans will skip this PR.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:
Qwen failed during address-review: timeout (2700000ms).

See the Qwen Autofix agent step logs for model/tool output.

中文说明

🤖 AutoFix 已停止:当前计数窗口内已累计 3 次时间预算耗尽(含其间推送过的轮次;本轮本身可能以别的方式失败)。即 3 次完整 agent 运行没有推送任何内容。应由人工拆分或缩减该 PR(或同时提高 agent 时间预算与其步骤兜底),然后评论 @qwen-code /retry 重新武装。在此之前,后续扫描将跳过本 PR。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33059773196


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/needs-human The autofix loop stopped on this PR — a human must re-arm, split, merge, or close it label Aug 27, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

⏸️ Takeover paused: this PR reached its round cap (100/100). Comment @qwen-code /takeover to re-arm a fresh window and continue management, or @qwen-code /takeover stop to release.

中文说明

⏸️ 托管已暂停:本 PR 达到轮次上限(100/100)。评论 @qwen-code /takeover 可重新武装、开启新窗口继续托管;或评论 @qwen-code /takeover stop 释放。

@qqqys

qqqys commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔄 Takeover re-armed: the round counter starts a fresh window (previous rounds no longer count toward the cap); management continues.

中文说明

🔄 已重新武装:轮次计数开启新窗口(此前轮次不再计入上限),托管继续。

@qwen-code-dev-bot qwen-code-dev-bot removed the autofix/needs-human The autofix loop stopped on this PR — a human must re-arm, split, merge, or close it label Aug 27, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix updated a stale base — the fix did not pass verification, but this PR was behind main, so it merged current main in via update-branch and will retry on the next scan. A stale base (a dependency or symbol main already changed) can fail the build without being the fix's fault; if it still fails once current, it hands off to a human.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Address-review round summary — PR #10024 (same-run verification repair)

This is a same-run verification repair round. The previous commit
(9b16e6e472, implementing 7 Critical findings from round 5) was rejected by
deterministic verification: the gate's vitest run --changed origin/main --passWithNoTests in packages/cli failed one test —
src/ui/utils/terminal-image-renderer.test.ts > terminalImageRenderer > caches a render so resize and restore do not re-spawn chafa with
AssertionError: expected 'unavailable' to be 'ansi'. Per the repair rule,
the rejected commit is preserved and this round adds exactly one follow-up
commit fixing that rejection; it makes no other production changes.

The rejection and its fix

Root cause (reproduced, not guessed). The test's fake chafa is an
extensionless CommonJS script (#!/usr/bin/env node + require(...))
written under os.tmpdir(). Node resolves an entry script's module type from
the nearest ancestor package.json; on this runner /tmp/package.json
exists and carries {"type":"module"}, so the fixture loaded as ESM and
crashed with ReferenceError: require is not defined in ES module scope
(probe output recorded). The production renderer then correctly reported the
crashed child as unavailable, failing the test's toBe('ansi'). The
production code is correct; the test fixture was environment-dependent.

Fix (commit c18e71382f, test-only, +7 lines). The test now writes
{"type":"commonjs"} as a

Why it was not pushed:

Note: the base has since been auto-updated; the verdict below predates that update, and the next round's re-measurement may charge the round.

tests failed in packages/cli

rs on valid directory �[33m 4205�[2mms�[22m�[39m
   �[33m�[2m✓�[22m�[39m clipboardUtils�[2m > �[22mmacOS/Windows fallback�[2m > �[22mnotifies after a cached native module load failure �[33m 8725�[2mms�[22m�[39m
   �[33m�[2m✓�[22m�[39m clipboardUtils�[2m > �[22mmacOS/Windows fallback�[2m > �[22mshares an in-flight native module load without false errors �[33m 6644�[2mms�[22m�[39m
   �[33m�[2m✓�[22m�[39m clipboardUtils�[2m > �[22mmacOS/Windows fallback�[2m > �[22mshould return false on non-linux platform when @teddyzhu/clipboard fails �[33m 2119�[2mms�[22m�[39m
   �[33m�[2m✓�[22m�[39m clipboardUtils�[2m > �[22mmacOS/Windows fallback�[2m > �[22mshould return null on non-linux platform when saving fails �[33m 481�[2mms�[22m�[39m
   �[33m�[2m✓�[22m�[39m clipboardUtils�[2m > �[22mcache behavior�[2m > �[22mshould reset wl-paste cache between clipboardHasImage calls �[33m 378�[2mms�[22m�[39m
   �[33m�[2m✓�[22m�[39m clipboardUtils�[2m > �[22mwriteOsc52�[2m > �[22mshould write OSC 52 sequence to stdout when stdout is TTY �[33m 356�[2mms�[22m�[39m
   �[33m�[2m✓�[22m�[39m clipboardUtils�[2m > �[22mwriteOsc52�[2m > �[22mshould return false and not write when neither stdout nor stderr is TTY �[33m 317�[2mms�[22m�[39m

�[31m⎯⎯⎯⎯⎯⎯⎯�[39m�[1m�[41m Failed Tests 1 �[49m�[22m�[31m⎯⎯⎯⎯⎯⎯⎯�[39m

�[41m�[1m FAIL �[22m�[49m src/serve/server-default-bridge-wiring.test.ts�[2m > �[22mcreateServeApp default bridge wiring�[2m > �[22mwires the internally-created bridge lifecycle into the workspace registry
�[31m�[1mError�[22m: Test timed out in 15000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".�[39m
�[36m �[2m❯�[22m src/serve/server-default-bridge-wiring.test.ts:�[2m56:3�[22m�[39m
    �[90m 54| �[39m  })�[33m;�[39m
    �[90m 55| �[39m
    �[90m 56| �[39m  it('wires the internally-created bridge lifecycle into the workspace…
    �[90m   | �[39m  �[31m^�[39m
    �[90m 57| �[39m    �[35mlet�[39m sessionLifecycle�[33m:�[39m �[33mBridgeOptions�[39m[�[32m'sessionLifecycle'�[39m]�[33m;�[39m
    �[90m 58| �[39m    �[35mlet�[39m bridgeOptions�[33m:�[39m �[33mBridgeOptions�[39m �[33m|�[39m undefined�[33m;�[39m

�[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯�[22m�[39m


�[2m Test Files �[22m �[1m�[31m1 failed�[39m�[22m�[2m | �[22m�[1m�[32m673 passed�[39m�[22m�[90m (674)�[39m
�[2m      Tests �[22m �[1m�[31m1 failed�[39m�[22m�[2m | �[22m�[1m�[32m20910 passed�[39m�[22m�[2m | �[22m�[33m87 skipped�[39m�[90m (20998)�[39m
�[2m   Start at �[22m 03:00:20
�[2m   Duration �[22m 202.17s�[2m (transform 307.98s, setup 105.63s, collect 5920.27s, tests 849.91s, environment 259.91s, prepare 101.14s)�[22m

JUNIT report written to /home/github-runner/actions-runner/_work/qwen-code/qwen-code/packages/cli/junit.xml
npm error Lifecycle script `test` failed with error:
npm error code 1
npm error path /home/github-runner/actions-runner/_work/qwen-code/qwen-code/packages/cli
npm error workspace @qwen-code/[email protected]
npm error location /home/github-runner/actions-runner/_work/qwen-code/qwen-code/packages/cli
npm error command failed
npm error command sh -c vitest run --changed origin/main --passWithNoTests
中文说明

🤖 AutoFix 更新了一个过期的 base —— 修复未通过验证,但本 PR 落后于 main,因此已通过 update-branch 合入当前 main,并将在下次扫描时重试。过期的 base(main 已改动的依赖或符号)可能让构建失败而并非修复本身的错;若 base 更新后仍然失败,将移交人工处理。

验证门的拒绝原因与日志证据见上方英文部分(gate-rejection 不翻译)。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33097756164


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

qqqys added 3 commits August 28, 2026 11:07
Round-6 findings:
- Drop the duplicate `getToolRegistry` this branch added to the telemetry
  swap test's config literal. TS1117 broke the packages/core build, which
  blocked every dependent workspace's build and all test suites.
- Detect the daemon's own Netlify command by its entry path, not by the
  interpreter's basename. `process.execPath` is whatever runs the daemon
  (`node-22`, Debian `nodejs`, a shim), so the daemon could not re-parse
  the command it had just written and setup 500'd after creating the site.
- Keep the scheme the Vercel CLI already puts on `latestProductionUrl`
  instead of emitting `https://https://...`.
- Strip Netlify password/SSO protection only for a site this flow created,
  tracked by a new `artifact.share.netlify.dedicatedSiteId`. `siteId` alone
  is also persisted for an adopted user-linked site, so the first publish
  removed that site's protection silently and without consent.

Standing findings from earlier rounds:
- Boundary-check the adoption branch, not just creation: netlify-cli
  resolves the link by walking up from the workspace cwd, so a nested
  workspace adopted and published into the parent repository's site.
- Distinguish "no such site" from "the API could not be reached" in
  `readSiteRecord`; a transient failure dropped a ready workspace back to
  the setup stage and made its configured site look non-dedicated.
- Accept any record `getSite` returns for the configured identifier, so a
  site name or subdomain alias is no longer rejected as unconfigured.
- Treat absent `sso_login`/`has_password` as unprotected rather than
  unknown; a public site failed with `netlify_public_access_failed`.
- Refuse to overwrite a user-authored `artifact.host.uploadCommand`.
- Refuse to adopt a same-named Vercel project from another scope once a
  project id has been persisted.
- Give the authorization poll its own AbortController: sharing one slot
  with setup and publish made them abort each other, stranding the dialog
  in a permanent polling state or killing an in-flight publish.
- Stop reading `window.open(..., 'noopener')`'s null return as a blocked
  popup — it is null per spec either way, so authorization was reported
  blocked on success and the poll it depends on was suppressed.

Claude-Session: https://claude.ai/code/session_018dYE4LwSMeMPFchXk5UBdM
@qqqys

qqqys commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator Author

Round-6 pass — 15 of the 19 open Criticals are closed in de76d60, and 7d2f844 merges upstream/main (two conflicts: the sdk bundle budget, taking main's 215KB, and the GeminiClient → LlmClient rename). The PR is MERGEABLE again.

Each fix is answered in its own thread with the test that pins it. Every one was mutation-checked: reverting the fix turns the named test red. Suites: route 69/69, web-shell dialogs 302/302, telemetry swap 10/10, AppContainer 167/167; packages/cli and packages/web-shell typecheck clean; the full workspace npm run build is green again — R6-1's duplicate key had been breaking packages/core and therefore every dependent build and test job, which is what the five red CI checks were.

Four findings are deliberately left open — they are architectural rather than local, and each would change the feature's trust model in a way that wants its own review rather than being folded into this pass:

  • R5-1 (providerEnv exact-case env denylist) and R3-1 (daemon-global credential store shared by every workspace's provider CLI). Both say the same thing: an env denylist around third-party CLIs keeps yielding bypasses, and case-variant keys are only the current instance. The real fix is an allowlist — spawn provider CLIs with an explicitly constructed env instead of a scrubbed inherited one — plus per-workspace credential isolation. That is a rewrite of providerEnv and the credential-home handling, not a patch.
  • R2-32 (artifact.share.* / artifact.host.* honored from repo-committable workspace settings). This is a policy decision about which scopes may configure a publish target, and it has precedent to follow — security.review.attribution is already restricted to User/System scopes. It belongs with whoever owns that policy.
  • R3-2 (no headless authorization path for Cloudflare/Vercel) is a missing capability rather than a defect in what is here: wrangler login / vercel login need a browser and a localhost callback on the daemon host. Surfacing the login URL through the setup response is a feature-sized change.

I would rather these were tracked as follow-ups than half-fixed here. Happy to take any of them in a separate PR — say which.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix ran out of time before finishing (timeout (3600000ms)) (attempt 2/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:
Qwen failed during address-review: timeout (3600000ms).

See the Qwen Autofix agent step logs for model/tool output.

中文说明

🤖 AutoFix 在完成前耗尽了时间(timeout (3600000ms))(第 2/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33128178614


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix updated a stale base — the fix did not pass verification, but this PR was behind main, so it merged current main in via update-branch and will retry on the next scan. A stale base (a dependency or symbol main already changed) can fail the build without being the fix's fault; if it still fails once current, it hands off to a human.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Round 9 — PR #10024 same-run verification repair

The previous commit (c31fe6f2ea, the round-7-findings batch) was REJECTED by
deterministic verification: the full packages/cli suite ran
vitest run --changed origin/main --passWithNoTests and exactly one test
failed — terminal-image-renderer.test.ts > caches a render so resize and restore do not re-spawn chafa (expected 'unavailable' to be 'ansi'). Per
the same-run repair rules, the rejected commit is preserved and this round
adds one verified follow-up commit on top: e0f3f5bd65.

Root cause (evidence-based, reproduced locally)

The test spawns a fake chafa — an extensionless node script whose body uses
CommonJS (require("fs")) — from a mkdtemp directory under os.tmpdir().
This self-hosted runner carries a stray /tmp/package.json containing
{"type":"module","dependencies":{"wrangler":"^4.127.0","yaml":"^2.9.0"}}
(created 2026-08-28 06:51 — probe debris from an earlier review round that
npm-installed wrangler into /tmp). Node resolves the nearest package.json
for the extensionless script, finds that "type":"module", runs the fake
chafa as an ES module, and the script crashes with
ReferenceError: require is not defined in ES module scope (captured via a
direct probe of renderTerminalImage, which returned
{"kind":"unavailable","reason":"file:///tmp/…/bin/chafa:2 …"}). The render
therefore degrades to unavailable and the assertion fails —
deterministically, on this runner,

Why it was not pushed:

Note: the base has since been auto-updated; the verdict below predates that update, and the next round's re-measurement may charge the round.

tests failed in packages/cli

m sessionLifecycle�[33m:�[39m �[33mBridgeOptions�[39m[�[32m'sessionLifecycle'�[39m]�[33m;�[39m
    �[90m 58| �[39m    �[35mlet�[39m bridgeOptions�[33m:�[39m �[33mBridgeOptions�[39m �[33m|�[39m undefined�[33m;�[39m

�[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/3]⎯�[22m�[39m

�[41m�[1m FAIL �[22m�[49m src/serve/server-default-bridge-wiring.test.ts�[2m > �[22mcreateServeApp default bridge wiring�[2m > �[22m'derives the scheduled-task budget fro…'
�[31m�[1mError�[22m: Test timed out in 15000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".�[39m
�[36m �[2m❯�[22m src/serve/server-default-bridge-wiring.test.ts:�[2m305:4�[22m�[39m
    �[90m303| �[39m      expected�[33m:�[39m �[33mMAX_SESSION_RESTORE_TIMEOUT_MS�[39m �[33m+�[39m �[34m1�[39m�[33m,�[39m
    �[90m304| �[39m    }�[33m,�[39m
    �[90m305| �[39m  ])(�[32m'$label'�[39m�[33m,�[39m �[35masync�[39m ({ sessionRestoreTimeoutMs�[33m,�[39m expected }) �[33m=>�[39m {
    �[90m   | �[39m   �[31m^�[39m
    �[90m306| �[39m    // Without this, deleting the `loadTimeoutMs` / `reviveTimeoutMs` …
    �[90m307| �[39m    �[90m// ships green and both helpers silently fall back to their own 70s�[39m

�[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[2/3]⎯�[22m�[39m

�[41m�[1m FAIL �[22m�[49m src/serve/server-default-bridge-wiring.test.ts�[2m > �[22mcreateServeApp default bridge wiring�[2m > �[22m'passes the disable sentinel when the …'
�[31m�[1mAssertionError�[22m: expected undefined to be defined�[39m
�[36m �[2m❯�[22m src/serve/server-default-bridge-wiring.test.ts:�[2m357:50�[22m�[39m
    �[90m355| �[39m      { manageScheduledTaskSessions: true, primaryWorkspaceTrusted: tr…
    �[90m356| �[39m    )�[33m;�[39m
    �[90m357| �[39m    �[35mawait�[39m vi�[33m.�[39m�[34mwaitFor�[39m(() �[33m=>�[39m �[34mexpect�[39m(rehydrateOpts)�[33m.�[39m�[34mtoBeDefined�[39m())�[33m;�[39m
    �[90m   | �[39m                                                 �[31m^�[39m
    �[90m358| �[39m
    �[90m359| �[39m    �[34mexpect�[39m(rehydrateOpts�[33m?.�[39mloadTimeoutMs)�[33m.�[39m�[34mtoBe�[39m(expected)�[33m;�[39m

�[31m�[2m⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[3/3]⎯�[22m�[39m


�[2m Test Files �[22m �[1m�[31m1 failed�[39m�[22m�[2m | �[22m�[1m�[32m692 passed�[39m�[22m�[90m (693)�[39m
�[2m      Tests �[22m �[1m�[31m3 failed�[39m�[22m�[2m | �[22m�[1m�[32m21638 passed�[39m�[22m�[2m | �[22m�[33m87 skipped�[39m�[90m (21728)�[39m
�[2m   Start at �[22m 20:26:24
�[2m   Duration �[22m 255.19s�[2m (transform 377.19s, setup 127.79s, collect 7745.00s, tests 1019.66s, environment 308.68s, prepare 122.61s)�[22m

JUNIT report written to /home/github-runner/actions-runner-21/_work/qwen-code/qwen-code/packages/cli/junit.xml
npm error Lifecycle script `test` failed with error:
npm error code 1
npm error path /home/github-runner/actions-runner-21/_work/qwen-code/qwen-code/packages/cli
npm error workspace @qwen-code/[email protected]
npm error location /home/github-runner/actions-runner-21/_work/qwen-code/qwen-code/packages/cli
npm error command failed
npm error command sh -c vitest run --changed origin/main --passWithNoTests
中文说明

🤖 AutoFix 更新了一个过期的 base —— 修复未通过验证,但本 PR 落后于 main,因此已通过 update-branch 合入当前 main,并将在下次扫描时重试。过期的 base(main 已改动的依赖或符号)可能让构建失败而并非修复本身的错;若 base 更新后仍然失败,将移交人工处理。

验证门的拒绝原因与日志证据见上方英文部分(gate-rejection 不翻译)。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33159300648


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix round report — no code changes this round

Head evaluated: 8f3ccb8ae6 (feat/artifact-share, merge of main at d6533785bd)

Why no changes

This round's blocking item was the rejected verification gate from the previous attempt ("tests failed in packages/cli" — three failures in src/serve/server-default-bridge-wiring.test.ts). That failure no longer reproduces at the current head: the rejected round's commits were discarded and the base was auto-updated since the verdict. I verified this by execution, not by reading the diff:

  • server-default-bridge-wiring.test.ts passes in isolation (7/7) and inside the full gate run below.
  • The exact gate command for packages/cli, vitest run --changed origin/main --passWithNoTests, passes: 691/698 files green (21,929 tests) once run in a clean environment (see the environment note for the other 7 files).

No defect attributable to this PR's code was found, so there was nothing to fix and no commit this round.

Environment note (local-only failures, proven infrastructural)

Running the suite from this agent's shell initially produced failures that do not exist in CI. Three controlled experiments identified the causes:

  1. This agent environment exports QWEN_HOME and SANDBOX, which a set of settings/config/UI tests assume are unset (one test is literally named "when QWEN_HOME is not set"). With both unset, settings.test.ts + config.test.ts pass 529/529.
  2. This sandbox user's HOME is not writable, so tests that mkdir ~/.qwen fail with EACCES (llm.test.tsx, AppContainer.test.tsx, …). With a writable HOME, all 7 affected files pass (1,185 tests).
  3. The reviewer-visible gate runner is unaffected: the previous gate run on the same command reported only the three bridge-wiring failures out of 21,728 tests, i.e. its environment is clean and those specific failures no longer reproduce.

These are sandbox/agent-environment artifacts, not PR defects — recorded here per the stop-rule evidence requirement rather than in failure.md, because the checks pass under proper conditions.

Remaining review findings

Per the budget warning (the previous round exhausted its time budget), this round did not retry the full finding batch. All unresolved inline findings are answered in their own threads via comment-replies.json (95 replies): the 4 architectural classes (env-denylist allowlist rewrite, per-workspace credential isolation, workspace-scope publish-target policy, headless Cloudflare/Vercel authorization) remain deferred per the maintainer's recorded round-6 decision; 13 threads whose fixes landed in de76d60 were independently re-verified as still holding at this head (noted in their threads); the rest are deferred to the next round.

Verification

Commands actually run this round, at head 8f3ccb8ae6:

  • npm run build — passed (also proves the R6-1 TS1117 duplicate-key regression stays fixed)
  • npm run typecheck — passed
  • npm run lint — passed
  • npm run generate:settings-schema — passed, committed schema artifact up to date (no drift)
  • packages/cli vitest run src/serve/server-default-bridge-wiring.test.ts — 7 passed
  • packages/cli vitest run src/config/settings.test.ts src/config/config.test.ts (env -u QWEN_HOME -u SANDBOX) — 529 passed
  • packages/cli gate command vitest run --changed origin/main --passWithNoTests (env -u QWEN_HOME -u SANDBOX) — 691/698 files, 21,929 passed; the 7 failing files (27 tests) re-run with a writable HOME — all 7 files pass (1,185 tests), proving them environment-only
  • packages/core vitest run src/tools/artifact/host-publisher.test.ts — passed
  • packages/web-shell vitest run client/App.test.tsx client/components/artifacts/TurnOutputs.dom.test.tsx client/components/messages/SettingsMessage.dom.test.tsx — 607 passed
  • packages/sdk-typescript vitest run test/unit/DaemonClient.test.ts — 353 passed
  • packages/webui vitest run src/daemon/workspace/actions.test.ts — 19 passed

No mutation probes this round: the round adds no guards, branches, or behavior changes.

中文说明

Autofix 轮次报告 — 本轮无代码改动

评估的分支头: 8f3ccb8ae6(feat/artifact-share,合并 main 至 d6533785bd)

为什么没有改动

本轮的阻断项是上一次尝试被拒的验证门禁("tests failed in packages/cli" — src/serve/server-default-bridge-wiring.test.ts 的三处失败)。该失败在当前分支头已无法复现:被拒轮次的提交已被丢弃,且裁决之后基线已自动更新。我以实际执行而非阅读 diff 的方式验证了这一点:

  • server-default-bridge-wiring.test.ts 单独运行通过(7/7),在下方完整门禁运行中也通过。
  • packages/cli 的确切门禁命令 vitest run --changed origin/main --passWithNoTests 通过:在干净环境下运行 691/698 个文件为绿(21,929 个测试)(其余 7 个文件的原因见环境说明)。

未发现可归因于本 PR 代码的缺陷,因此本轮无修复、无提交。

环境说明(仅本地出现的失败,已证实为基础设施问题)

在本代理的 shell 中运行测试套件最初出现了一些 CI 中不存在的失败。三组对照实验确定了原因:

  1. 本代理环境导出了 QWEN_HOME 和 SANDBOX,而一批 settings/config/UI 测试假定它们未设置(其中一个测试名即为 "when QWEN_HOME is not set")。两者取消后,settings.test.ts + config.test.ts 通过 529/529。
  2. 本沙箱用户的 HOME 不可写,因此执行 mkdir ~/.qwen 的测试以 EACCES 失败(llm.test.tsx、AppContainer.test.tsx 等)。改用可写的 HOME 后,全部 7 个受影响文件通过(1,185 个测试)。
  3. 评审可见的门禁运行器不受影响:同一命令的上次门禁运行在 21,728 个测试中只报告了那三处 bridge-wiring 失败,说明其环境是干净的,且这些具体失败已不再复现。

这些是沙箱/代理环境的产物,不是 PR 缺陷——按停止规则的证据要求记录于此,而不是写入 failure.md,因为这些检查在正常条件下是通过的。

剩余评审发现

按预算警告(上一轮耗尽了时间预算),本轮未重试完整的发现批次。所有未解决的行内发现均已通过 comment-replies.json 在各自线程中回复(95 条):四个架构类问题(环境拒绝清单改写为白名单、按工作区隔离凭据、工作区作用域发布目标策略、Cloudflare/Vercel 无头授权)按维护者第 6 轮记录在案的决定继续延后;13 个修复已随 de76d60 落地的线程已独立复核在当前头仍然成立(已在其线程中注明);其余延后至下一轮。

验证

本轮在分支头 8f3ccb8ae6 实际执行的命令:

  • npm run build — 通过(同时证明 R6-1 的 TS1117 重复键回归保持已修复)
  • npm run typecheck — 通过
  • npm run lint — 通过
  • npm run generate:settings-schema — 通过,已提交的 schema 产物为最新(无漂移)
  • packages/cli vitest run src/serve/server-default-bridge-wiring.test.ts — 7 通过
  • packages/cli vitest run src/config/settings.test.ts src/config/config.test.ts(环境 -u QWEN_HOME -u SANDBOX)— 529 通过
  • packages/cli 门禁命令 vitest run --changed origin/main --passWithNoTests(环境 -u QWEN_HOME -u SANDBOX)— 691/698 个文件、21,929 通过;其中 7 个失败文件(27 个测试)改用可写 HOME 重跑 — 7 个文件全部通过(1,185 个测试),证明其仅为环境问题
  • packages/core vitest run src/tools/artifact/host-publisher.test.ts — 通过
  • packages/web-shell vitest run client/App.test.tsx client/components/artifacts/TurnOutputs.dom.test.tsx client/components/messages/SettingsMessage.dom.test.tsx — 607 通过
  • packages/sdk-typescript vitest run test/unit/DaemonClient.test.ts — 353 通过
  • packages/webui vitest run src/daemon/workspace/actions.test.ts — 19 通过

本轮无变异探针:本轮未新增任何守卫、分支或行为改动。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix review round — no code changes (PR #10024)

Head evaluated: 8f3ccb8ae6 (feat/artifact-share)

What was triaged

The workflow-prepared feedback for this round contains no new reviews, inline comments, or issue-level comments since the last evaluation (2026-08-28T17:55:00Z). The only listed items are 86 check entries, all reading Signal the reviewed fork PR: CANCELLED.

Why the CANCELLED entries are not actionable

Each claim below was checked against the full check-run history (1,749 runs) rather than assumed:

  • No failures exist. Every check run in the history has conclusion SUCCESS (26), SKIPPED (1,544), or CANCELLED (179). Zero runs have conclusion FAILURE.
  • Every cancellation is in the automation's own machinery. All 179 CANCELLED runs are the autofix loop's route (94) and Signal the reviewed fork PR (85) workflow steps — none is a build, lint, unit, or integration check for this PR's code.
  • The cancellations are concurrency artifacts, not errors. The cancelled runs lasted about one second (e.g., started 19:07:58Z, completed 19:07:59Z): they were cancelled at startup when newer runs of the same workflow superseded them, not failed mid-step.
  • The same check is green on the latest run. Signal the reviewed fork PR succeeded at 17:55:17Z and again at 19:11:02Z — after the entire 19:07–19:10 cancellation window. This round's "Still-red checks" section is empty.
  • Every code check that ran is green: Test (ubuntu-latest, Node 22.x), Serve A/B, web-shell visual capture, TruffleHog secret scan, Dependency CVE audit, precheck, Classify PR, label, Remind on force-push, the Java 11/17/21 builds, and Real daemon E2E.

These steps belong to the autofix loop's own workflow area, which this PR neither touches nor may modify; in any case no defect is evidenced. There is nothing a code change could address here.

Conclusion

There was no actionable review feedback and no code-attributable check failure this round, so no changes were made and nothing was committed. The previous round already evaluated this exact head, confirmed by execution that the earlier verification-gate rejection no longer reproduces, and answered every open inline thread (see the round report comment of 2026-08-28T19:10Z).

中文说明

Autofix 复审轮次 — 无代码变更(PR #10024)

评估的 head: 8f3ccb8ae6(feat/artifact-share)

本轮分诊内容

本轮工作流准备的反馈中,自上次评估(2026-08-28T17:55:00Z)以来没有任何新的 review、行内评论或 issue 级评论。唯一列出的条目是 86 条检查记录,全部为 Signal the reviewed fork PR: CANCELLED。

为什么这些 CANCELLED 条目不可处理

以下每一项结论都是对照完整的检查运行历史(共 1,749 次运行)核实出来的,而非假设:

  • 不存在失败。 历史记录中所有检查运行的结论为 SUCCESS(26 次)、SKIPPED(1,544 次)或 CANCELLED(179 次),没有任何一次结论为 FAILURE。
  • 所有取消都发生在自动化自身的机制中。 全部 179 次 CANCELLED 运行都是 autofix 循环的 route(94 次)与 Signal the reviewed fork PR(85 次)工作流步骤——没有一个是针对本 PR 代码的构建、lint、单元测试或集成测试检查。
  • 这些取消是并发调度的产物,而非错误。 被取消的运行只持续了约 1 秒(例如 19:07:58Z 开始、19:07:59Z 结束):它们是在启动时被同一工作流的更新运行取代而取消的,并非在步骤中途失败。
  • 同一检查在最新运行中是绿色的。 Signal the reviewed fork PR 在 17:55:17Z 成功,又在 19:11:02Z 再次成功——晚于整个 19:07–19:10 的取消窗口。本轮反馈的"持续红色检查"(Still-red checks)部分为空。
  • 所有实际运行的代码检查均为绿色: Test(ubuntu-latest,Node 22.x)、Serve A/B、web-shell 视觉截图、TruffleHog 密钥扫描、依赖 CVE 审计、precheck、Classify PR、label、强推提醒(Remind on force-push)、Java 11/17/21 构建,以及 Real daemon E2E。

这些步骤属于 autofix 循环自身的工作流领域,本 PR 既未触及也不允许修改;而且无论如何都没有证据表明存在缺陷。这里没有任何可以通过代码变更解决的问题。

结论

本轮没有可处理的评审反馈,也没有可归因于代码的检查失败,因此未做任何变更、未提交任何内容。上一轮已经评估过完全相同的 head,通过实际执行确认此前的验证门禁拒绝不再复现,并回复了所有未关闭的行内讨论串(见 2026-08-28T19:10Z 的轮次报告评论)。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Reviewed the latest feedback — no changes needed. Why, point by point: · 已审阅最新反馈——无需改动。逐点说明原因如下:

Autofix review round — no action taken

No new actionable feedback arrived for this round, so no code changes were made and nothing was committed.

Feedback triage

Item Source Classification Disposition
ic:5460387461 @qwen-code-ci-bot (issue-level comment) Informational — automated verification status notification ("Sandboxed verification is running"), not a defect claim, suggestion, or change request No action required

Round state

  • Reviews: none listed.
  • Inline comments: none listed.
  • Failed checks: none.
  • Still-red checks: none.
  • Diff growth this window: source 0 / test 0 net lines vs budgets of 400/400 — well within budget, no growth audit needed.
  • Base conflict: none reported (--conflict false); no merge performed.

The branch head remains unchanged at the last pushed commit. The pending sandboxed verification run will report its own outcome as a later comment; nothing in this round's feedback requires a code change.

中文说明

Autofix 审查轮次 —— 未采取任何操作

本轮没有收到新的可处理反馈,因此未做任何代码修改,也没有提交任何内容。

反馈分类

条目 来源 分类 处理结果
ic:5460387461 @qwen-code-ci-bot(issue 级评论) 信息性内容 —— 自动化验证状态通知("沙箱验证正在运行"),既不是缺陷报告,也不是建议或修改请求 无需处理

本轮状态

  • Review: 无。
  • 行内评论: 无。
  • 失败的检查项: 无。
  • 持续失败的检查项: 无。
  • 本窗口 diff 增长: 相对基线源码净增 0 行、测试净增 0 行,预算为 400/400 —— 远在预算之内,无需增长审计。
  • 与 base 分支的冲突: 未报告(--conflict false);未执行任何合并。

分支 head 保持不变,仍停留在上次推送的提交。正在进行的沙箱验证运行稍后会以新评论的形式报告其结果;本轮反馈中没有任何内容需要代码修改。

Base-conflict check · 基分支冲突检查: no conflict with main. · 与 main 无冲突。


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@wenshao

wenshao commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Local runtime verification (Linux, real daemon + real browser)

I built the exact submitted head (8f3ccb8ae6, merged over d6533785bd) into a real qwen serve
daemon serving the built Web Shell, and drove the whole flow from a real headless Chromium.
The feature works end to end: an HTML artifact really is installed-to, authorized-with,
connected-to, deployed, and served at a public HTTPS URL that opens without any daemon access.

Four things I would like you to look at before merging are in Findings below; the first two
matter for the remote-daemon case that motivates this feature.

Harness — what was real, what was simulated

Layer How it ran
Daemon qwen serve --port 4170 --workspace <ws> --workspace <ws2> --token …, built from the PR head, serving packages/web-shell/dist (no dev proxy)
Browser Playwright headless Chromium against http://127.0.0.1:4170, real sessions, real write_file turns producing the artifacts
Provider CLI install Real. The PR's own POST /workspace/artifact/:provider/setup ran npm install --global --prefix ~/.qwen/artifact-hosting/tools … against the public registry and installed wrangler 4.127.1, vercel 59.10.0, netlify-cli 27.4.1
Netlify authorization ticket Real. netlify login --request … --json hit the real api.netlify.com and returned a real https://app.netlify.com/authorize?... ticket; login --check polled it
Provider APIs after login Stand-in CLIs installed over the real package entry points, reproducing the argv surface and stdout shapes taken from the real shipped CLI sources
Public hosting Local HTTPS server on :443 with per-host certs from a local CA, answering the real *.pages.dev / *.vercel.app / *.netlify.app names, so the daemon's public-URL probe and the browser both make genuine HTTPS requests

Because the install stage is real, the daemon's own path resolution was validated against the real
package layouts (wrangler/bin/wrangler.js, vercel/dist/vc.js, netlify-cli/bin/run.js).
I also replayed every command this route issues against the real CLIs: none was rejected as an
unknown flag (all reached the network/auth layer), and the parsers match the shipped CLI code —
wrangler prints "Project Name" / "Project Domains" for pages project list --json, vercel prints
{projects:[{id,name,latestProductionUrl}],contextName}, netlify's --json suppresses its non-JSON
logging so deploy --json stdout is pure JSON, and wrangler's logger.log goes to stdout
(console[level]), so reading the Pages URL from stdout only is correct.

What passed

Area Result
Install stage fresh workspace → cloudflare_cli_missing → real npm install → cliInstalled: true, stage authenticate
Publish (all three providers) 200 with an HTTPS link; fetched anonymously in a clean browser context → the artifact renders
Unchanged content reused: true, deploy invocations stayed at 1 (verified from a CLI invocation trace)
force: true new deployment, reused: false
Changed content publications.<p>.upToDate: false, dialog shows "Publish new version", new deployment
Deploy OK but URL not public 502 {"code":"cloudflare_public_access_failed"} after the full retry ladder (measured 20.3 s), and the stored publication was not overwritten
artifact.share.enabled: false 403 artifact_sharing_disabled on config/setup/publish, Share button disappears in the UI, Download stays; re-enabling restores it
Non-HTML artifact CSV card shows Download / Open only
Workspace isolation qualified routes resolve ws2; ws2 publish refuses with cloudflare_not_configured (its own settings); primary route rejects a ws2 path with path_outside_workspace
Cancellation "Stop" during authorization killed the real provider CLI child in 0.05 s and returned control
Request validation unknown provider 404, bad action 400, missing path 400, bad provider 400, non-boolean force 400, ../../../etc/passwd 400, 17 MB artifact 400 artifact_too_large, no token 401
Secrets workspace settings hold only ids (account/project/site/publications) — no tokens or credentials
Tests (this sandbox) core 29, cli artifact-publish 69, cli settings+env-guard 26, sdk 353, webui 19, web-shell 607 — all pass. AppContainer.test.tsx cannot collect here (missing @qwen-code/channel-dws in my node_modules), unrelated to this PR

Findings

1. Cloudflare and Vercel authorization cannot be completed from a browser that is not on the daemon host (high).
loginProvider runs wrangler login / vercel login as a daemon child and never surfaces their output:
defaultRunCommand returns stdout only, and the return value is discarded on success. Captured from the
real CLIs on this box:

  • wrangler login → stdout: Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?...&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback
  • vercel login → stderr: Visit https://vercel.com/oauth/device?user_code=RWZH-LGCS

In the dialog the user sees "Continue in the browser to authorize Cloudflare" with a spinner, no window
opens and no URL/code is shown
(asserted: 0 popups, no http substring in the dialog body — screenshot 06),
because wantsAuthWindow is selectedProvider === 'netlify' && !authenticated. The click then blocks for up to
LOGIN_TIMEOUT_MS = 10 minutes. Cloudflare is the default-selected provider.

This is fine when the daemon runs on the user's own desktop (wrangler opens their browser itself). It is not
recoverable when the Web Shell is used remotely, and for Cloudflare it is structurally impossible even if the URL
were shown, because the OAuth callback goes to localhost:8976 on the daemon host. Vercel's device-code flow
would work from any browser if the code were surfaced. Netlify already does the right thing by returning
authorizationUrl to the client. Suggestion: return the provider's authorization URL/device code the same way for
Cloudflare and Vercel (parse it out of the CLI stream), or state in the dialog that these two require a local daemon.

2. The setup lock is process-wide, so one pending login freezes artifact sharing for every workspace (high).
setupQueue is a module-level promise chain, and the interactive login runs inside it. Measured on the two-workspace
daemon: while a Cloudflare setup was pending in ws2, a Netlify poll for ws got no response at all within 18 s
(client timeout), while the read-only publish-config route answered in 0.16 s. Combined with finding 1, a user who
clicks "Set up with Cloudflare" and cannot finish blocks every setup/authorize action on that daemon for up to
10 minutes. The publish lock is correctly per-workspace (publishQueues keyed by workspaceCwd); the setup lock is not.
Suggestion: key the setup queue by workspaceCwd (or workspace+provider), and/or don't hold it across the login wait.

3. artifact.share.netlify.dedicatedSiteId is never written on the path that creates the site, so makeSitePublic is dead code (medium).
finishAuthenticatedSetup creates the site and calls connectSite(site.id, …, site) with knownSite set, so
createdHere stays false and the marker write is skipped. Runtime evidence from a full Netlify setup:
settings ended as "netlify": {"siteId": "site-…"} with no dedicatedSiteId, and api updateSite was never invoked
during publish. A/B on the same state: adding dedicatedSiteId by hand → updateSite invocations 0 → 1.
So the "publish relaxes password/SSO protection on the site this flow created" behaviour never runs for the site this
flow created — exactly the case the marker exists for. The tests only ever pre-seed the marker; mutating
let createdHere = false → true is caught only by the adoption test ("migrates an empty linked project"), so no
test pins the create path.

4. Each publish returns a new immutable URL, but the dialog says the link is updated (medium-low).
parsePublishedUrl prefers deploy_ssl_url / deploy_url, and the Cloudflare regex takes the first
*.pages.dev match, which is the per-deployment host — so all three providers return a per-deployment URL rather than
the stable production alias (https://<project>.pages.dev, netlify's url / siteUrl, Vercel's production alias).
Verified: after "Publish new version", the previously shared v1 link still returns 200 with the old content, while
the dialog text reads "Publish a new version to update the public link". Anyone holding the earlier link silently keeps
the old version. Immutable per-version links are a defensible choice — but then the copy should say so, and it may be
worth showing the stable alias next to it.

5. No test pins "content changed → deploy again" (low, test-only).
Mutating the reuse condition existing?.contentHash === contentHash → existing !== undefined (i.e. always reuse the
recorded link, even for changed content) leaves all 69 tests in workspace-artifact-publish.test.ts green. There are
tests for identical-content reuse and for force: true, and one for changing accounts, but none that changes the
file and asserts reused: false. Runtime behaviour is correct; the guard is just unprotected. Other mutants were
killed (dropping the artifact.share.enabled gate, skipping the public-URL check, ungating makeSitePublic).

Smaller notes

  • Setting up all three providers left 1.6 GB under ~/.qwen/artifact-hosting/ (wrangler 251 MB, vercel 322 MB,
    netlify-cli 383 MB, plus a 656 MB npm cache that is never pruned). The Details panel says "installs it on this machine"
    with no size hint, and the cache directory is private to this feature.
  • An HTML artifact whose file changed outside the agent (status: 'changed') loses Share as well as Download, since
    canShareArtifact builds on canDownloadArtifact. Consistent with Download, but it means the "file changed → publish a
    new version" path only exists when the change went through the agent.
  • The PR description calls the lock "workspace-wide"; that is true of the publish lock and not of the setup lock (finding 2).
  • Not a PR issue, recorded so nobody re-derives it: under vite dev the client abort behind the dev proxy does not
    reach the daemon, so "Stop" appears not to kill the CLI child. Against the daemon-served build it does (0.05 s).

Screenshots

Full set: assets/pr10024-validation.

artifact card share dialog
HTML artifact card exposes Share Share dialog, Cloudflare ready, Details expanded
public link stale
the published link opened anonymously, no daemon artifact changed after publish → "Publish new version"
cloudflare authorizing netlify authorize
finding 1 — "continue in the browser", but no window and no URL Netlify opens the real authorization page from the dialog
sharing disabled non-html
Artifact sharing off → Share gone, Download kept non-HTML artifact never offers Share
中文版

本地真实环境验证(Linux,真实 daemon + 真实浏览器)

我把提交的 head(8f3ccb8ae6,基于 d6533785bd)构建成真实的 qwen serve daemon,由它直接托管构建后的 Web Shell,
并用真实 headless Chromium 走完整流程。功能整体可用:HTML Artifact 能真正完成安装 CLI、授权、连接项目、部署,
并得到一个不依赖 daemon 就能打开的公开 HTTPS 链接。

合并前建议关注下面 问题 的前两条,它们影响这个功能最主要的远程 daemon 场景。

环境说明:哪些是真的,哪些是替身

层 运行方式
Daemon 由 PR head 构建,qwen serve --port 4170 --workspace ws --workspace ws2 --token …,直接托管 packages/web-shell/dist(不经 dev proxy)
浏览器 Playwright headless Chromium 访问 http://127.0.0.1:4170,真实会话、真实 write_file 产生 Artifact
平台 CLI 安装 真实。由 PR 自己的 POST /workspace/artifact/:provider/setup 执行 npm install --global --prefix ~/.qwen/artifact-hosting/tools …,从公共 registry 装上 wrangler 4.127.1、vercel 59.10.0、netlify-cli 27.4.1
Netlify 授权票据 真实。netlify login --request … --json 请求真实 api.netlify.com,返回真实的 https://app.netlify.com/authorize?...;login --check 也是真实轮询
登录之后的平台 API 用替身 CLI 覆盖真实包的入口文件,argv 与 stdout 形状均照抄真实 CLI 源码
公开托管 本地 :443 HTTPS 服务 + 本地 CA 逐域名签发证书,直接应答真实的 *.pages.dev / *.vercel.app / *.netlify.app 域名,因此 daemon 的公开链接探测和浏览器访问都是真实 HTTPS 请求

因为安装阶段是真实的,daemon 的路径解析也就针对真实包结构做了验证(wrangler/bin/wrangler.js、vercel/dist/vc.js、
netlify-cli/bin/run.js)。我还把这个路由会发出的每条命令都在真实 CLI 上重放了一遍:没有任何一条因为未知参数被拒绝
(都走到了网络/鉴权层),解析格式也与真实 CLI 源码一致——wrangler 的 pages project list --json 输出 "Project Name" /
"Project Domains",vercel 输出 {projects:[{id,name,latestProductionUrl}],contextName},netlify 的 --json 会屏蔽普通日志
所以 deploy --json 的 stdout 是纯 JSON,wrangler 的 logger.log 走的是 stdout(console[level]),因此只读 stdout 取
Pages 链接是正确的。

通过的验证

项 结果
安装阶段 全新工作区 cloudflare_cli_missing → 真实 npm 安装 → cliInstalled: true,阶段变为 authenticate
发布(三个平台) 200 并返回 HTTPS 链接;在干净浏览器上下文中匿名打开可正常渲染
内容未变 reused: true,部署调用次数保持 1(由 CLI 调用追踪确认)
force: true 重新部署,reused: false
内容变化 publications.<p>.upToDate: false,界面显示「发布新版本」,产生新部署
部署成功但链接不公开 走完重试阶梯后返回 502 {"code":"cloudflare_public_access_failed"}(实测 20.3 秒),且已保存的发布记录没有被覆盖
artifact.share.enabled: false config/setup/publish 均 403 artifact_sharing_disabled,界面「分享」消失而「下载」保留;重新打开后恢复
非 HTML Artifact CSV 卡片只有下载 / 打开
工作区隔离 qualified 路由正确解析 ws2;ws2 未配置时发布返回 cloudflare_not_configured;主工作区路由拒绝 ws2 路径(path_outside_workspace)
取消 授权过程中点「中止」,真实平台 CLI 子进程 0.05 秒被杀掉并交还控制权
参数校验 未知平台 404、错误 action 400、缺 path 400、错误 provider 400、force 非布尔 400、../../../etc/passwd 400、17 MB 文件 400 artifact_too_large、无 token 401
凭据 工作区设置里只有各种 id(账号/项目/站点/发布记录),没有 token 或任何凭据
测试(本机) core 29、cli artifact-publish 69、cli settings+env-guard 26、sdk 353、webui 19、web-shell 607 全部通过。AppContainer.test.tsx 在本机无法收集(我的 node_modules 缺 @qwen-code/channel-dws),与本 PR 无关

问题

1. 当浏览器不在 daemon 所在机器上时,Cloudflare 和 Vercel 无法完成授权(高)。
loginProvider 以子进程方式执行 wrangler login / vercel login,但从不把它们的输出交还给前端:
defaultRunCommand 只返回 stdout,而成功时返回值被丢弃。本机真实 CLI 的输出为:

  • wrangler login → stdout:Opening a link in your default browser: https://dash.cloudflare.com/oauth2/auth?...&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback
  • vercel login → stderr:Visit https://vercel.com/oauth/device?user_code=RWZH-LGCS

而对话框只显示「Continue in the browser to authorize Cloudflare」加一个转圈,不会打开任何窗口,也不显示任何 URL 或 code
(已断言:弹出页面数 0,对话框正文不含 http,见截图 06),因为 wantsAuthWindow 的条件是
selectedProvider === 'netlify' && !authenticated。这次点击随后会阻塞最长 LOGIN_TIMEOUT_MS = 10 分钟。而 Cloudflare 恰好是默认选中的平台。

daemon 跑在用户自己电脑上时没问题(wrangler 会自己打开浏览器)。但远程使用 Web Shell 时无法恢复;而且对 Cloudflare 来说,
即便把 URL 显示出来也仍然走不通,因为 OAuth 回调指向的是 daemon 主机上的 localhost:8976。Vercel 的 device code 流程
只要把 code 显示出来,任何浏览器都能完成。Netlify 已经用 authorizationUrl 做对了。建议:让 Cloudflare / Vercel 也把授权 URL
或 device code 返回给前端(从 CLI 输出中解析),或者在对话框中说明这两个平台需要本机 daemon。

2. 配置锁是进程级的,一个挂起的登录会冻结所有工作区的分享配置(高)。
setupQueue 是模块级 promise 链,交互式 login 就在锁内执行。在注册了两个工作区的 daemon 上实测:ws2 的 Cloudflare 配置
挂起期间,ws 的 Netlify poll 在 18 秒内完全没有响应(客户端超时),而只读的 publish-config 0.16 秒就返回。结合问题 1,
一个用户点了「配置 Cloudflare」又完不成,就会让这个 daemon 上所有配置/授权操作阻塞最长 10 分钟。发布锁是按工作区隔离的
(publishQueues 以 workspaceCwd 为键),配置锁没有。建议按 workspaceCwd(或工作区+平台)分锁,并且不要在等待登录时持锁。

3. 创建站点的那条路径从不写入 artifact.share.netlify.dedicatedSiteId,导致 makeSitePublic 成为死代码(中)。
finishAuthenticatedSetup 创建站点后调用 connectSite(site.id, …, site) 并传入 knownSite,于是 createdHere 一直是 false,
标记写入被跳过。完整 Netlify 配置后的实测:设置里只有 "netlify": {"siteId": "site-…"},没有 dedicatedSiteId;发布过程中
api updateSite 一次都没有被调用。同一状态下做 A/B:手工补上 dedicatedSiteId 后,updateSite 调用次数由 0 变 1。
也就是说「发布时放开本流程所创建站点的密码/SSO 保护」这个行为,恰恰在本流程创建的站点上永远不会发生。测试里只出现过预置
该标记的用例;把 let createdHere = false 改成 true 只会让「adoption」用例(migrates an empty linked project)失败,
没有任何用例覆盖创建路径。

4. 每次发布都会产生一个新的不可变链接,但界面说的是「更新公开链接」(中低)。
parsePublishedUrl 优先取 deploy_ssl_url / deploy_url,Cloudflare 的正则取的是第一个 *.pages.dev 匹配,也就是单次部署的
域名——三个平台返回的都是单次部署链接,而不是稳定的生产别名(https://<project>.pages.dev、netlify 的 url / siteUrl、
Vercel 的生产别名)。实测:发布新版本后,之前分享出去的 v1 链接仍然 200 且返回旧内容,而对话框写的是
「Publish a new version to update the public link」。拿到旧链接的人不会看到更新。按版本不可变的链接是合理的设计选择,
但文案应当照此说明,也可以考虑同时展示稳定别名。

5. 没有测试锁定「内容变化 → 必须重新部署」(低,仅测试)。
把复用条件 existing?.contentHash === contentHash 改成 existing !== undefined(即内容变了也复用旧链接),
workspace-artifact-publish.test.ts 的 69 个用例仍然全绿。现有用例覆盖了「内容相同则复用」和 force: true,
还有一个「切换账号」的用例,但没有「改文件后断言 reused: false」。运行时行为是对的,只是缺少测试保护。
其他变异都被杀掉了(去掉 artifact.share.enabled 网关、跳过公开链接检查、去掉 makeSitePublic 的标记判断)。

其他小点

  • 配置完三个平台后,~/.qwen/artifact-hosting/ 占用 1.6 GB(wrangler 251 MB、vercel 322 MB、netlify-cli 383 MB,
    外加 656 MB 且从不清理的 npm 缓存)。「详细信息」里只说「会安装到本机」,没有体积提示,缓存目录也是这个功能独有的。
  • 如果 HTML Artifact 的文件在 agent 之外被修改(status: 'changed'),它会同时失去「分享」和「下载」,因为
    canShareArtifact 建立在 canDownloadArtifact 之上。与下载保持一致是合理的,但这意味着「文件变了 → 发布新版本」
    只有在改动经由 agent 完成时才走得通。
  • PR 描述把锁称为「workspace-wide」:发布锁确实如此,配置锁不是(见问题 2)。
  • 与 PR 无关,仅记录以免他人重复排查:在 vite dev 下,客户端 abort 不会穿过 dev proxy 传到 daemon,所以「中止」看起来
    没杀掉 CLI 子进程;直接访问 daemon 托管的构建产物时是正常的(0.05 秒)。

截图

完整截图见 assets/pr10024-validation 分支。

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix ran out of time before finishing (timeout (3600000ms)) (attempt 4/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:
Qwen failed during address-review: timeout (3600000ms).

See the Qwen Autofix agent step logs for model/tool output.

中文说明

🤖 AutoFix 在完成前耗尽了时间(timeout (3600000ms))(第 4/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33237036539


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix hit a verification-gate error before reaching a verdict (attempt 5/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Autofix review round — PR #10024

Feedback addressed: issue-level comment ic:5460641100 (@wenshao's local runtime verification). All five findings are addressed in commit 1c8fab8db5; each code change has a mutation-probed test witness.

Findings

1. Cloudflare/Vercel authorization cannot be completed from a remote browser (high) — addressed via the dialog copy remedy. The report offered two remedies: parse the authorization URL/device code out of the provider CLI stream and surface it like Netlify's authorizationUrl, or state in the dialog that these providers require a browser on the daemon host. This round implements the second (smaller) one: at the authenticate stage the dialog now says, for Cloudflare/Vercel only, that the provider completes authorization in a browser on the machine where Qwen Code runs, that the page updates automatically once the provider confirms, and that Netlify can authorize from the current browser. Cloudflare's OAuth callback targets localhost on the daemon host, so even a surfaced URL would not be remotely completable; Vercel's device-code flow would be, but surfacing it requires restructuring the blocking wrangler login/vercel login call into a background ticket flow like Netlify's beginLogin/pollLogin — deliberately not attempted in this budget-constrained round. The click remains recoverable via Stop, which kills the provider CLI child (verified in the report at 0.05 s).

**2. Process-wide setup lock froze every

中文说明

🤖 AutoFix 在得出结论之前遇到验证门错误(第 5/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33244388739


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

@qqqys qqqys closed this Aug 29, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 AutoFix hit a verification-gate error before reaching a verdict (attempt 6/100) — it will retry on the next scan.

⚠️ This change was NOT pushed — any commit referenced below was made only in the runner workspace and has been discarded. What the agent reported:

Round summary — PR #10024 artifact sharing (maintainer runtime-verification feedback)

All five findings from @wenshao's local runtime verification comment are addressed in one commit (a620572d93). No conflict resolution was requested (--conflict false) and none was performed; the branch stays on its current head merged over main.

Findings and dispositions

1. (high) Cloudflare/Vercel authorization cannot be completed from a browser that is not on the daemon host — Acted (the reviewer's minimal option).
Verified against the code: loginProvider runs wrangler login / vercel login as daemon children and discards their output; only Netlify returns an authorizationUrl to the client; the dialog's auth-window logic is Netlify-only; and the authenticate-stage copy claimed "Continue in the browser to authorize …" for every provider while nothing opens for Cloudflare/Vercel.
Change: the share dialog now uses provider-aware copy. For Cloudflare and Vercel the status line and the storage note state that the provider CLI opens its authorization page in a browser on the machine running Qwen Code, that the user should finish it there, and that the dialog updates when authorization completes (new i18n keys share.authorizeStatus.daemonHosted and share.storageNote.daemonHosted, en + zh). Netlify keeps its existing wording because its flow genuinely opens the authorization page from the user's browser.
Follow-up, deliberately not in this round: the reviewer

中文说明

🤖 AutoFix 在得出结论之前遇到验证门错误(第 6/100 次尝试)—— 将在下次扫描时重试。

Run log: https://github.com/QwenLM/qwen-code/actions/runs/33251275125


🧠 Handled by Qwen Code · model/模型 qwen3.8-max

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants