NanoClaw 的 Codex 提供商出现“Turn timed out after 600000ms”,意味着 Codex 的 Responses WebSocket 停止发送事件,而 Codex 报告的第一条错误要等到 NanoClaw 固定的十分钟回合计时器已经触发之后才会出现。Codex 会为闲置流等待五分钟,第一次重试时保持静默,只报告第二次重试。我们于 2026 年 10 月 1 日使用 Codex 0.138.0——错误报告中的版本——以及 NanoClaw 当前固定使用的 0.155.1,在一个接受连接后保持静默的本地端点上复现了该问题。
这是 NanoClaw 问题 #3338 背后的复现案例;检查时,该问题仍处于未关闭状态,且尚无维护者回复。关于提供商的选择,请参阅 NanoClaw API 成本与提供商;如果机器人因其他原因在 Telegram 上没有回应,请参阅 NanoClaw Telegram 无响应。
你看到的情况
发送给机器人的消息十分钟内没有任何回复——没有错误,也没有部分回复——然后才出现错误。期间发送的消息似乎消失了。日志如下:
# Codex, inside the agent container (from the issue; our 0.155.1 run printed the same reason)
stream error: idle timeout waiting for websocket
stream disconnected - retrying sampling request (1/5 ...)
# NanoClaw, ten minutes after the message
Error: Turn timed out after 600000ms复现结果
| 回合开始后的秒数 | Codex 0.138.0 | Codex 0.155.1(NanoClaw 固定使用的版本) |
|---|---|---|
| ≈1–16 | 第一条请求通过 WebSocket 发送;没有回复 | 预热请求在 16 秒时由 Codex 关闭;随后发送真正的请求 |
| ≈300–316 | 第一条请求超时;Codex 再次发送该回合的请求——没有通知 | 空闲超时;记录了重试 1——没有通知 |
| 600 | NanoClaw 的计时器结束回合:“Turn timed out after 600000ms” | |
| ≈601–616 | 空闲超时;记录了“retrying sampling request (1/5)”——仍然没有通知(750 秒内完全没有通知) | 第一条 error 通知:“Reconnecting... 2/5”,“idle timeout waiting for websocket”——晚了 15 秒 |
该端点是一个接受 WebSocket 但不发送任何内容的替身;客户端是真实的 Codex app-server 二进制程序,使用与 NanoClaw 相同的调用驱动,并按照 NanoClaw 自身的规则判断——任何错误通知或已完成的回合都会结束回合,否则由十分钟计时器结束。
为什么时间安排会如此糟糕地重叠
- Codex 会等待 300 秒,等待 Responses 流中的下一个事件,然后才将流视为失效,并最多重试五次。这是两个版本中内置 OpenAI 提供商的默认设置。
- Codex 会隐藏第一次 WebSocket 重试。其重试代码在注释中明确说明:“在发布版本中,隐藏第一次 websocket 重试通知,以减少嘈杂的临时重连消息”,因此前端要到第二次重试时才会听到任何内容,大约是在十分钟后。
- NanoClaw 为 Codex 回合精确分配 600,000 ms,并在收到任何错误通知时结束回合。两个空闲窗口加上 Codex 的预热时间超过了这一时长,因此在默认设置下,计时器会先结束。
沉默期间发送的消息会丢失
NanoClaw 会将活动回合期间到达的后续消息发送到 Codex 的 turn/steer。在该运行中,Codex 将其接收到卡住的回合中,没有向上游发送任何新内容;超时后恢复会话时,历史中有原始提示,但没有被引导的消息。这就是机器人看起来完全卡住的原因:你输入的所有内容都进入了一个永远不会结束的回合。
摆脱卡顿
已验证:超时后,下一条消息会在新的 app-server 上恢复同一会话——这是 NanoClaw 的行为——在端点响应后,该回合在我们的运行中于 0.16 秒内完成。因此:等待超时,然后重新发送沉默期间输入的内容。如果下一条消息也卡住,上游连接仍然存在问题——检查 OpenAI 的状态;如果容器通过代理连接 OpenAI,还要检查该代理是否传递 WebSocket 升级请求(拉取请求 #2672 描述了不支持的代理;这里未测试)。
可以缩短等待时间的选项,已测量但尚未发布:
| 更改 | NanoClaw 看到的第一条错误 | 注意事项 |
|---|---|---|
| 通过 HTTP/SSE 而不是 WebSockets 运行 Codex——PR #3851,开放,尚未合并 | 300 秒:“Reconnecting... 1/5” | 通知表示 Codex 将重试,而且它确实重试了,但 NanoClaw 会因此结束回合——从十分钟失败缩短为五分钟失败 |
Codex stream_idle_timeout_ms = WebSockets 上的 60,000 | 136 秒 | NanoClaw 会自行写入 Codex 的 config.toml,因此这是代码更改,而不是可以添加的设置 |
两者仍然都会将回合作为失败结束。测量结果指向 NanoClaw 这一侧——应将正在重试的错误视为进展而不是结束,或将回合预算从上一个真正事件开始计算——但这只是基于这些运行结果的推断,并非维护者已确认的修复方案。
常见问题
为什么 NanoClaw 的 Codex 代理会沉默十分钟,然后超时?
因为 Codex 愿意报告的第一个故障信号出现时,NanoClaw 已经放弃等待。当 Responses WebSocket 停止发送事件时,Codex 会等待 300 秒的空闲超时,然后重试——而在发布版本中,它会刻意隐藏第一次 WebSocket 重试。第一次错误通知要等到第二个 300 秒窗口之后才会出现,此时已经超过 NanoClaw 固定的 600,000 ms 回合计时器。我们于 2026 年 10 月 1 日复现了这一问题:Codex 0.138.0 在 750 秒内完全没有发送通知,而 NanoClaw 当前固定使用的 0.155.1 在 615.7 秒时发送了第一条通知;NanoClaw 的规则在 600 秒时结束了回合。
我在机器人卡住期间发送的消息会怎样?
它们会被导入卡住的回合中,并且在我们的运行中丢失。NanoClaw 会将活动回合期间到达的后续消息路由到 Codex 的 turn/steer;Codex 将其接收到同一个卡住的回合中,没有向上游发送任何新内容,超时并恢复后,被引导的文本也不在会话历史中。机器人再次回复后,请重新发送沉默期间输入的任何内容。
如何从 NanoClaw 的 Codex 回合超时中恢复?
发送下一条消息。NanoClaw 会启动新的 app-server 并恢复同一会话;在我们的运行中,端点开始响应后,恢复的回合在 0.16 秒内完成,原始提示仍在历史中。如果上游仍然卡住,新回合也会以同样方式卡住,因此先检查 OpenAI 的状态或你的网络路径——并重新发送被吞掉的后续消息。
将 Codex 从 WebSockets 切换到 HTTP 能解决问题吗?
它会缩短沉默时间,但尚未合并。拉取请求 #3851(开放状态,由该问题的报告者提交)添加了 NANOCLAW_CODEX_TRANSPORT=http,使 Codex 通过 HTTP/SSE 运行。在我们对该方案的运行中,第一条错误通知在 300 秒时到达,而不是在计时器之后——但它携带 willRetry true,而此时 Codex 已经在重新发送;NanoClaw 会在任何错误通知上结束回合,因此回合会在五分钟时失败,而不是恢复。
这是一般性的 Codex 超时吗?
不是。Codex 自身会继续重试;十分钟的沉默来自 NanoClaw 的回合计时器与 Codex 隐藏的第一次重试之间的配合方式。在 0.155.1 中,app-server 大约在 616 秒时报告了“Reconnecting... 2/5”——拥有更长回合预算的客户端可以看到它;NanoClaw 永远看不到。NanoClaw 的默认提供商是 Claude Agent SDK,与此无关。
于 2026 年 10 月 1 日复现,使用从 npm 获取的原生 codex app-server 二进制文件(@openai/codex 0.138.0 和 0.155.1,darwin-arm64),通过 stdio 传入 NanoClaw providers 分支(提交 3959d1f055)中的 initialize、thread/start、turn/start 和 turn/steer 参数,针对通过 WebSocket 和 HTTP/SSE 通信的本地 Responses 模拟端点进行测试。Codex 默认设置以及隐藏的首次重试逻辑读取自 codex-rs 的 rust-v0.138.0 和 rust-v0.155.1 标签。未运行:NanoClaw 的编排器和容器、OneCLI、Telegram,也未调用 OpenAI 的真实端点——该模拟端点复现的是静默流,而不是导致 OpenAI 流静默的原因。问题和拉取请求状态于同日读取。