Skip to content

[code-review] openai.go mid-stream retrySleep 失败分支漏设 streamError — Error 之后再发带 usage 的 Done,违反终止事件契约 #181

Description

@topcheer

文件与行号

internal/provider/openai.go:485-487(mid-stream retry 的 retrySleep 失败分支)对照 :401-404(connect 阶段对称路径)与 anthropic.go :436-439

问题描述

流式请求收到可重试错误且尚未 emit 内容时进入 mid-stream retry;retrySleep 等待期间 ctx 取消 → 发送 StreamEventError 后直接 return,但没有设置 streamError = true。外层 L590 if !streamError 随后计算 usage 并发送 StreamEventDone → 消费者先收到 Error 再收到 Done,终止事件契约(恰好一个 Error 或 Done)被违反。

三处对称代码中两处正确设置了标志(openai connect 阶段、anthropic mid-stream),唯独此处漏写,不对称性证明是疏忽。gemini.go 无此问题(其 return 直接退出 goroutine)。

触发场景

connect 成功 + 首个 Recv 返回可重试错误(429/5xx)且未 emit + backoff 期间(最长 30s,Retry-After)用户 Ctrl+C/会话超时 → Error 后紧跟带 usage 的 Done。

预期行为 vs 实际行为

  • 预期:终止事件恰好一个(Error)
  • 实际:Error 之后又发 Done(usage=0)

主消费路径(agent.go L4559 在首个 Error 即 return)当前被意外保护,实际故障面为零;但 fallback 透传层及未来任何读完 channel 的消费者会把带 Usage 的 Done 解读为成功完成。

修复建议

L486 后补一行 streamError = true,与 L403、anthropic L438 对齐。

严重程度

Low(协议契约违规,经独立 subagent 复核确认;现网消费路径短路保护,属潜在契约陷阱)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions