Skip to content

[Bug]: split 模式 gRPC turn 边界丢失 ErrSessionBusy 哨兵,session 占槽被报成 Internal RPC error 发给用户 #1153

Description

@qqqqqf-q

Bug Description

split 部署(server↔channel 走 internal gRPC)下,当一个 session 已有一个 run 处于 waiting_decision(例如 ask_user 挂起等待作答)时,用户发来任何新消息,admission 会正确拒绝为 ErrSessionBusy(internal/agent/runtime/session/ledger/postgres.go:496-501)。按设计,channel 收到 busy 应静默延迟、等平台重投(internal/channel/inbound/channel.go:1234-1241 分支,且不该让用户看到任何错误)。

但 gRPC 边界 mapError(internal/agent/turn/grpctransport/server.go:236-255)的 sentinel 映射表只有 ErrDuplicateTurn / ErrTeamNotServed / context.Canceled / context.DeadlineExceeded 四项,漏了 ErrSessionBusy → 落入 default 分支,返回 codes.Internal。sentinel 一过 gRPC 即被销毁,channel 侧 errors.Is(startErr, turn.ErrSessionBusy) 永不匹配,busy 分支在 split 模式下完全失效,错误被原样作为聊天消息发给用户。

对照:WS/HTTP 边界对同一 sentinel 有正确映射(internal/handlers/local_channel.go:1259-1267 映射为 session_runtime.session_busy 的 run_rejected 事件),只有 gRPC turn 边界漏了。

注意:即使哨兵正确传递,现有 busy 处理假设「平台会重投」对 Telegram 长轮询也不成立——普通消息不会重投,嵌入模式下这条消息会被静默吞掉。这需要在修复时一并考虑(至少给用户一个可读提示,或将消息排队)。

Steps to Reproduce

  1. split 模式部署(internal_rpc.shared_secret 非空),bot 接 Telegram。
  2. 让 bot 触发 ask_user(产生 pending 问题,run 进入 waiting_decision)。
  3. 不用按钮,直接在输入框发任意文本。
  4. bot 回复 Error: rpc error: code = Internal desc = internal turn operation failed

Error Messages / Logs

# 用户可见(Telegram 聊天消息)
Error: rpc error: code = Internal desc = internal turn operation failed

# server 侧(component=turn_rpc)
level=ERROR msg="internal turn rpc failed" operation="start turn" error="turn: thread already has a run in flight: thread <id>"

# channel 侧
level=ERROR msg="start turn failed" error="rpc error: code = Internal desc = internal turn operation failed"

Version

main(2026-09-03,274128aa1 引入 turn 边界后即存在)

Channel

Telegram(所有走 gRPC turn 边界的渠道均受影响;waiting_decision 占槽期间每条新消息都会触发)

Additional Context

  • 影响放大器:waiting_decision 的 run 无 ExpiresAt 且租约持续续期,会永久占住 session 唯一活跃槽;/stop 无效(只取消 activeStreams,park 时条目已删)。一旦进入该状态,session 内后续每条消息都撞同一堵墙。用户恢复手段:Web UI 回答该问题,或 /new 开新会话。
  • 修复方向:mapErrorErrSessionBusy case(建议 codes.Aborted 或可识别 message),并在 client 侧 mapClientError 还原为 turn.ErrSessionBusy,对齐 WS/HTTP 边界行为。

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions