fix(channel): 修复问答与审批续跑后的输出丢失 - #1159
Conversation
Snapshot 只表示 session 运行状态;每个答案的原始事件改存 Backend 新增的 DecisionOutputStore(Memory 切片 / Redis list+hash),每条事件只追加一条, 不再整体重写快照。读端游标、Claimed 仲裁、终态宽限、订阅唤醒逻辑不变, 但唤醒改用 backend 原始 pub/sub,日志本身是唯一事实源。 送达完成后释放日志条目,保留 done/claimed 标记到 TTL,避免迟到的重试 再次转发。删除 hydrateSnapshotFromLedger 对合成 key 的特判。
finish 只在 Done/EOF 时置 stop,普通读取错误被调用方忽略并继续等待, 直到请求 context 过期才返回。现在任何错误都停止读取并原样返回。
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 968bc2888f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Execution deduplication can return success to concurrent callers that both | ||
| // passed routing's replay check. Shared backend arbitration gives only one of | ||
| // those callers permission to forward, including across server instances. | ||
| claimed, err := m.backend.ClaimDecisionOutput(ctx, ref) |
There was a problem hiding this comment.
Release abandoned output claims before allowing retries
If the channel/gRPC context is canceled after this claim succeeds—such as while sending the acceptance receipt or forwarding later events—the claim remains set until TTL expiry, while ReleaseDecisionOutput is never reached and cannot clear the claim anyway. The continuation still writes its output, but a retry with the same control ID exits through the result.Replayed branch before reading the log, permanently dropping that reply. Use a renewable consumer lease or otherwise allow takeover when the original forwarder did not complete delivery.
Useful? React with 👍 / 👎.
修复 ask_user / 工具审批接受后,后台继续运行但渠道收不到后续回复的问题。答案接受后立即回执并同步问题卡片,随后转发续跑文本、附件及下一张问题卡片;接受后的输出错误与答案提交失败分开反馈。
实现
与 #1156 及部署的关系
#1156 已先合入 main(29ddb19d8),负责答案接收、按钮 fence 和停止清理。本 PR head 968bc28 已同步该 main。渠道错误处理冲突沿用已验证组合版本:提交前失败显示本地化提交错误,接受后失败显示输出中断。
Server / Channel 应协调更新;不要假定任意新旧混跑兼容。新 Channel 调用旧 Server 的 StopTurn 会返回 Unimplemented;旧 runtime owner 不支持渠道追加日志。上线及回滚均应暂停入口、处理在途输出并协调切换全部相关实例。回滚不撤销已提交答案或已取消问题。
成本和边界
每个命令最多保留 8 MiB、32768 条原始事件,超限显式中断,遗留日志沿用 Backend TTL。此日志不是持久化渠道 outbox,不承诺外部消息 exactly-once,也不承诺断线或进程重启后完整补发。Memory 进程丢失、Redis 状态丢失仍是恢复边界。
代码量自审
远端 base 29ddb19,head 968bc28,按 base...head 统计:
没有独立批量机械变更;局部字段对齐仍计入生产行。新增生产代码主要用于两种 Backend 的追加/读取/消费权/释放、渠道日志读取、接受回执和中断处理。测试覆盖并发重试、输出突发、通知丢失、终态退出、存储失败、日志上限与释放、Web 隔离。少量问答/审批错误处理仍有重复,不为压行数扩大抽象。
自审修复:纯 Web 不再写渠道日志;并发重复提交只允许一个转发者;日志读取失败不再等待至请求超时。当前未发现新的已复现代码阻塞项。重点行为为渠道续跑、重复提交、停止等待及协调升级边界。
验证与真人 QA
组合源码 tree d1269ab 的全仓 go test ./... 通过;独立 PostgreSQL 的 fenced 问答持久化测试及既有 finishing migration 往返测试通过;独立 Redis 的通知丢失恢复、跨客户端消费权及释放测试通过。混合版本 RPC 测试确认旧 Run 兼容、新 Stop 对旧服务不兼容。本次同步 main 后,internal、cmd、db、go.mod、go.sum 与上述组合源码逐文件一致,保持这些验证的代码范围。
2026-09-06,用户在 @Memoh_test_bot 对组合版本明确确认真人 happy path 通过并提供截图:自由文字“0 点”提交后原样回复、原问题显示答案、再次提问后的选项答案与续跑回复送达。部署来源为 #1156 ec274d9 + #1159 1150071,版本 qa-1156-1159-latest。此次 main 同步保留已验证后端行为。
真人确认不覆盖 Other、多题、/stop、工具审批、群聊权限或跨实例故障恢复的完整人工验证;自动检查不替代这些人工项目。PR 最新 CI 由本次同步 main 后的检查结果单独确认。