Skip to content

feat(cli): enable dynamic workflows from a settings key - #9098

Merged
wenshao merged 8 commits into
QwenLM:mainfrom
qqqys:feat/workflow-settings-key
Aug 23, 2026
Merged

feat(cli): enable dynamic workflows from a settings key#9098
wenshao merged 8 commits into
QwenLM:mainfrom
qqqys:feat/workflow-settings-key

Conversation

@qqqys

@qqqys qqqys commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Adds a tools.workflowsEnabled setting that actually turns dynamic workflows on, wires it into the config field that has been waiting for it, and scopes the opt-in so that only the user — never a repository — can grant it. Until now the only way to enable the feature was an undocumented environment variable.

Why it's needed

ConfigParameters.workflowsEnabled is declared, defaulted in the Config constructor, and read by Config.isWorkflowsEnabled() — but nothing in packages/cli ever writes it. loadCliConfig populates dozens of neighbouring fields from settings and simply skips this one, so params.workflowsEnabled is always undefined and the field always falls back to false. AGENTS.md names this exact shape in its review guidance: an optional field that is declared and read but never set by any caller is a dead switch.

The practical effect is that dynamic workflows can only be reached by exporting QWEN_CODE_ENABLE_WORKFLOWS=1, which is undocumented, has to be repeated in every shell that launches qwen, and cannot be discovered from the settings dialog. A user reading the settings surface has no way to learn the feature exists.

This adds the setting and populates the field from it. Precedence is unchanged and still resolved entirely in core: QWEN_CODE_DISABLE_WORKFLOWS beats everything, then QWEN_CODE_ENABLE_WORKFLOWS, then the setting.

Who may grant the opt-in

A workflow run can dispatch many subagents against the user's token budget, so enabling it is a capability grant, not a preference. It must come from the user or their operator, never from whatever repository happens to be open. Three changes enforce that:

  • tools.workflowsEnabled is stripped from Workspace scope during the merge, joining security.allowPrivateNetworkHooks and security.allowedInsecureVoiceBaseUrls in the existing stripWorkspaceRestrictedSettings mechanism (renamed from stripWorkspaceSecurityBypasses, since the concern is no longer only network security). User, System and SystemDefaults scopes are unaffected, so an operator can still force the feature on or off fleet-wide.
  • A setting that appears in workspace scope and is ignored now produces a startup warning naming the file, so the user is not left wondering why their setting did nothing.
  • QWEN_CODE_ENABLE_WORKFLOWS and QWEN_CODE_DISABLE_WORKFLOWS join PROJECT_ENV_HARDCODED_EXCLUSIONS, so a project .env or settings.env cannot enable the feature or override a user's opt-out through the environment either. Closing the settings path while leaving the env path open would not have been worth much.

Why it requires a restart

requiresRestart: true is load-bearing rather than decorative. The Workflow tool is registered once while the tool registry is built, /workflows is gated when the builtin commands load, and keyword steering reads the same startup-built Config on each submission. A live toggle would leave the settings dialog reporting the feature as on while the tool is absent from the registry and the command is missing from typeahead — worse than requiring a restart.

The description also disambiguates this key from the unrelated experimental.sessionWorkflow ("Session Workflow Plan & Review") and points at ui.disableWorkflowKeywordTrigger. Three settings now share the word "workflow" and they control entirely different things, so the dialog needs to say which is which.

Reviewer Test Plan

How to verify

cd packages/cli && npx vitest run src/config/config.test.ts src/config/settingsSchema.test.ts src/config/settings.test.ts src/services/BuiltinCommandLoader.test.ts — 555 passed.

Coverage added:

  • settingsSchema.test.ts pins the key's type, false default, requiresRestart, showInDialog and category.
  • config.test.ts adds a loadCliConfig workflowsEnabled block: default-off, the setting turning it on, the QWEN_CODE_DISABLE_WORKFLOWS kill switch overriding a true setting, and QWEN_CODE_ENABLE_WORKFLOWS overriding a false one.
  • settings.test.ts covers the scope rules: a workspace-scope value is stripped and warned about, while User/System/SystemDefaults values survive the merge.
  • BuiltinCommandLoader.test.ts covers /workflows appearing only when the feature resolves on.

The second config.test.ts case is the regression guard for the bug being fixed: reverting only the one-line change in packages/cli/src/config/config.ts and re-running that block fails exactly should be enabled when workflowsEnabled is set to true in settings, and the other three still pass — which is precisely why the dead switch went unnoticed. The core-side precedence tests in config.workflows.test.ts already passed against the dead field, since they inject workflowsEnabled through ConfigParameters directly.

End to end: add { "tools": { "workflowsEnabled": true } } to your user settings.json, restart, and confirm /workflows is available and the Workflow tool is registered. Put the same key in a workspace .qwen/settings.json instead and confirm it is ignored and warned about. With QWEN_CODE_DISABLE_WORKFLOWS=1 set, neither enables it.

packages/vscode-ide-companion/schemas/settings.schema.json is regenerated by the build from the schema definition; that hunk is generated output, not a hand edit.

Evidence (Before & After)

Before: no workflow entry exists in the settings dialog, and tools.workflowsEnabled in settings.json has no effect at any scope — the only way in is QWEN_CODE_ENABLE_WORKFLOWS=1.

After: "Dynamic Workflows" appears under Tools in the settings dialog, defaulting to off, and setting it from user scope enables the feature on the next launch. From workspace scope it is ignored, with a warning naming the file.

Tested on

OS Status
🍏 macOS N/A
🪟 Windows N/A
🐧 Linux

Environment (optional)

Unit tests, Node 22.23.0 on Linux.

Risk & Scope

  • Main risk or tradeoff: settings/env precedence is the only real hazard, and the kill switch must keep winning over an explicitly true setting. All four combinations are pinned by test. The default stays false, so no existing installation changes behaviour.
  • Not validated / out of scope: this makes the existing gate reachable and decides who may open it. It does not change what the Workflow tool does, does not add a size guideline or a pre-use consent prompt, and adds no startup tip — TipContext carries no Config, so a tip could not tell whether the user had already enabled the feature and would keep advising them to.
  • Breaking changes / migration notes: none. The setting is new in this PR, so the workspace-scope restriction cannot invalidate an existing configuration. The rename of stripWorkspaceSecurityBypasses to stripWorkspaceRestrictedSettings is internal to settings.ts.

Linked Issues

None.

中文说明

这个 PR 做了什么

新增一个真正能打开 dynamic workflows 的 tools.workflowsEnabled 设置项,把它接到那个一直在等待它的 config 字段上,并且限定这项开关只能由用户授予、而不能由仓库授予。在此之前,启用该功能的唯一途径是一个没有文档的环境变量。

为什么需要

ConfigParameters.workflowsEnabled 已经声明、在 Config 构造函数里有默认值、并且被 Config.isWorkflowsEnabled() 读取——但 packages/cli 里没有任何地方写入过它。loadCliConfig 从 settings 填充了几十个相邻字段,唯独跳过了这一个,所以 params.workflowsEnabled 永远是 undefined,该字段永远回落到 false。AGENTS.md 的评审指引里恰好点名了这种形态:一个被声明、被读取、却从未被任何调用方设置的可选字段,就是一个死开关。

实际后果是,dynamic workflows 只能通过 export QWEN_CODE_ENABLE_WORKFLOWS=1 触达——这个变量没有文档,需要在每个启动 qwen 的 shell 里重复设置,而且无法从设置对话框里发现。一个只看设置界面的用户根本无从得知这个功能的存在。

本 PR 新增该设置项并用它填充字段。优先级保持不变,且仍然完全在 core 内解析:QWEN_CODE_DISABLE_WORKFLOWS 优先于一切,其次是 QWEN_CODE_ENABLE_WORKFLOWS,最后才是设置项。

谁有权授予这项开关

一次 workflow 运行可以派发大量 subagent、消耗用户的 token 预算,因此"启用"是一次能力授予,而不是一项偏好设置。它必须来自用户或其运维方,绝不能来自恰好被打开的那个仓库。三处改动落实了这一点:

  • tools.workflowsEnabled 在合并阶段会被从 Workspace 作用域剥离,与 security.allowPrivateNetworkHookssecurity.allowedInsecureVoiceBaseUrls 一起纳入既有的 stripWorkspaceRestrictedSettings 机制(由 stripWorkspaceSecurityBypasses 更名而来,因为关注点已不只是网络安全)。User、System、SystemDefaults 作用域不受影响,运维方仍可全机群强制开启或关闭。
  • 当该设置出现在 workspace 作用域并被忽略时,启动时会给出一条指明文件路径的警告,用户不必困惑于"我明明设置了却没生效"。
  • QWEN_CODE_ENABLE_WORKFLOWSQWEN_CODE_DISABLE_WORKFLOWS 加入 PROJECT_ENV_HARDCODED_EXCLUSIONS,因此项目的 .envsettings.env 也无法通过环境变量启用该功能或覆盖用户的关闭决定。只堵住 settings 路径而放着 env 路径不管,意义有限。

为什么需要重启

requiresRestart: true 是有实际作用的,而非装饰。Workflow 工具在构建工具注册表时只注册一次,/workflows 在内置命令加载时就已确定是否可用,关键词引导每次提交都读取同一个启动时构建的 Config。一个可即时生效的开关会导致设置对话框显示功能已开启,而工具其实并不在注册表里、命令也不在候选补全中——这比要求重启更糟。

描述文案还把这个键与不相关的 experimental.sessionWorkflow("Session Workflow Plan & Review")区分开,并指向 ui.disableWorkflowKeywordTrigger。现在有三个设置项都带 "workflow" 这个词,控制的却是完全不同的东西,所以对话框里必须说清楚谁是谁。

审阅者验证方案

如何验证

cd packages/cli && npx vitest run src/config/config.test.ts src/config/settingsSchema.test.ts src/config/settings.test.ts src/services/BuiltinCommandLoader.test.ts——555 项通过。

新增覆盖:

  • settingsSchema.test.ts 固定该键的类型、false 默认值、requiresRestartshowInDialog 与分类。
  • config.test.ts 新增 loadCliConfig workflowsEnabled 块:默认关闭、设置项开启、QWEN_CODE_DISABLE_WORKFLOWS 覆盖 true 设置、QWEN_CODE_ENABLE_WORKFLOWS 覆盖 false 设置。
  • settings.test.ts 覆盖作用域规则:workspace 作用域的值被剥离并给出警告,而 User/System/SystemDefaults 的值能通过合并保留。
  • BuiltinCommandLoader.test.ts 覆盖 /workflows 仅在功能解析为开启时出现。

config.test.ts 中的第二个用例是本次修复的回归护栏:只回退 packages/cli/src/config/config.ts 里那一行改动并重跑该测试块,恰好只有 should be enabled when workflowsEnabled is set to true in settings 失败,另外三个仍然通过——这也正是这个死开关一直没被发现的原因。config.workflows.test.ts 里 core 侧的优先级测试在字段还是死的时候就已经全部通过,因为它们是直接通过 ConfigParameters 注入 workflowsEnabled 的。

端到端验证:在用户级 settings.json 中加入 { "tools": { "workflowsEnabled": true } },重启,确认 /workflows 可用且 Workflow 工具已注册。改为放进 workspace 的 .qwen/settings.json,确认它被忽略并给出警告。设置 QWEN_CODE_DISABLE_WORKFLOWS=1 后,两者都无法启用。

packages/vscode-ide-companion/schemas/settings.schema.json 是构建过程根据 schema 定义重新生成的,那一段属于生成产物,不是手工编辑。

证据(前后对比)

修复前:设置对话框中不存在任何 workflow 条目,settings.json 里的 tools.workflowsEnabled 在任何作用域下都不起作用——唯一入口是 QWEN_CODE_ENABLE_WORKFLOWS=1

修复后:"Dynamic Workflows" 出现在设置对话框的 Tools 分类下,默认关闭;从用户作用域设置后在下次启动生效。从 workspace 作用域设置则被忽略,并给出指明文件的警告。

测试环境

OS Status
🍏 macOS N/A
🪟 Windows N/A
🐧 Linux

环境(可选)

单元测试,Linux 上的 Node 22.23.0。

风险与范围

  • 主要风险或取舍:settings 与环境变量的优先级是唯一真正的风险点,kill switch 必须始终压过一个显式为 true 的设置。四种组合均已被测试固定。默认值仍为 false,因此任何现有安装的行为都不会改变。
  • 未验证 / 不在范围内:本 PR 让既有的开关变得可触达,并决定了谁有权打开它。它不改变 Workflow 工具的行为,不新增规模指引或使用前的授权提示,也不添加启动提示——TipContext 里没有 Config,提示无法判断用户是否已经开启该功能,只会不停地劝已经开启的人去开启。
  • 破坏性变更 / 迁移说明:无。该设置项是本 PR 新增的,因此 workspace 作用域的限制不可能使任何既有配置失效。stripWorkspaceSecurityBypasses 更名为 stripWorkspaceRestrictedSettings 属于 settings.ts 内部改动。

关联 Issue

无。

`ConfigParameters.workflowsEnabled` is declared, defaulted, and read by
`Config.isWorkflowsEnabled()` — but `loadCliConfig` never writes it, so no
setting has ever reached it. The only way to turn dynamic workflows on is
the undocumented `QWEN_CODE_ENABLE_WORKFLOWS=1`, which has to be exported
in every shell that launches qwen. AGENTS.md names this shape directly: an
optional field that is declared and read but never set by any caller is a
dead switch.

Add `tools.workflowsEnabled` to the settings schema and populate the field
from it. Precedence is unchanged and still resolved in core:
`QWEN_CODE_DISABLE_WORKFLOWS` beats everything, then
`QWEN_CODE_ENABLE_WORKFLOWS`, then the setting. Because `settings.merged`
already folds the System scope, an operator gets a fleet-wide force-off
with no extra code.

`requiresRestart` is load-bearing rather than decorative: the Workflow tool
is registered once while the tool registry is built, `/workflows` is gated
when commands load, and keyword steering resolves at startup — so a
mid-session toggle would leave the dialog claiming the feature is on while
the tool is absent from the registry.

The setting description also disambiguates it from the unrelated
`experimental.sessionWorkflow` plan-and-review view, which shares the word
"workflow" and would otherwise be easy to confuse in the settings dialog.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the PR — re-running triage on the current head, which has grown well beyond the originally reviewed wiring commit.

Template looks good ✓

Problem: observed, not theoretical — verified in the tree. ConfigParameters.workflowsEnabled is declared, defaulted in the Config constructor, and read by isWorkflowsEnabled() in core, but nothing in packages/cli ever writes it, so the field always falls back to false and the only way in today is the undocumented QWEN_CODE_ENABLE_WORKFLOWS=1. AGENTS.md's review guidance names this exact shape: an optional field that is declared and read but never set by any caller is a dead switch.

Direction: aligned, and the expanded scope holds together. The first commit wires the setting; everything after answers one question that wiring creates — who may grant the opt-in. A workflow run can dispatch many subagents against the user's token budget, so enabling it is a capability grant, and the PR scopes it to user/system using the repo's own precedent (security.allowPrivateNetworkHooks and security.allowedInsecureVoiceBaseUrls are stripped from workspace scope by this same mechanism). Default stays false, so this is purely additive. Claude Code's CHANGELOG shows sustained investment in dynamic workflows — no direct counterpart to this wiring fix, but the area is clearly active.

