[ZCode 主仓 bug] 后台智能体运行期间编辑消息重发会永久丢失完成通知(stale-branch fencing 误伤),任务 UI 永久运行中——已定位到源码丢弃点
维护者通常 2 天内回复
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 52/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- typescript
调研方向
Start with core/src/runtime-task/registry.ts and trace branchGeneration updates in core/src/runtime/methods/rewind-message.ts. Then inspect core/src/runtime/methods/background-notifications.ts and core/src/runtime/methods/subagent.ts, using the described edit-and-retry scenario to follow notification and terminal-event fencing. Done means a still-running task remains deliverable after an edit, stale abandoned-branch events remain fenced, and the UI reaches a terminal state.
由索引模型根据 Issue 内容生成。
描述
借道说明同 #60:ZCode 主仓 issues 关闭、外部账号 PR 被权限拦截,只能在本仓反馈。本 issue 已完成日志证据与开源源码(main@29628c9)双侧定位,丢弃点、日志级别、设计意图注释均可直接对应到行。
[Bug] 后台智能体运行期间"编辑消息重发"会永久丢失该智能体的完成通知,任务在 UI 永久显示运行中
一句话
用户在后台子智能体运行期间编辑一条已发送消息并重发(editUserQuery → conversation rewind → 会话分支代数 branchGeneration 推进),该智能体完成后其通知被"迟到围栏"(stale-branch fencing)以 debug 级日志静默丢弃——生产日志不可见、无报错、无补偿;UI 因终态事件被同一围栏拦截而永久显示"运行中",取消时报 background_task_not_running,形成死循环。
复现步骤
- 在会话中派一个后台子智能体(
Agent工具run_in_background=true),任务需运行数分钟; - 智能体运行期间,编辑一条已发送的消息并重发(触发
editUserQuery→ rewind); - 等智能体完成。
结果:~/.zcode/cli/agents/<会话>/<智能体id>/metadata.json 为 status: completed(执行侧干净结束),但父会话永远收不到完成通知;UI 任务永久"运行中";尝试取消报 background work <id> was not cancelled: background_task_not_running。
实测证据链(2026-10-04,zcode-2026-10-04.jsonl + model-io 转录,时间为本地)
| 时间 | 事件 | 证据 |
|---|---|---|
| 10:38:22 | round2 评审智能体后台启动 | tracking.started,taskId=agent_0003c234 |
| 10:45:07 / 10:45:29 / 10:45:40 | 用户三次编辑重发消息(根因窗口) | 3 次 conversation.command.edit_user_query;伴随 2 次 stopActiveForegroundExecution + 1 次 sendQueuedNow preempts active turn;回合计数 165→32(回卷重建痕迹) |
| 11:03:53.281 | 智能体完成,通知"封存" | runtime.background_task_notifications.sealed, reason: subagent_terminal |
| 11:03:53.313 | 通知"入队"但随即被丢弃 | subagent.background.notification.enqueued(info)之后全天无该智能体的 background_task.notification.runtime_enqueued——实际在 background-notifications.ts:33 被 debug 级 stale_branch_dropped 丢弃(见下方源码;当天日志级别分布 info 9332 / warn 65 / error 14 / debug 0,故不可见) |
| — | 通知从未注入模型 | 父会话转录中 <task-id>agent_d2419519(对照组 round1)通知块 23 个;<task-id>agent_0003c234 0 个(会话全天共 265 个通知块) |
| 11:21-11:44 | 桥本身正常 | 回卷之后启动的任务分别获得 runtime_command_52/55/57 并正常送达——坏的只有"回卷前注册"的那一个 |
| 11:44:32-11:47:31 | 取消失败 ×5 | background_task_not_running;UI 仍显示运行中(终态事件 emit 被同一围栏拦截,状态永不更新) |
对照:round1(agent_d2419519)10:20:34 完成瞬间为完整链 runtime_enqueued(runtime_command_36) → enqueued → batch_started → 注入;全天仅有的 3 次 edit_user_query 恰好全部落在 round2 运行窗口内。
源码因果链(四步闭合,apps/zcode-cli)
-
注册打戳:
core/src/runtime-task/registry.ts:119-122——任务注册时盖branchGeneration戳,接口注释明说意图:"task 注册时所属 active conversation branch;用于迟到 completion fencing"。 -
编辑→回卷→代数推进:
bootstrap/src/zcode-protocol-v4/commands/handlers/fork-edit-retry.ts:4("editUserQuery = 换文本的 retryTurn:rewind 截断该 turn");core/src/runtime/methods/rewind-message.ts:580-581——this.branchGeneration = branchGeneration; this.runtimeTaskRegistry.setActiveBranchGeneration(...),只推进新注册指针,存量运行中任务的旧代数戳原样保留。 -
丢弃点:
core/src/runtime/methods/background-notifications.ts:31-43——通知到达时查注册表,task.branchGeneration !== this.branchGeneration→logger.debug("Dropped stale-branch background task notification")+ return。debug 级 + 生产日志无 debug = 静默。 -
误导性"成功"痕迹:
core/src/subagent/runner.ts:1860-1896——enqueueParentTaskNotification为 void 调用、不检查结果,之后无条件打 info 级subagent.background.notification.enqueued。日志里"已入队"不代表进了投递队列(真正的入队日志在runtime-command-queue.ts:23-35的runtime_enqueued)。
补充:终态事件路径 core/src/runtime/methods/subagent.ts:76(isStaleBranchRuntimeTaskEvent)被同一围栏拦截,导致 UI 永久"运行中";回卷后新任务通知正常(其代数戳为新代数),且不在注册表的 taskId 会走 ?? this.branchGeneration 回退而恰好放行——两个现象都与源码一致。
设计缺陷本质
围栏无法区分两种情形:任务属于被放弃的旧分支(该拦:防止迟到通知污染回卷后的新对话)vs 任务仍在运行、用户只是编辑了一条消息(不该拦:智能体还在为新分支工作,其完成通知对新分支完全有效)。后者的通知被拦后,任务在 UI 悬死且无任何可见痕迹。
修复建议(按侵入性排序)
- 语义修复:回卷时(或通知到达时)对注册表中非终态的运行中任务重刷
branchGeneration为新代数——仍存活的任务,其通知对新分支有效;只有终态/已通知条目保留旧代数参与围栏。 - 可观测性修复:
stale_branch_dropped从 debug 提升到 warn,并在 runner 侧检查投递结果,消除误导性的 info 级 "enqueued" 痕迹。 - 状态收口:终态事件(emitParentEvent 路径)的 fencing 应与通知 fencing 区分处理,至少保证 UI 侧任务状态能收口,避免"永久运行中 + 取消报 not_running"的死循环。
规避(用户侧,已验证)
后台智能体运行期间不要"编辑消息重发",需补充内容新发一条;若已触发,以磁盘产物为准:读 ~/.zcode/cli/agents/<会话>/<智能体id>/metadata.json 的 status,最终回复在同目录 task.output。
- 主要语言
- Python
- 星标
- 86
- 派生
- 51
- PR 合并指标
- 30 天内没有已合并 PR
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
zai-org/zcode-plugins 的其他 Issue
-
video-agent-kit (0.4.3): hooks invoke python3, which doesn't exist on Windows — SessionStart/UserPromptSubmit/Stop hooks fail on every trigger可能已有人在做 @dvd233 于 12 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 85/100
zai-org/zcode-plugins#14 ·
维护者通常 2 天内回复
-
难度 5/5 一周以上 新手友好度 25/100
zai-org/zcode-plugins#63 ·
维护者通常 2 天内回复
-
难度 3/5 1-2 天 新手友好度 25/100
zai-org/zcode-plugins#60 ·
维护者通常 2 天内回复
-
难度 4/5 3-5 天 新手友好度 38/100
zai-org/zcode-plugins#59 ·
维护者通常 2 天内回复
-
难度 5/5 一周以上 新手友好度 45/100
zai-org/zcode-plugins#58 ·
维护者通常 2 天内回复
查看 zai-org/zcode-plugins 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 82/100
LearningCircuit/local-deep-research#7206 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 68/100
chingu-voyages/V62-tier3-team-33#285 ·
维护者通常 1 天内回复
-
Proxy drops log notifications from backends that don't send FastMCP's msg/extra dict可能已有人在做 @asasemahmed 今天认领。 未关闭bug server
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 82/100
维护者通常 1 天内回复
-
[Bug]: Bedrock request metadata forwarding does not work for /embeddings可能已有人在做 关联的 PR 仍在进行中或已合并。 未关闭bug llm translation
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复