Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix: enable fork subagents by default#4963

Merged
wenshao merged 2 commits into
QwenLM:mainQwenLM/qwen-code:mainfrom
he-yufeng:fix/fork-subagent-default-bubblehe-yufeng/qwen-code:fix/fork-subagent-default-bubbleCopy head branch name to clipboard
Jun 13, 2026
Merged

fix: enable fork subagents by default#4963
wenshao merged 2 commits into
QwenLM:mainQwenLM/qwen-code:mainfrom
he-yufeng:fix/fork-subagent-default-bubblehe-yufeng/qwen-code:fix/fork-subagent-default-bubbleCopy head branch name to clipboard

Conversation

@he-yufeng

Copy link
Copy Markdown
Contributor

Fixes #4956.

Summary

  • make fork subagents available by default in interactive sessions
  • keep non-interactive sessions on the existing general-purpose fallback path
  • set the implicit fork agent's approval mode to default, so trusted folders do not silently promote fork workers to auto-edit
  • remove the now-dead /fork feature-gate error string from required i18n keys and locale files

Current main does not define a separate bubble approval mode; default is the existing mode that routes tool calls through the normal permission prompt path.

To verify

  • npm run generate
  • npm run build --workspace=@qwen-code/web-templates
  • npm run build --workspace=@qwen-code/channel-base
  • npm run build --workspace=@qwen-code/channel-telegram
  • npm run build --workspace=@qwen-code/channel-weixin
  • npm run build --workspace=@qwen-code/channel-dingtalk
  • npm run build --workspace=@qwen-code/channel-feishu
  • npm run build --workspace=@qwen-code/channel-plugin-example
  • npm run build --workspace=@qwen-code/acp-bridge
  • npm test -- src/tools/agent/agent.test.ts from packages/core
  • npm test -- src/ui/commands/forkCommand.test.ts src/i18n/mustTranslateKeys.test.ts from packages/cli
  • npm run check-i18n from packages/cli
  • npm run typecheck --workspace=@qwen-code/qwen-code-core
  • npm run typecheck --workspace=@qwen-code/qwen-code
  • targeted npx eslint on the changed core/CLI/i18n files
  • git diff --check

@he-yufeng
he-yufeng force-pushed the fix/fork-subagent-default-bubble branch from a0b4d6e to b83dc5d Compare June 10, 2026 15:37
*/
export function isForkSubagentEnabled(config: Config): boolean {
return config.isForkSubagentEnabled() && config.isInteractive();
return config.isInteractive();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] Config.isForkSubagentEnabled() and its backing field forkSubagentEnabled are now dead code. After this change, the standalone function only calls config.isInteractive() — nothing in production code calls the Config method anymore (verified via grep). Consider removing Config.isForkSubagentEnabled(), the forkSubagentEnabled field, and the forkSubagentEnabled constructor parameter in a follow-up cleanup.

— qwen3.7-max via Qwen Code /review


it('omits fork discipline when interactive but fork flag is off', async () => {
it('includes fork discipline when interactive even if the legacy flag is off', async () => {
(config as unknown as Record<string, unknown>)['isInteractive'] = vi

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[Suggestion] The mock for config.isForkSubagentEnabled in this test (and several others in this file) no longer controls the fork gate. The production path goes through the standalone isForkSubagentEnabled(config) in fork-subagent.ts, which only checks config.isInteractive(). These mocks are harmless but may mislead future readers into thinking the test is controlling fork availability via the config method. Consider removing the isForkSubagentEnabled mocks from tests that exercise the fork-gate decision — only the isInteractive mock matters.

— qwen3.7-max via Qwen Code /review

@he-yufeng
he-yufeng force-pushed the fix/fork-subagent-default-bubble branch from 34ac0a4 to 2d3152d Compare June 11, 2026 21:51
@he-yufeng

Copy link
Copy Markdown
Contributor Author

Updated for the review feedback and rebased onto current upstream/main. The stale Config forkSubagentEnabled field/method and the related misleading test mocks are removed; fork availability now depends only on interactive mode through the standalone gate. Verified locally: prettier --check on the touched files, eslint on the touched files, typecheck for packages/core and packages/cli, core agent.test.ts (98 tests), cli forkCommand.test.ts (15 tests), and git diff --check.

@DragonnZhang DragonnZhang 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. Clean removal of the fork feature gate. All references to forkSubagentEnabled, isForkSubagentEnabled(), and the QWEN_CODE_ENABLE_FORK_SUBAGENT env var are consistently removed across config, commands, i18n (all locales), and tests. The isForkSubagentEnabled() helper now correctly gates only on isInteractive(), preserving the non-interactive safety guard. The explicit approvalMode: 'default' on FORK_AGENT with satisfies SubagentConfig is a good hardening — makes fork permissions deterministic rather than relying on the fallback path. Tests are well updated, including the renamed assertions for fork discipline and interactive-mode behavior.

@wenshao

wenshao commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

✅ Verification report — built the real CLI and ran a live A/B for the fork gate

I built the actual qwen CLI from this PR's head, then ran a faithful before/after A/B in tmux plus the full cited test suite. The core fix (drop the QWEN_CODE_ENABLE_FORK_SUBAGENT env gate, keep fork interactive-only, pin the fork agent to approvalMode: 'default') behaves exactly as described.

Method

  • git worktree from PR head → built @qwen-code/qwen-code-core + @qwen-code/qwen-code (PR's tree needs a real proper-lockfile dep; installed it to build clean).
  • A/B at dist level: compiled the 4 changed runtime files (forkCommand.ts, config.ts, fork-subagent.ts, agent.ts) from the PR's own merge-base to get a faithful baseline .js, then swapped only those files between runs — same toolchain/env, only the PR delta varies. (Note: current main has drifted far ahead of this branch's base, so the honest "before" is the merge-base, not main's tip.)
  • Live TUI on glm-4.7, QWEN_CODE_ENABLE_FORK_SUBAGENT unset in both runs.

Live A/B — the gate removal (env var unset in both)

Build /fork investigate the repo… (no env var)
Baseline (gate present) The /fork command requires the fork feature gate. Set QWEN_CODE_ENABLE_FORK_SUBAGENT=1 to enable it.
PR (gate removed) Forked into a background agent… — agent launched, ran a Shell tool (git config --list --local), and reported back (freshly initialized, empty git repository) ✅

The PR fork ran end-to-end: inherited the conversation, executed a tool, completed (▶ 19s · 71k tokens). No env var needed.

The other two claims

Non-interactive stays on general-purpose fallback

  • Gate is now exactly config.isInteractive() (fork-subagent.ts); /fork is supportedModes: ['interactive'].
  • -p headless smoke works and won't fork (isInteractive() is false there). Unit test "falls back to general-purpose when non-interactive" passes.

Fork agent runs in default, not auto-edit, in trusted folders ✅ (this is the issue's "bubble permission prompts")

  • FORK_AGENT.approvalMode: 'default' flows into resolveSubagentApprovalMode(parent, 'default', isTrusted). The agent-declared default short-circuits before the isTrustedFolder → AutoEdit branch, so a trusted folder no longer silently promotes fork workers to auto-edit.
  • Proven by passing unit tests: "should respect agent-declared mode when parent is default and folder is trusted" (new path) vs "should default to auto-edit when parent is default and folder is trusted" (old path), plus a new assertion expect(createArgs[1].getApprovalMode()).toBe(ApprovalMode.DEFAULT).

Tests & build (all green)

  • core agent.test.ts98 passed; cli forkCommand.test.ts + mustTranslateKeys.test.ts35 passed; npm run check-i18n → ✅.
  • Clean removal: no dangling callers of the removed config.isForkSubagentEnabled() method or forkSubagentEnabled param; the gate-error i18n key removed from all 9 locales + mustTranslateKeys; QWEN_CODE_ENABLE_FORK_SUBAGENT is gone tree-wide (incl. docs).

One thing for reviewers to confirm is intended (not a bug)

The PR removes the forkSubagentEnabled config field entirely, so fork is now always on in interactive sessions with no off-switch (no env var, no settings.json key). That matches issue #4956 ("enable by default"); flagging only so it's a conscious decision — if a kill-switch is wanted, it'd need to be added separately.

Verdict

Looks good to merge. The fix is verified live (faithful A/B) and by the full cited test suite; removal is complete with no dangling references or stale docs; the approval-mode behavior is sound and test-covered.

中文版验证报告(点击展开)

✅ 验证报告 —— 构建真实 CLI 并对 fork 开关做了实测 A/B

我从本 PR 的 HEAD 构建了真实的 qwen CLI,在 tmux 中做了忠实的前/后 A/B 对比,并跑通了 PR 列出的全部测试。核心修复(移除 QWEN_CODE_ENABLE_FORK_SUBAGENT 环境变量开关、保持 fork 仅交互模式可用、将 fork agent 固定为 approvalMode: 'default')行为与描述完全一致。

方法

  • 从 PR HEAD 拉 git worktree,构建 core + cli(本 PR 的树需要真实的 proper-lockfile 依赖,已补装以保证干净构建)。
  • dist 级 A/B:把 4 个改动的运行时文件(forkCommand.tsconfig.tsfork-subagent.tsagent.ts)从本 PR 自身的 merge-base 编译出基线 .js,运行时只在这几个文件之间切换——工具链/环境相同,仅 PR 改动这一变量在变。(注意:当前 main 已远远领先于本分支的 base,因此忠实的"之前"应取 merge-base,而非 main 顶端。)
  • 真实 TUI 跑在 glm-4.7,两次运行均未设置 QWEN_CODE_ENABLE_FORK_SUBAGENT

实测 A/B —— 开关移除(两次均未设环境变量)

构建 /fork investigate the repo…(无环境变量)
基线(开关存在) The /fork command requires the fork feature gate. Set QWEN_CODE_ENABLE_FORK_SUBAGENT=1 to enable it.
PR(开关移除) Forked into a background agent… —— agent 已启动,执行了 Shell 工具(git config --list --local)并回报结果(freshly initialized, empty git repository) ✅

PR 的 fork 端到端跑通:继承了对话、执行了工具、运行完成(▶ 19s · 71k tokens),全程无需环境变量。

另外两个声明

非交互模式仍走 general-purpose 回退

  • 开关现在就是 config.isInteractive()(fork-subagent.ts);/forksupportedModes: ['interactive']
  • -p 无头冒烟可正常工作且不会 fork(此处 isInteractive() 为 false)。单测 "falls back to general-purpose when non-interactive" 通过。

fork agent 在受信任目录下以 default 运行,而非 auto-edit(即 issue 所说的 "bubble permission prompts") ✅

  • FORK_AGENT.approvalMode: 'default' 进入 resolveSubagentApprovalMode(parent, 'default', isTrusted)。agent 声明的 defaultisTrustedFolder → AutoEdit 分支之前短路,因此受信任目录不再把 fork worker 静默提升为 auto-edit。
  • 由通过的单测佐证:"should respect agent-declared mode when parent is default and folder is trusted"(新路径)对比 "should default to auto-edit when parent is default and folder is trusted"(旧路径),外加新断言 expect(createArgs[1].getApprovalMode()).toBe(ApprovalMode.DEFAULT)

测试与构建(全绿)

  • core agent.test.ts98 通过;cli forkCommand.test.ts + mustTranslateKeys.test.ts35 通过;npm run check-i18n → ✅。
  • 干净移除:不再有对已删除的 config.isForkSubagentEnabled() 方法或 forkSubagentEnabled 参数的悬空引用;开关错误提示的 i18n key 已从 9 个 locale + mustTranslateKeys 中移除;QWEN_CODE_ENABLE_FORK_SUBAGENT 在整棵树(含 docs)中已彻底消失。

一处建议 reviewer 确认是否为预期(并非 bug)

本 PR 完全移除了 forkSubagentEnabled 配置字段,因此 fork 现在在交互会话中始终开启且无关闭开关(无环境变量,也无 settings.json 键)。这与 issue #4956("默认启用")一致;此处仅作提示,以便这是一个有意识的决定——若需要一个 kill-switch,需另行添加。

结论

可以合并。 修复已通过实测(忠实 A/B)与全部所列测试验证;移除彻底、无悬空引用、无过时文档;审批模式行为正确且有测试覆盖。

@wenshao
wenshao merged commit 662197e into QwenLM:main Jun 13, 2026
23 checks passed
doudouOUC pushed a commit that referenced this pull request Jun 15, 2026
* fix: enable fork subagents by default

* fix: respect fork subagent config flag
wenshao added a commit to wenshao/qwen-code that referenced this pull request Jun 15, 2026
…ype awaitable

Enabling fork-by-default for every interactive session (QwenLM#4963) made an omitted `subagent_type` mean a fire-and-forget background fork. A fork's findings never flow back into the parent turn, so any caller that spawns parallel agents and then aggregates their results breaks — the bundled `review`/`simplify` skills, third-party skills, and the model's own delegation. The orchestrator waits forever for results that never arrive; nudged by the periodic todo reminder it can also spin on identical tool calls until DashScope rejects the request with `InternalError.Algo.InvalidParameter: Repetitive tool calls detected` (HTTP 400).

Make forking an explicit, deliberate choice and stop steering the model toward it when it needs the results:
- Dispatch: `subagent_type: "fork"` selects a fork (interactive only; otherwise general-purpose). Omitting `subagent_type` always resolves to the awaitable general-purpose subagent whose result returns inline — never a fork. `/fork` passes `subagent_type: "fork"` explicitly.
- Prompt: drop the "launch parallel forks for research" guidance, reframe the tool description and "When to fork" around "never fork work whose output you need", and stop advertising `fork` in the subagent_type enum (it stays valid via validation for `/fork` and intentional use).
- Skills: `review` and `simplify` launch their parallel agents with `subagent_type: "general-purpose"` and are told explicitly not to fork, since they aggregate findings.

Forking stays a first-class, default-available feature for genuinely fire-and-forget work — it just isn't the silent default, an enum pick, or something the model is nudged toward when it must read the results.
wenshao added a commit that referenced this pull request Jun 16, 2026
…ype awaitable (#5155)

Enabling fork-by-default for every interactive session (#4963) made an omitted `subagent_type` mean a fire-and-forget background fork. A fork's findings never flow back into the parent turn, so any caller that spawns parallel agents and then aggregates their results breaks — the bundled `review`/`simplify` skills, third-party skills, and the model's own delegation. The orchestrator waits forever for results that never arrive; nudged by the periodic todo reminder it can also spin on identical tool calls until DashScope rejects the request with `InternalError.Algo.InvalidParameter: Repetitive tool calls detected` (HTTP 400).

Make forking an explicit, deliberate choice and stop steering the model toward it when it needs the results:
- Dispatch: `subagent_type: "fork"` selects a fork (interactive only; otherwise general-purpose). Omitting `subagent_type` always resolves to the awaitable general-purpose subagent whose result returns inline — never a fork. `/fork` passes `subagent_type: "fork"` explicitly.
- Prompt: drop the "launch parallel forks for research" guidance, reframe the tool description and "When to fork" around "never fork work whose output you need", and stop advertising `fork` in the subagent_type enum (it stays valid via validation for `/fork` and intentional use).
- Skills: `review` and `simplify` launch their parallel agents with `subagent_type: "general-purpose"` and are told explicitly not to fork, since they aggregate findings.

Forking stays a first-class, default-available feature for genuinely fire-and-forget work — it just isn't the silent default, an enum pick, or something the model is nudged toward when it must read the results.
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.

Enable the fork subagent by default and let default agents bubble permission prompts

3 participants

Morty Proxy This is a proxified and sanitized view of the page, visit original site.