Size: core paths touched (packages/cli/src/config/**, packages/cli/src/services/**) — 192 production lines, 408 test lines, 5 generated (settings.schema.json), 3 docs. Under every threshold; no maintainer escalation on size.

Approach: coherent, nothing drive-by. Each added surface closes the same door: the merge strip + startup warning (settings files), the dialog filter (TUI), PROJECT_ENV_HARDCODED_EXCLUSIONS (project .env / settings.env), and the two daemon POST routes (serve API). The WORKSPACE_RESTRICTED_SETTINGS single list replaces two hand-maintained copies so the four surfaces cannot drift apart — the right call when adding a third key. The requiresRestart: true rationale checks out: tool registration and /workflows gating both happen at startup, so a live toggle would be a lying switch. The two doc gaps flagged in the previous triage round are now fixed in-diff.

Risk: no elevated risk signals (no high-risk paths matched).

Moving on to code review. 🔍

中文说明

感谢贡献——本次是对当前 head 的重新 triage,PR 的范围已远超最初审查的接线 commit。

模板完整 ✓

问题:已观测到,而非理论性问题——已在代码树中核实。ConfigParameters.workflowsEnabled 已声明、在 Config 构造函数中有默认值、被 core 的 isWorkflowsEnabled() 读取,但 packages/cli 中没有任何地方写入它,所以该字段永远回落到 false,目前唯一入口是未写入文档的 QWEN_CODE_ENABLE_WORKFLOWS=1。AGENTS.md 的评审指引恰好点名了这种形态:一个被声明、被读取、却从未被任何调用方设置的可选字段就是一个死开关。

方向:对齐,且扩展后的范围自洽。第一个 commit 完成接线;之后的所有内容都在回答接线所引出的一个问题——谁有权授予这项开关。一次 workflow 运行可以派发大量子智能体、消耗用户的 token 预算,因此启用它是一次能力授予;本 PR 沿用仓库自身的先例将其限定在 user/system 作用域(security.allowPrivateNetworkHookssecurity.allowedInsecureVoiceBaseUrls 正是通过同一机制从 workspace 作用域剥离的)。默认值保持 false,纯增量改动。Claude Code 的 CHANGELOG 显示 dynamic workflows 方向持续有投入——虽无与本接线修复直接对应的条目,但该领域明显活跃。

规模:触及核心路径(packages/cli/src/config/**packages/cli/src/services/**)——生产代码 192 行、测试 408 行、生成产物 5 行(settings.schema.json)、文档 3 行。低于所有阈值,无需因规模升级至维护者。

方案:自洽,无夹带改动。每个新增表面都在关闭同一扇门:merge 剥离 + 启动警告(settings 文件)、对话框过滤(TUI)、PROJECT_ENV_HARDCODED_EXCLUSIONS(项目 .env / settings.env)、以及两条 daemon POST 路由(serve API)。WORKSPACE_RESTRICTED_SETTINGS 单一清单取代了两份手工维护的拷贝,使四个表面不会互相漂移——在新增第三个键时这是正确的做法。requiresRestart: true 的理由成立:工具注册和 /workflows 门控都发生在启动阶段,即时生效的开关只会是说谎的开关。上一轮 triage 指出的两处文档空缺已在本 diff 中修复。

风险:无升级风险信号(未命中任何高风险路径)。

进入代码审查 🔍

Qwen Code · qwen3.8-max

Reviewed at 0e01e7aada10c34d48bff8d8cfa61dc44679f299 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Code review

Baseline first, written before reading the diff: wire settings.tools?.workflowsEnabled into the ConfigParameters literal in loadCliConfig (the single chokepoint every entry path shares), add a schema entry with requiresRestart: true, regenerate the vscode JSON, and pin precedence in tests. The PR's first commit is exactly that; the rest of the diff answers the scope-ownership question that wiring opens. I verified each piece against the tree at the reviewed commit:

  • Wiring & precedence. The one line lands beside useRipgrep/useBuiltinRipgrep; all production entry paths (TUI + headless via gemini.tsx, both ACP paths via acpAgent.ts) build Config through loadCliConfig. Precedence stays in core, untouched: isWorkflowsEnabled() checks QWEN_CODE_DISABLE_WORKFLOWSQWEN_CODE_ENABLE_WORKFLOWS → the field, and the new config.test.ts block pins all four combinations — including the kill switch beating an explicit true setting.
  • One list, four surfaces. WORKSPACE_RESTRICTED_SETTINGS in settingsUtils.ts drives the merge strip, the startup warning, the dialog filter, and the daemon guard — no second hand-maintained copy remains. The placement avoids a runtime import cycle (settingsUtils.ts imports settings.js type-only). The rewritten stripWorkspaceRestrictedSettings behaves identically for the two pre-existing security keys and returns the input unchanged when nothing is restricted; the warning is emitted only when the raw workspace file carries the key.
  • Daemon routes. Both write paths are guarded after scope/key validation: POST /workspace/settings (workspace+user scopes) and POST /workspaces/:workspace/settings (workspace scope only, trusted active runtime required first). Both answer 400 with a stable workspace_restricted_setting code, and a user-scope write of the same key still succeeds (pinned by test). I checked the other serve routes — voice/model setup persist fixed key sets, so no other path can write an arbitrary key at workspace scope.
  • Env path. Both workflow vars join PROJECT_ENV_HARDCODED_EXCLUSIONS: a project .env can no longer enable the feature or override a user's opt-out, while home-scoped .env files (~/.qwen/.env, ~/.env) still apply — the same semantics as QWEN_TLS_INSECURE and the other hardcoded exclusions.
  • Dialog. With Workspace scope selected, restricted keys drop out of getDialogSettingKeys; every toggle/edit/save path in the dialog derives from that enumeration, so no workspace write of a restricted key is reachable through the TUI.

No critical issues. The six items the /review round 10 deferred were each re-checked against the tree and remain non-blocking: the restricted list's leaf keys aren't schema-type-checked (failure mode is a harmless no-op strip), the save path doesn't re-filter (unreachable — enumeration is filtered upstream), the dialog call site lacks its own test, a user-scope settings.json env opt-in is blocked alongside project ones (consistent with every sibling exclusion; the settings key itself is the first-class path), the served GET can show a pre-existing dead workspace value next to a correct effective, and the schema description omits the workspace-scope caveat its two siblings carry. All follow-up material.

Files changed (17)
File What changed
packages/cli/src/config/config.ts The one-line wiring: settings value into ConfigParameters
packages/cli/src/config/settingsSchema.ts New tools.workflowsEnabled entry: boolean, default false, requiresRestart, shown in dialog
packages/cli/src/config/settings.ts Merge strip and startup warning now driven by the shared restricted list
packages/cli/src/utils/settingsUtils.ts The single WORKSPACE_RESTRICTED_SETTINGS list plus the dialog filter option
packages/cli/src/config/shared-env-keys.ts Both workflow env vars added to the project-env exclusions
packages/cli/src/ui/components/SettingsDialog.tsx Hides restricted keys while Workspace is the selected scope
packages/cli/src/serve/routes/workspace-settings.ts Both daemon POST routes reject restricted keys at workspace scope
packages/cli/src/services/BuiltinCommandLoader.ts Comment-only: the flag's sources now include the setting
packages/vscode-ide-companion/schemas/settings.schema.json Regenerated from the schema definition
docs/users/configuration/settings.md New key added to the tools reference table with scope caveats
docs/users/features/commands.md /workflows registration note updated for the setting path
packages/cli/src/config/config.test.ts Wiring regression guard plus all four precedence combinations
packages/cli/src/config/settings.test.ts Scope strip/warn rules incl. system scopes, and single-source list tests
packages/cli/src/utils/settingsUtils.test.ts Dialog filter drops only the restricted key
packages/cli/src/serve/routes/workspace-settings.test.ts Both daemon routes reject workspace scope; user scope survives
packages/cli/src/config/settingsSchema.test.ts Pins type, default, requiresRestart, showInDialog, category
packages/cli/src/services/BuiltinCommandLoader.test.ts /workflows appears only when the feature resolves on

Test evidence

Unattended CI run — no PR code was built or executed here; the table below is the PR's own CI read through the API at the reviewed commit. Everything that ran is green:

Check Conclusion
Test (ubuntu-latest, Node 22.x) ✅ success
Serve A/B (ubuntu-latest, Node 22.x) ✅ success
Real daemon E2E / Java 11 ✅ success
Desktop Shell (ubuntu-22.04) ✅ success
Desktop Shell (windows-2022) ✅ success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) ✅ success
Classify PR ✅ success

One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。

The Linux unit suite runs all six changed test files; Serve A/B and the real daemon E2E are meaningful signal for the new serve-route guard. Test (macos-latest / windows-latest) and Integration Tests (CLI, No Sandbox) are skipped for this fork PR — the change is platform-agnostic config wiring, so the Linux suite is the load-bearing signal. The author reports local verification on Linux only — author's claim, not independently re-run here.

Sandboxed verification would settle the remaining surface claim: @qwen-code /tmux — that "Dynamic Workflows" actually renders under Tools in the settings dialog and /workflows appears after a restart currently rests on the author's word; the suite pins the wiring, the scope rules, and the command gating, but not the dialog surface itself.

中文说明

代码审查

先写基线方案(读 diff 之前):在 loadCliConfig(所有入口共享的唯一咽喉点)的 ConfigParameters 字面量里接上 settings.tools?.workflowsEnabled,新增带 requiresRestart: true 的 schema 条目,重新生成 vscode JSON,并用测试固定优先级。PR 的第一个 commit 正是如此;diff 的其余部分在回答接线所引出的作用域归属问题。在 reviewed commit 上逐一核实:

  • 接线与优先级。 这一行落在 useRipgrep/useBuiltinRipgrep 旁边;所有生产入口(gemini.tsx 的 TUI + headless、acpAgent.ts 的两条 ACP 路径)都经 loadCliConfig 构建 Config。优先级保持在 core 未被改动:isWorkflowsEnabled() 依次检查 QWEN_CODE_DISABLE_WORKFLOWSQWEN_CODE_ENABLE_WORKFLOWS → 字段,新增的 config.test.ts 用例固定了全部四种组合——包括 kill switch 压过显式 true 设置。
  • 一张清单驱动四个表面。 settingsUtils.ts 中的 WORKSPACE_RESTRICTED_SETTINGS 同时驱动 merge 剥离、启动警告、对话框过滤和 daemon 守卫——不再有第二份手工维护的拷贝。放置位置避免了运行时循环导入(settingsUtils.tssettings.js 仅类型导入)。重写后的 stripWorkspaceRestrictedSettings 对原有两个 security 键行为完全一致,无受限键时原样返回输入;警告仅在 workspace 原始文件确实含有该键时发出。
  • daemon 路由。 两条写入路径都在 scope/key 校验之后加了守卫:POST /workspace/settings(workspace+user 作用域)与 POST /workspaces/:workspace/settings(仅 workspace 作用域,且先要求受信任的活跃 runtime)。两者均以稳定的 workspace_restricted_setting 错误码返回 400,同一键在 user 作用域的写入仍然成功(有测试固定)。其余 serve 路由也核查过——voice/model 只写固定键集合,没有其他路径能在 workspace 作用域写入任意键。
  • 环境变量路径。 两个 workflow 变量加入 PROJECT_ENV_HARDCODED_EXCLUSIONS:项目 .env 无法再启用该功能或覆盖用户的退出选择,而 home 作用域 .env~/.qwen/.env~/.env)仍然生效——与 QWEN_TLS_INSECURE 等既有硬编码排除项语义一致。
  • 对话框。 选中 Workspace 作用域时受限键从 getDialogSettingKeys 中剔除;对话框内所有 toggle/编辑/保存路径都派生自该枚举,因此 TUI 无法写入 workspace 作用域的受限键。

无阻塞问题。/review 第 10 轮延后的六项已逐一对照代码树复核,均不构成阻塞:受限清单的叶子键未做 schema 类型校验(失败模式是无害的空操作)、保存路径未二次过滤(不可达——上游枚举已过滤)、对话框调用点缺独立测试、user 作用域 settings.jsonenv 启用同样被拦(与所有同类排除项一致,设置项本身才是一等路径)、serve GET 可能把既有的 workspace 死值与正确的 effective 并排显示、schema 描述缺少两个同类键都有的 workspace 作用域提示。均属后续跟进事项。

测试证据

无人值守 CI 运行——此处未构建或执行任何 PR 代码;下表是通过 API 读取的该 PR 在 reviewed commit 上的自身 CI 结果,所有运行的检查均为绿色。Linux 单元测试套件会运行全部六个被修改的测试文件;Serve A/B 与真实 daemon E2E 对新增的 serve 路由守卫是有效信号。Test (macos-latest / windows-latest)Integration Tests (CLI, No Sandbox) 对该 fork PR 为 skipped——改动是与平台无关的配置接线,Linux 套件是承重信号。作者自述仅在 Linux 上本地验证——系作者陈述,未在此独立复跑。

沙箱验证可以落定剩下的表层断言:@qwen-code /tmux——"Dynamic Workflows" 是否真实出现在设置对话框 Tools 分类下、重启后 /workflows 是否出现,目前仅有作者的说法;测试套件固定了接线、作用域规则与命令门控,但未覆盖对话框表层本身。

Qwen Code · qwen3.8-max

Reviewed at 0e01e7aada10c34d48bff8d8cfa61dc44679f299 · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, well-scoped fix for a verified dead switch, with the grant-ownership question answered the right way; the leftovers are cosmetic follow-ups.

Stepping back: the problem is real and code-evident — before this change no writer for workflowsEnabled existed anywhere in the CLI package, so the setting core was already reading could never be set. The fix lands at the single chokepoint every entry path shares, and the expanded scope is not bloat: this PR creates the grant path, so it is the one that must decide who may open it. It picks "the user, never the repository" and enforces that consistently across all four surfaces — merge strip + startup warning, dialog filter, env exclusions, daemon routes — driven by one list so they cannot drift. My independent proposal and the diff are the same change; I never found a simpler path, and the two doc gaps from the previous triage round are now fixed in-diff. CI on the reviewed commit is green across the board, including the Linux unit suite, Serve A/B, and the real daemon E2E that exercises the guarded routes.

The remaining nits — the schema description missing the workspace-scope caveat, the dialog call site lacking its own test, and a user-scope settings.json env opt-in sharing its siblings' exclusion semantics — are follow-up material, not blockers. Approving.

中文说明

置信度:4/5 —— 对一个已核实的死开关做了干净、恰当范围的修复,能力授予的归属问题也以正确的方式解决;剩余项均为外观层面的跟进事项。

退一步看整体:问题真实存在且可在代码中直接证实——本次改动之前,整个 CLI 包里不存在任何 workflowsEnabled 的写入点,core 一直在读的这个设置永远无法被设置。修复落在所有入口共享的唯一咽喉点上;扩展的范围并非膨胀:是本 PR 创建了这条授予路径,因此也必须由它来决定谁有权打开。它选择了"只能是用户,绝不能是仓库",并在全部四个表面一致地执行——merge 剥离 + 启动警告、对话框过滤、环境变量排除、daemon 路由——由同一张清单驱动,不会互相漂移。我独立设想的方案与 diff 就是同一个改动;我没有找到更简单的路径,上一轮 triage 指出的两处文档空缺也已在本 diff 中修复。reviewed commit 上的 CI 全线绿色,包括 Linux 单元测试套件、Serve A/B,以及覆盖了受守卫路由的真实 daemon E2E。

剩余的小问题——schema 描述缺少 workspace 作用域提示、对话框调用点缺独立测试、user 作用域 settings.jsonenv 启用了沿用同类排除项的语义——属于后续跟进事项,不构成阻塞。予以批准。

Qwen Code · qwen3.8-max

Reviewed at 0e01e7aada10c34d48bff8d8cfa61dc44679f299 · re-run with @qwen-code /triage

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship — CI landed green after the review. ✅

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed. Suggestions are inline.

Not explored to full depth (tool budget reached): "PR #9098 adds a tools.workflowsEnabled settings key…": execute scripts/generate-settings-schema.ts and diff its output against the committed settings.schema.json — the review worktree has no node_modules or bu…; "You are review agent reverse-audit — Reverse audit agent…": none — all planned checks completed within budget..

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.

Test Plan (not a blocker): 377 passed — this review observed 19468, 494 passed.

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未探索到全部深度(达到工具调用预算):"PR #9098 adds a tools.workflowsEnabled settings key…"execute scripts/generate-settings-schema.ts and diff its output against the committed settings.schema.json — the review worktree has no node_modules or bu…"You are review agent reverse-audit — Reverse audit agent…"none — all planned checks completed within budget.

未审查:反向审计——在 5 轮的反审轮数上限内未收敛。

Test Plan(非阻断):377 passed — this review observed 19468, 494 passed

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/cli/src/config/settingsSchema.ts
Comment thread packages/cli/src/config/settingsSchema.ts
Comment thread packages/cli/src/config/settingsSchema.ts Outdated
Comment thread packages/cli/src/config/settingsSchema.ts Outdated
Comment thread packages/cli/src/config/config.ts

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Test Plan (not a blocker): 377 passed — this review observed 494 passed.

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

Test Plan(非阻断):377 passed — this review observed 494 passed

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/cli/src/services/BuiltinCommandLoader.ts

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "PR #9098 adds a tools.workflowsEnabled settings key and…": none — all checks I started completed within budget.; "PR #9098 adds a tools.workflowsEnabled settings key and…": none — all checks in my dimension completed within budget..

Test Plan (not a blocker): 377 passed — this review observed 19472, 494 passed.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"PR #9098 adds a tools.workflowsEnabled settings key and…"none — all checks I started completed within budget."PR #9098 adds a tools.workflowsEnabled settings key and…"none — all checks in my dimension completed within budget.

Test Plan(非阻断):377 passed — this review observed 19472, 494 passed

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/cli/src/config/config.ts
Comment thread packages/cli/src/config/settingsSchema.ts Outdated
@qqqys
qqqys dismissed qwen-code-ci-bot’s stale review August 13, 2026 22:21

已被后续 commit 取代,当前 head 需重新 review

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "PR #9098 (QwenLM/qwen-code) adds a tools.workflowsEnabled…": could not execute the PR's unit tests in this worktree — npx vitest run src/config/config.test.ts -t "workflowsEnabled" from packages/cli fails during transfo…; "PR #9098 (QwenLM/qwen-code) adds a tools.workflowsEnabled…": did not execute npm run typecheck or the new test files in the worktree (review-only checkout; relied on CI + the fact that every type used is either pre-exis….

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"PR #9098 (QwenLM/qwen-code) adds a tools.workflowsEnabled…"could not execute the PR's unit tests in this worktree — npx vitest run src/config/config.test.ts -t "workflowsEnabled" from packages/cli fails during transfo…"PR #9098 (QwenLM/qwen-code) adds a tools.workflowsEnabled…"did not execute npm run typecheck or the new test files in the worktree (review-only checkout; relied on CI + the fact that every type used is either pre-exis…

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/config/settingsSchema.ts
Comment thread packages/cli/src/config/settings.ts Outdated
Comment thread packages/cli/src/config/settings.ts Outdated
@qqqys
qqqys dismissed qwen-code-ci-bot’s stale review August 14, 2026 00:23

已被后续 commit ade8371 取代,当前 head 需重新 review

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed. Suggestions are inline.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "PR #9098 (QwenLM/qwen-code) adds a tools.workflowsEnabled…": none — all checks I started were completed within budget.; "PR #9098 (QwenLM/qwen-code) adds a tools.workflowsEnabled…": none — all planned checks completed within budget.; "You are review agent reverse-audit — Reverse audit agent…": none — all checks above completed within budget..

Test Plan (not a blocker): 377 passed — this review observed 494 passed.

中文说明

仅完成部分审查,审查缺口已披露。 建议见行内评论。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"PR #9098 (QwenLM/qwen-code) adds a tools.workflowsEnabled…"none — all checks I started were completed within budget."PR #9098 (QwenLM/qwen-code) adds a tools.workflowsEnabled…"none — all planned checks completed within budget."You are review agent reverse-audit — Reverse audit agent…"none — all checks above completed within budget.

Test Plan(非阻断):377 passed — this review observed 494 passed

— qwen3.8-max via Qwen Code /review (v0.21.11)

Comment thread packages/cli/src/config/settings.test.ts

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "You are review agent reverse-audit — Reverse audit agent…": direct source read of packages/cli/src/serve/fast-path-settings.ts — its coverage of the new keys was inferred from the environment.ts:33-38 comment and fa….

Test Plan (not a blocker): 555 passed — this review observed 19478, 494 passed.

中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"You are review agent reverse-audit — Reverse audit agent…"direct source read of packages/cli/src/serve/fast-path-settings.ts — its coverage of the new keys was inferred from the environment.ts:33-38 comment and fa…

Test Plan(非阻断):555 passed — this review observed 19478, 494 passed

— qwen3.8-max via Qwen Code /review (v0.21.11)

@qqqys
qqqys requested review from wenshao and yiliang114 August 18, 2026 03:41
@qqqys

qqqys commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

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

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

2 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

  • R4-2 showInDialog + Workspace-scope save trap — already reported (comment 3779912243)
  • R4-3 hand-maintained workspace-restricted list in three parallel places — already reported (comment 3779912255)

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not reviewed: reverse audit — stopped before round 5 by the review time budget.

Test Plan (not a blocker): 555 passed — this review observed 19478, 494 passed.

中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 2 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未审查:反向审计——评审时间预算不足,未能开始第 5 轮。

Test Plan(非阻断):555 passed — this review observed 19478, 494 passed

— qwen3.8-max via Qwen Code /review (v0.21.13)

R4-3: the restricted set was hand-maintained in three parallel places — a
per-key warning block, the condition in `stripWorkspaceRestrictedSettings`,
and that function's destructure. Adding one restricted setting needed three
synchronized edits, and either omission is silent: forgetting the warning
discards a workspace value with no diagnostic, forgetting the strip honors a
value the warning says is ignored.

`WORKSPACE_RESTRICTED_SETTINGS` is now the single source, and the warning
loop and the strip both derive from it. It lives in `settingsUtils.ts`
rather than `settings.ts` because `settings.ts` already value-imports that
module — defining it there and importing it back would close a runtime
import cycle.

R4-2: `tools.workflowsEnabled` is the first setting that is both
`showInDialog: true` and stripped from Workspace scope, so the dialog
offered a toggle that silently never took effect — it renders from the raw
scope file, so it kept showing the value it wrote while the feature stayed
at its merged value, leaving a dead entry in the repo's .qwen/settings.json.
`getDialogSettingKeys` gained `excludeWorkspaceRestricted`, which the dialog
passes when the selected scope is Workspace. The scope comparison stays in
the component so settingsUtils keeps its type-only dependency on settings.ts.
Unlike `showInDialog: false` (what the two pre-existing restricted settings
use), the setting stays visible and editable under the scopes that honor it.

Verified: settings 169/169, settingsUtils 85/85, BuiltinCommandLoader 13/13;
packages/cli typecheck clean. Mutation-checked both ways — forcing the
filter off fails 1 test, dropping a key from the list fails 3.
SettingsDialog.test.tsx's 23 failures are pre-existing and environmental:
identical counts on upstream/main and on this branch before the change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Test Plan (not a blocker): 555 passed — this review observed 19482, 494 passed.

Deferred under the convergence posture (round 8, not a blocker) — recorded, not requested in this round:

  • packages/cli/src/config/shared-env-keys.ts:44 — [probe] no membership pin or .env-file boundary test for the two new PROJECT_ENV_HARDCODED_EXCLUSIONS entries (D8-1)
  • packages/cli/src/ui/components/SettingsDialog.tsx:226 — [probe] the dialog's workspace-scope filter has no test; both named mutants ship green (D8-2)
  • packages/cli/src/ui/components/SettingsDialog.tsx:226 — [probe] cross-scope pending overlay can flush a queued restricted-key change into the workspace file via applyRestart (D8-3)
中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

Test Plan(非阻断):555 passed — this review observed 19482, 494 passed

收敛姿态下延后(第 8 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。

— qwen3.8-max via Qwen Code /review (v0.21.13)

Comment thread packages/cli/src/config/settingsSchema.ts
R8-1. The workspace restriction stopped at the TUI dialog. `stripWorkspace
RestrictedSettings` drops these keys before every merge, so a workspace-scope
write through the settings API persists a committable dead entry into the
repo's `.qwen/settings.json` and answers 200 + `requiresRestart: true` while
the feature never turns on — GET then reports `workspace: true` beside
`effective: false`, and the warnings channel carries only `corrupted`, so the
client never learns the write was inert. Exactly the trap the SettingsDialog
comment in this same PR says it eliminates, one layer over.

`tools.workflowsEnabled` is the first workspace-restricted key with
`showInDialog: true`, which is what puts it in `getDialogSettingKeys()` and
therefore in `getAllowedKeys()` — the two pre-existing restricted keys are
`showInDialog: false` and never reached the API.

Both POST handlers now call one shared `rejectWorkspaceRestrictedWrite`,
answering 400 `workspace_restricted_setting`. One helper rather than two
copies, for the reason the previous commit collapsed the warning/strip pair.
User scope is untouched — that scope honors the key, and a guard that reached
it would kill this PR's whole enablement path.

Verified: workspace-settings 22/22, settings 169/169, settingsUtils 85/85.
Mutation-checked three ways — dropping either call site fails a test, and
widening the guard past workspace scope fails the user-scope test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@qqqys

qqqys commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

🩺 serve daemon A/B

Built the PR base vs this PR head 0e01e7a, drove a fixed endpoint set against each, and diffed the JSON responses. Only fields that changed are shown.

No response changes against the PR base across 4 scenario(s).

Qwen Code · serve A/B

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with @qwen-code /review. See workflow logs.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Deferred under the convergence posture (round 9, not a blocker) — recorded, not requested in this round:

  • packages/cli/src/ui/components/SettingsDialog.tsx:227 — [probe] the dialog's workspace-scope filter for the restricted key has no test; the inversion mutation ships green (D9-1)
  • packages/cli/src/config/shared-env-keys.ts:44 — [probe] the env exclusion also blocks a user-scope settings.json env opt-in; docs don't qualify the source (D9-2)
  • packages/cli/src/config/settingsSchema.ts:2717 — [review] the new key's description omits the workspace-scope caveat both sibling restricted settings carry (D9-3)
  • packages/cli/src/ui/components/SettingsDialog.tsx:227 — [probe] deferred restart-save flush can write the restricted key into the workspace file and discards the user-scope reset (D9-4)
中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

收敛姿态下延后(第 9 轮,非阻断)——已记录,本轮不要求修改:共 4 条(原文未翻译,列表见上方英文部分)。

— qwen3.8-max via Qwen Code /review (v0.21.13)

@qqqys

qqqys commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

Requesting a fresh round so the verdict reflects the current head.

The standing CHANGES_REQUESTED was submitted 2026-08-18T10:33Z, which predates the fix commit 0e01e7aa at 11:22Z that addressed it. The round after that commit (2026-08-18T16:44Z) came back COMMENTED with only deferred, explicitly non-blocking items, and all 14 review threads are resolved — but dismiss_stale_reviews_on_push is false, so the older verdict is still the one on the PR.

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally.

Not explored to full depth (tool budget reached): "agent 1a": executing the new unit tests (settings.test.ts, config.test.ts, settingsUtils.test.ts, workspace-settings.test.ts, settingsSchema.test.ts) — the worktree has no….

Test Plan (not a blocker): 555 passed — this review observed 494 passed.

Deferred under the convergence posture (round 10, not a blocker) — recorded, not requested in this round:

  • packages/cli/src/utils/settingsUtils.ts:381 — [review] restricted-settings list never type-checks its keys against the schema
  • packages/cli/src/ui/components/SettingsDialog.tsx:226 — [review] applyRestart save path lacks the workspace-restricted key filter
  • packages/cli/src/ui/components/SettingsDialog.tsx:227 — [review] dialog workspace-scope filter condition has no test (inversion mutant ships green)
  • packages/cli/src/config/shared-env-keys.ts:44 — [review] env exclusion also blocks a user-scope settings.json env opt-in, silently
  • packages/cli/src/serve/routes/workspace-settings.ts:123 — [review] served GET reports an inert workspace value with no warning; served clients show the feature as enabled
  • packages/cli/src/config/settingsSchema.ts:2717 — [review] schema descriptions omit the workspace-scope caveat both sibling restricted keys carry
中文说明

仅完成部分审查,审查缺口已披露。

未审查:build-and-test — Integration Tests (CLI, No Sandbox) was skipped in CI and its suite did not run locally。

未探索到全部深度(达到工具调用预算):"agent 1a"executing the new unit tests (settings.test.ts, config.test.ts, settingsUtils.test.ts, workspace-settings.test.ts, settingsSchema.test.ts) — the worktree has no…

Test Plan(非阻断):555 passed — this review observed 494 passed

收敛姿态下延后(第 10 轮,非阻断)——已记录,本轮不要求修改:共 6 条(原文未翻译,列表见上方英文部分)。

— qwen3.8-max via Qwen Code /review (v0.21.14)

@qqqys
qqqys dismissed qwen-code-ci-bot’s stale review August 20, 2026 09:47

Critical findings were fixed on the current head and their review threads are resolved; the current-head bot review reports no blocking findings. Dismissing this stale review before a fresh triage.

@qqqys

qqqys commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ❌ not passed — findings reported (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 867 passed · 2 failed · 869 total

Flakiness gate: ✅ 6 changed test file(s) x 5 identical rounds, no divergence

中文 — 判定:❌ 不通过 · 报告了发现(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:867 通过 · 2 失败 · 869 总计

抖动门:✅ 6 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR 9098 Deep Verification — feat(cli): enable dynamic workflows from a settings key

Verdict: findings — scripted assertions: 867 pass / 2 fail / 869 total (the 2 fails are test-pinning gaps, not behavioral defects; every behavioral assertion passed).
Verified head: 0e01e7aada10c34d48bff8d8cfa61dc44679f299 (git rev-parse HEAD^2), A/B base: 3b3818db87ceb01d8c11279362970d25a5221aeb (HEAD^1, the merge-ref base tip).

中文摘要
  • 结论 findings:全部行为断言通过;两条 finding 均为测试覆盖缺口(非行为缺陷、非阻塞)。
  • A/B 结论(中心主张成立,见 01-ab-settings-matrix-base-vs-head.png):base 构建中用户设置 tools.workflowsEnabled: true 被正常合并但字段是死开关(enabled=false, merged=true);head 构建中同一设置真正启用功能且 /workflows 命令出现。优先级保持不变:DISABLE 环境变量 > ENABLE 环境变量 > 设置项;默认关闭。System / SystemDefaults 作用域照常生效(运维可全机群强制)。
  • 作用域收紧成立:workspace 作用域的值在合并前被剥离并产出指明文件路径的启动警告;项目的 .envsettings.env 无法再启用功能(base 上可以——这是 PR 关闭的真实口子),用户自己的 ~/.env 不受影响。
  • daemon API02-daemon-api-wire-ab.png):workspace 作用域写入返回 400 workspace_restricted_setting 并给出"改用 user 作用域"的指引;user 作用域写入 200 且真实落盘;把守卫突变掉后 R8-1 陷阱完整复现(200 + 死条目 + GET 显示 workspace:true / effective:false + 广播),证明守卫 load-bearing,两条路由的调用点各自被测试钉死。
  • Findings(均为 Suggestion):F1 两个 workflow 环境变量排除项没有任何测试钉住(删掉后 76/76 全绿;已给出并实测了补丁测试);F2 SettingsDialog 的 Workspace 作用域过滤接线没有渲染测试钉住(过滤机制本身有测试)。
  • 未覆盖:逐 commit 归因(depth-2 浅克隆仅存 3 个 commit)、与最新 main 的试合并(本地无 main 引用)、完整 qwen serve 进程级 E2E(以真实路由 + 真实持久化链路驱动代替)、TUI 实机渲染、需要真实模型调用的关键词引导路径。

Central claim and A/B proof

Central claim: tools.workflowsEnabled in user/system-scope settings reaches the previously dead ConfigParameters.workflowsEnabled field and turns dynamic workflows on, with precedence QWEN_CODE_DISABLE_WORKFLOWS > QWEN_CODE_ENABLE_WORKFLOWS > setting, default off. Secondary claims: (A) only user/system scopes may grant it — workspace values are stripped with a warning, and project env files cannot set either feature flag; (B) the daemon settings API rejects workspace-scope writes of the key on both routes while still accepting user-scope writes.

Method: harness 1 (harness/h1-run.mjsh1-child.mjs) drives the real compiled loadSettings → loadCliConfig → Config.isWorkflowsEnabled / BuiltinCommandLoader pipeline from the head dist (CI build at the merge commit) and from a base worktree build at HEAD^1 (only packages/cli rebuilt; a git diff HEAD^1..HEAD --name-only census shows every changed file lives under docs/, packages/cli/src/, or packages/vscode-ide-companion/schemas/, so all internal workspace deps are untouched; realpath checks and the confound note are in Methodology). 13 cells × 2 arms, 72 scripted assertions, 72/72 pass — witness 01-ab-settings-matrix-base-vs-head.png, raw logs logs/h1-{head,base}-stdout.log.

cell scenario base (HEAD^1) head (PR)
C1 no settings, no env off off
C2 user settings.json: workflowsEnabled: true off (value merged, field dead) on, /workflows present
C3 user true + DISABLE_WORKFLOWS=1 off off (kill switch wins)
C4 user false + ENABLE_WORKFLOWS=1 on on
C5 workspace true (trusted) off, merged=true, no warning off, merged value stripped, warning names the file
C6 user false + workspace true off, merged=true off, merged=false (strip beats workspace override), warning
C7 System scope true off (dead) on (operator force-on works)
C8 SystemDefaults true off (dead) on
C9 both env vars set off off (disable wins)
C10 launch env ENABLE=1 only on on (user env path untouched)
E1 project .env sets ENABLE=1 on — a repo could enable it off — excluded
E2 workspace settings.json env: block on off — excluded
E3 user ~/.env sets ENABLE=1 on on (user-owned path kept)

The C2 row is the load-bearing flip: on base the setting value demonstrably reaches the merged settings (merged=true) yet isWorkflowsEnabled() is false and /workflows is absent from the real command loader; on head the same file enables the feature. E1/E2 show the env-exclusion claim is not decorative — on base, an untrusted repository's .env or settings.env genuinely turned the feature on.

The startup warning observed on head (C5/C6), verbatim:
Warning: tools.workflowsEnabled in workspace settings (<path>/.qwen/settings.json) is ignored. This setting is only honored from User, System, or SystemDefaults scope settings.

Daemon settings API (wire oracle)

Harness 2 (harness/h2-run.mjsh2-child.mjs) builds a real express app with the real registerWorkspaceSettingsRoutes from each dist, wired to the real persistence chain (loadSettings(...).setValue, mirroring persistSettingFn in run-qwen-serve.ts), on a loopback port; 9 cells × 3 arms, 42/42 assertions pass — witness 02-daemon-api-wire-ab.png.

cell base head head, guard mutated off
POST workspace tools.workflowsEnabled=true 400 disallowed_key (key unknown to base schema) 400 workspace_restricted_setting + "set it at user scope instead" 200 requiresRestart:true
workspace settings file after no entry no entry dead entry persisted
GET /workspace/settings key not listed descriptor present, workspace: —, effective: false (default) workspace: true beside effective: false + broadcast
POST user scope same key 400 disallowed_key 200 + persisted to user file (accept end intact) 200 + persisted
POST workspace ui.hideTips (control) 200 persisted 200 persisted 200 persisted (guard doesn't over-block)
POST workspace security.allowPrivateNetworkHooks (sibling) 400 disallowed_key 400 disallowed_key 400 disallowed_key

Two attribution notes. (1) The R8-1 "200 + inert write" trap is not reproducible against the merge base — base rejects the key wholesale because its schema lacks the entry; the trap exists in the PR's intermediate state (schema added before the guard). The guard-mutant arm (single-point mutation inside rejectWorkspaceRestrictedWrite) reproduces it exactly, including the workspace: true / effective: false GET shape and the success broadcast, which is what proves the guard load-bearing. (2) The guard sits after the allowedKeys check; the two pre-existing restricted keys are showInDialog: false and never reach it — they are rejected by the older disallowed_key path on both arms, unchanged.

Mutation matrix (vacuity + pinning)

All on HEAD source; positive controls are the four killed mutants plus green unmutated baselines. Witness 03-mutation-matrix.png, logs in logs/m*.log.

mutation suite result
M1 revert the one-line config.ts fix config.test.ts -t "loadCliConfig workflowsEnabled" killed — exactly 1 red: should be enabled when workflowsEnabled is set to true in settings, expected false to be true (behavioral, not import/compile). Matches the author's claim precisely.
M2 drop the key from WORKSPACE_RESTRICTED_SETTINGS settings + settingsUtils + workspace-settings suites killed — 6 new reds: strip ×2, warning ×1, single-source dotted keys ×1, both route guards ×2
M3 SettingsDialog wiring → excludeWorkspaceRestricted: false SettingsDialog.test.tsx survived 61/61 → Finding F2
M3b filter implementation in settingsUtils.getDialogSettingKeys neutered settingsUtils.test.ts killed — exactly 1 red (to not include 'tools.workflowsEnabled') — mechanism pinned, wiring not
M4 remove only the qualified-route guard call site workspace-settings.test.ts killed — exactly 1 red on the qualified-route test, primary-route test stays green
M5 remove both workflow env vars from PROJECT_ENV_HARDCODED_EXCLUSIONS shared-env-keys.test.ts + environment.test.ts survived 76/76 → Finding F1

Findings

F1 (Suggestion) — the two env exclusions are not pinned by any test

QWEN_CODE_ENABLE_WORKFLOWS / QWEN_CODE_DISABLE_WORKFLOWS were added to PROJECT_ENV_HARDCODED_EXCLUSIONS, but no test asserts their presence (M5 survives: removing both leaves shared-env-keys.test.ts + environment.test.ts at 76/76 green), even though every other exclusion tier in that file has explicit cases. The behavior is correct — harness cells E1/E2 prove head blocks project .env/settings.env while E3 keeps the user path — so this is a regression-latency gap, not a defect. A future edit dropping the two entries would ship silently and reopen the exact repository-enable path this PR closes.

Measured fix (applied in a scratch copy, then removed): add to packages/cli/src/config/shared-env-keys.test.ts, in the file's existing style,

it('excludes the dynamic-workflow feature flags', () => {
  for (const key of [
    'QWEN_CODE_ENABLE_WORKFLOWS',
    'QWEN_CODE_DISABLE_WORKFLOWS',
  ]) {
    expect(PROJECT_ENV_HARDCODED_EXCLUSIONS).toContain(key);
    expect(isHardcodedProjectEnvExclusion(key)).toBe(true);
  }
});

Results: green on head (1/1); against the M5 mutant it fails expected [ 'QWEN_HOME', …(56) ] to include 'QWEN_CODE_ENABLE_WORKFLOWS' (logs/f1-proposed-pin-vs-m5.log). No production code changes needed.

F2 (Suggestion) — the dialog's Workspace-scope exclusion wiring is not pinned

SettingsDialog.tsx passes excludeWorkspaceRestricted: selectedScope === SettingScope.Workspace; M3 (forcing the option to false) leaves SettingsDialog.test.tsx at 61/61 — no rendered-output test asserts the key disappears under Workspace scope. The filter mechanism itself is pinned (M3b), so the exposure is limited to the one-line wiring regressing (e.g. a constant or an inverted comparison) without notice. Suggested fixture (shape only, not measured in this round): render the dialog, select the Workspace scope, assert tools.workflowsEnabled is absent from the rendered items while still present under User scope — the file's existing should render with different scope selected (Workspace) test is the natural home.

Not covered

  • Per-commit attribution: the checkout is depth 2 — only the merge commit, HEAD^1, and HEAD^2 exist locally while the metadata lists 8 PR commits (git rev-list HEAD^1..HEAD^2 returns 1 at the shallow boundary). The aggregate HEAD^1..HEAD diff is what was verified.
  • Trial merge into current main: the snapshot's baseRefOid (fe6d2ac…) differs from the merge-ref base tip (3b3818d…) — the merge commit was built against 3b3818d, so main moved past the PR's recorded base (a shallow clone cannot prove which is newer, nor reach a main ref to re-run the suite against a fresher tip). The A/B uses the merge-ref base tip, the strongest base available here.
  • Full qwen serve process boot: harness 2 drives the real route module with the real persistence chain but not the complete daemon (auth token, workspace registry, bridge). The guard sits before all of that, and M4 pins the second route's call site.
  • TUI-rendered settings dialog in a real terminal and the keyword-steering surface (needs live model calls); the command-loader surface is covered by real BuiltinCommandLoader runs in harness 1, and the schema's requiresRestart rationale (three startup-built surfaces) is pinned by settingsSchema.test.ts.
  • The author's "23 pre-existing SettingsDialog failures" did not reproduce here: the suite is 61/61 green in this environment on head (and the test file is unmodified by the PR). That is better than claimed, not a regression; the discrepancy is environmental.
  • 12 settings.test.ts failures in this sandbox are environmental, A/A-proven: the same 12 names fail on the base build (logs/gate-settings-base.log); they concern home-dir/.env resolution, and the PR-attributed delta is +8 tests, all passing (04-gates-and-attribution.png).
  • packages/core precedence tests (config.workflows.test.ts) were not re-run — core is untouched by the diff and the precedence behavior is covered by harness cells C3/C4/C9/C10 against the real Config.

Methodology

Environment: node:22-bookworm container (Node v22.23.2), CI-built head dist at the merge commit; base arm = git worktree add tmp/pr9098-base HEAD^1 with packages/cli rebuilt there (tsc --build after generating git-commit.ts), worktree removed after the A/B cells were captured. Base-tree node_modules were symlinked to the root install; readlink -f node_modules/@qwen-code/qwen-code-core resolves into the head tree — accepted as a clean control because the changed-path census (git diff HEAD^1..HEAD --name-only) confines the entire diff to docs/, packages/cli/src/, and packages/vscode-ide-companion/schemas/, so no internal dependency's code differs between the arms (package.json/package-lock.json untouched). Harnesses import compiled dist modules by absolute path and spawn fresh node processes with hermetic HOME/QWEN_HOME/QWEN_CODE_SYSTEM_SETTINGS_PATH fixtures; oracles are isWorkflowsEnabled(), merged-value inspection, getSettingsWarnings(), real command-loader output, HTTP status/body on loopback, settings-file contents on disk, and recorded broadcasts. Mutations were applied to source, run through the package's own vitest, and restored (git status clean after each). Raw per-arm logs live in logs/; harness scripts in harness/; the guard-mutant dist in mutant-dist/. Evidence captures were produced with scripts/verify-capture.mjs.

Flakiness gate log

rounds=5 files=6 skipped=0
file packages/cli/src/config/config.test.ts: (cd packages/cli) npx --no-install vitest run ./src/config/config.test.ts
file packages/cli/src/config/settings.test.ts: (cd packages/cli) npx --no-install vitest run ./src/config/settings.test.ts
file packages/cli/src/config/settingsSchema.test.ts: (cd packages/cli) npx --no-install vitest run ./src/config/settingsSchema.test.ts
file packages/cli/src/serve/routes/workspace-settings.test.ts: (cd packages/cli) npx --no-install vitest run ./src/serve/routes/workspace-settings.test.ts
file packages/cli/src/services/BuiltinCommandLoader.test.ts: (cd packages/cli) npx --no-install vitest run ./src/services/BuiltinCommandLoader.test.ts
file packages/cli/src/utils/settingsUtils.test.ts: (cd packages/cli) npx --no-install vitest run ./src/utils/settingsUtils.test.ts


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/cli/src/config/config.test.ts: PPPPP
  packages/cli/src/config/settings.test.ts: PPPPP
  packages/cli/src/config/settingsSchema.test.ts: PPPPP
  packages/cli/src/serve/routes/workspace-settings.test.ts: PPPPP
  packages/cli/src/services/BuiltinCommandLoader.test.ts: PPPPP
  packages/cli/src/utils/settingsUtils.test.ts: PPPPP

verdict: pass
summary: 6 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/cli/src/config/config.test.ts: P (exit 0)
round 1 · packages/cli/src/config/settings.test.ts: P (exit 0)
round 1 · packages/cli/src/config/settingsSchema.test.ts: P (exit 0)
round 1 · packages/cli/src/serve/routes/workspace-settings.test.ts: P (exit 0)
round 1 · packages/cli/src/services/BuiltinCommandLoader.test.ts: P (exit 0)
round 1 · packages/cli/src/utils/settingsUtils.test.ts: P (exit 0)
round 2 · packages/cli/src/config/config.test.ts: P (exit 0)
round 2 · packages/cli/src/config/settings.test.ts: P (exit 0)
round 2 · packages/cli/src/config/settingsSchema.test.ts: P (exit 0)
round 2 · packages/cli/src/serve/routes/workspace-settings.test.ts: P (exit 0)
round 2 · packages/cli/src/services/BuiltinCommandLoader.test.ts: P (exit 0)
round 2 · packages/cli/src/utils/settingsUtils.test.ts: P (exit 0)
round 3 · packages/cli/src/config/config.test.ts: P (exit 0)
round 3 · packages/cli/src/config/settings.test.ts: P (exit 0)
round 3 · packages/cli/src/config/settingsSchema.test.ts: P (exit 0)
round 3 · packages/cli/src/serve/routes/workspace-settings.test.ts: P (exit 0)
round 3 · packages/cli/src/services/BuiltinCommandLoader.test.ts: P (exit 0)
round 3 · packages/cli/src/utils/settingsUtils.test.ts: P (exit 0)
round 4 · packages/cli/src/config/config.test.ts: P (exit 0)
round 4 · packages/cli/src/config/settings.test.ts: P (exit 0)
round 4 · packages/cli/src/config/settingsSchema.test.ts: P (exit 0)
round 4 · packages/cli/src/serve/routes/workspace-settings.test.ts: P (exit 0)
round 4 · packages/cli/src/services/BuiltinCommandLoader.test.ts: P (exit 0)
round 4 · packages/cli/src/utils/settingsUtils.test.ts: P (exit 0)
round 5 · packages/cli/src/config/config.test.ts: P (exit 0)
round 5 · packages/cli/src/config/settings.test.ts: P (exit 0)
round 5 · packages/cli/src/config/settingsSchema.test.ts: P (exit 0)
round 5 · packages/cli/src/serve/routes/workspace-settings.test.ts: P (exit 0)
round 5 · packages/cli/src/services/BuiltinCommandLoader.test.ts: P (exit 0)
round 5 · packages/cli/src/utils/settingsUtils.test.ts: P (exit 0)

Evidence images

01-ab-settings-matrix-base-vs-head

02-daemon-api-wire-ab

03-mutation-matrix

04-gates-and-attribution

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

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

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@wenshao

wenshao commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Local real-environment verification — head 0e01e7a

Verdict: every user-facing claim in this PR holds up in a real build. No blocking findings. I built two full arms locally — base = merge-base 7141976, head = 0e01e7a, each with all workspace sibling packages compiled and the esbuild bundle produced — and drove them through the real TUI (node-pty + xterm.js render, screenshots below) and a real qwen serve daemon process. This complements the sandboxed CI verification, which explicitly listed live TUI rendering and process-level serve E2E as its uncovered areas.

Method guards: the head bundle contains the PR-only strings workspace_restricted_setting / Dynamic Workflows while the base bundle contains neither (proves the A/B actually swapped); every TUI scenario ran against a recording fake OpenAI endpoint that logged 0 requests (all checks are keystroke-only, no model involvement); each scenario used a fresh isolated $HOME and workspace.

Unit tests + regression-guard check

  • 666/666 pass across the six touched test files (config, settingsSchema, settings, settingsUtils, workspace-settings routes, BuiltinCommandLoader), run with vitest from packages/cli.
  • RED/GREEN: reverting only the one-line wiring in packages/cli/src/config/config.ts to base flips exactly should be enabled when workflowsEnabled is set to true in settings red while the other three cases in the block stay green — precisely what the PR's test plan claims for its regression guard. Restored → all green again.

Real TUI, both arms

# Scenario base 7141976 head 0e01e7a
1 user settings.json tools.workflowsEnabled: true /workflows absent — value accepted but dead /workflows in typeahead
2 /settings dialog, search "workflow" (User scope) no Dynamic Workflows row row present, true*, beside the two unrelated workflow settings
3 dialog switched to Workspace scope exactly that row disappears; the two unrelated ones stay
4 workspace .qwen/settings.json true silently ignored, no warning startup warning names the file, feature stays off
5 project .env QWEN_CODE_ENABLE_WORKFLOWS=1 /workflows appears — a repo could self-grant the feature excluded, stays off
6 /tools with user-scope true no Workflow tool Workflow tool registered
7 user true + QWEN_CODE_DISABLE_WORKFLOWS=1 off (kill switch wins)
8 user-owned paths on head: ~/.env ENABLE=1; launch-env ENABLE=1 both still enable (no regression on the kept paths)

Row 1 is the dead switch coming alive; row 5 is the real security hole this PR closes — on base an untrusted repository's .env turns the feature on, on head it cannot.

Row 1 — same user settings file, head vs base:

head: /workflows in typeahead

base: nothing

Rows 2–3 — dialog, User scope then Workspace scope (head). The three same-word settings the description disambiguates are visible side by side; under Workspace scope only Dynamic Workflows is filtered out:

head dialog user scope

head dialog workspace scope

Row 4 — workspace-scope value warned about and ignored (head):

head workspace warning

Row 5 — project .env, base vs head (the closed hole):

base: repo .env enables the feature

head: excluded

Row 6 — Workflow tool actually in the registry (head, /tools):

head /tools shows Workflow

Real qwen serve daemon

Started each arm's bundled CLI as a real daemon (serve --token …) and hit the settings API:

  • head: workspace-scope POST /workspace/settings400 workspace_restricted_setting with the actionable "set it at user scope instead" message, and no dead entry is written into the repo's .qwen/settings.json. User-scope write → 200 requiresRestart: true and the value genuinely lands in ~/.qwen/settings.json on disk. GET then reports values: { effective: true, user: true }.
  • base: the key is invisible to the API — both scopes answer 400 disallowed_key and the key is absent from GET. So the new 400 is purely additive; no previously-working client call changes shape.
  • The second call site (workspace-qualified route) needs an active multi-workspace runtime to drive over HTTP; it is pinned by this PR's route tests (workspace-settings.test.ts, R8-1 second-call-site case), which pass here.

Generated schema check

Re-running npx tsx scripts/generate-settings-schema.ts in the head worktree reproduces packages/vscode-ide-companion/schemas/settings.schema.json byte-for-byte — the vscode hunk is generated output, as the PR states.

Not covered / notes

  • Keyword steering wasn't driven (needs a real model turn); it reads the same startup-built Config as the two surfaces verified above.
  • On the earlier sandbox findings: the settings.env-side exclusion of the two env keys is unit-pinned (settings.test.ts asserts both stay undefined); the project-.env path is covered live by row 5 here. The dialog's workspace-scope filter still has no render test, but rows 2–3 exercise the real wiring end to end. Both remain reasonable unit-coverage follow-ups, not blockers.
  • Linux only (matches the green ubuntu CI lane; macOS/Windows lanes were routed off for this PR).
中文说明

本地真实环境验证 — head 0e01e7a

结论:该 PR 的所有用户可见主张在真实构建中全部成立,无阻塞性发现。 本地构建了两个完整臂 —— base = merge-base 7141976,head = 0e01e7a,各自编译全部 workspace 兄弟包并产出 esbuild bundle —— 分别驱动真实 TUI(node-pty + xterm.js 渲染,截图见上)与真实 qwen serve 守护进程。这正好补上 CI 沙箱验证明确列为未覆盖的两块:TUI 实机渲染与 serve 进程级 E2E。

方法护栏:head bundle 含 PR 独有字符串 workspace_restricted_setting / Dynamic Workflows,base bundle 两者皆无(证明 A/B 真的换了代码);所有 TUI 场景连着一个记录请求的 fake OpenAI 端点,全程 0 次模型请求(纯按键路径);每个场景使用全新隔离的 $HOME 与工作区。

单元测试 + 回归护栏

  • 六个被改测试文件 666/666 通过(在 packages/cli 下用 vitest 实跑)。
  • RED/GREEN:仅把 packages/cli/src/config/config.ts 的一行接线回退到 base,恰好 should be enabled when workflowsEnabled is set to true in settings 一条变红、同块其余三条保持绿 —— 与 PR 测试计划所述完全一致;恢复后全绿。

真实 TUI 矩阵(对应上文截图)

  1. 用户 settings.jsontrue:base 无 /workflows(值被合并但字段死);head typeahead 出现 /workflows
    2–3. /settings 对话框搜 "workflow":head 在 User scope 显示 Dynamic Workflows true*,与两个同名不相干设置同屏(正是描述里担心的歧义,肉眼可辨);切到 Workspace scope 后仅该行消失。base 无此行。
  2. 工作区 .qwen/settings.jsontrue:head 启动警告点名文件、功能保持关;base 静默吞掉、无警告。
  3. 项目 .envQWEN_CODE_ENABLE_WORKFLOWS=1:base 上 /workflows 直接出现 —— 仓库可自授能力;head 上被排除,保持关。 这就是本 PR 堵上的真实口子。
  4. /tools:head 注册了 Workflow 工具,base 没有。
  5. 用户 true + QWEN_CODE_DISABLE_WORKFLOWS=1:head 保持关(kill switch 优先)。
  6. 保留路径无回归(head):用户自己的 ~/.env 与启动环境变量置 ENABLE=1 均仍能开启。

真实 serve 守护进程

  • head:workspace scope 写入 → 400 workspace_restricted_setting,报错信息给出"改用 user scope"指引,且不会往仓库 .qwen/settings.json 写死条目;user scope 写入 → 200 requiresRestart: true,值真实落盘到 ~/.qwen/settings.json;GETvalues: { effective: true, user: true }
  • base:该 key 对 API 完全不可见 —— 两个 scope 都是 400 disallowed_key,GET 里没有该条目。因此新增的 400 纯属增量,不改变任何既有客户端调用的行为。
  • 第二个调用点(workspace-qualified 路由)需要激活的多 workspace runtime 才能走 HTTP 驱动;由本 PR 的路由测试(R8-1 第二调用点用例)钉住,本地实跑通过。

生成物校验

在 head worktree 重跑 npx tsx scripts/generate-settings-schema.ts,packages/vscode-ide-companion/schemas/settings.schema.json 逐字节一致 —— vscode 那块 hunk 确为生成产物。

未覆盖 / 备注

  • 关键词引导路径未驱动(需要真实模型回合);它读取的是与上面两个已验证面相同的启动期 Config
  • 关于此前沙箱报告的两条建议:两个环境变量在 settings.env 层的排除已有单测钉住(settings.test.ts 断言两者保持 undefined);项目 .env 路径由上面第 5 行实测覆盖。对话框 Workspace scope 过滤仍无渲染测试,但第 2–3 行已端到端驱动了真实接线。两条仍是合理的补测建议,不构成阻塞。
  • 仅 Linux(与本 PR 绿灯的 ubuntu CI 道一致;macOS/Windows 道被路由跳过)。

🤖 Generated with Claude Code — Claude Fable 5

@wenshao
wenshao added this pull request to the merge queue Aug 23, 2026
Merged via the queue into QwenLM:main with commit 98fa2e9 Aug 23, 2026
198 of 202 checks passed
TianYuan1024 added a commit to TianYuan1024/qwen-code that referenced this pull request Aug 23, 2026
One conflict, in the workspace-scope strip this PR had added to.

`main` (QwenLM#9098, QwenLM#9737) generalised that strip into a single data list:
WORKSPACE_RESTRICTED_SETTINGS in settingsUtils.ts now drives the strip, the
"your workspace value was ignored" warning, and the settings dialog's scope
filter, so the three surfaces cannot drift apart.

This PR had hand-rolled the same thing for permissions.planMode — a second
branch inside stripWorkspaceSecurityBypasses and a third copy of the warning
text. Both are deleted in favour of main's version, and the setting is
registered as one entry in the list instead. The behaviour is unchanged and
the four scope tests added here still pass against the generic mechanism; the
dialog filter comes along for free, though planMode sets showInDialog: false.
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.22.2.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants