Skip to content

bug(runtime): HTTP 200 hides retryable transport errors and stops recovery #5656

Description

@sunsunsun-java

What happened

Maka Desktop 在执行一个多步骤任务时中断了。界面提示“任务出错”,诊断信息是:

Failed to process successful response (status=200)
unknown · HTTP 200 · retryable false

通俗地说:服务器已经回复“请求成功”(HTTP 200),但后续处理响应失败,Maka 没有继续尝试,而是结束了任务。此前同一任务里的超时、503 都曾重试成功,最后一次却只尝试了一次。

排查时发现一个可以单独复现的分类缺陷:同一个底层连接重置错误,没有 HTTP 状态码时会被判为可重试的网络故障;外面多包了一层 HTTP 200,就会变成 unknown / retryable: false。 收到成功响应头并不代表响应内容已经完整接收,后续仍可能断线。

**证据边界:**真实事故已经确认的是 retry: { decision: "declined", because: "policy" },但现存日志没有保存底层 cause,所以不能证明当时一定是 ECONNRESET 或 UND_ERR_SOCKET,也不能排除解析错误。本 issue 报告的是排查中复现的“HTTP 200 遮住底层传输错误”缺口,真实事故作为相关现场证据。

期望行为:

  • HTTP 2xx 包装下,如果有明确的可恢复网络错误证据,应进入现有的安全重试判断。
  • 安全条件、取消状态和预算仍然有效;不能重复执行已经完成的工具。
  • 不要把所有 HTTP 200 处理错误都设为可重试。没有网络证据的解析/校验错误,需要与断线区分。

How to reproduce

实际发生条件(间歇性)

  1. 在 Maka Desktop 使用 openai-codex/gpt-6-astra。
  2. 发起一个需要连续调用工具的任务。本次任务是检查 Maka 的本机数据存储位置及是否支持修改路径,执行了设置查询、只读 shell 检查和读取工具输出。
  3. 同一轮先出现模型超时、503,并自动重试成功。
  4. 一个 Read 续读参数错误之后,运行时仍继续发起了下一次模型请求。
  5. 最后一次模型请求约 513.7 秒后失败,记录 HTTP 200 / unknown / retryable false;整轮约 17 分 54 秒,任务终止。

这里只保留了一次具有完整诊断证据的现场记录,尚未通过真实网络故障注入稳定重现该事故,不提供发生概率。

可确定复现的分类器输入

在依赖和 workspace 已按贡献指南安装、构建后,可以从仓库根目录用下面的 ESM 代码检查分类器(该代码不发网络请求,也不执行工具):

import { APICallError } from '@ai-sdk/provider';
import {
  providerModelFailure,
  providerFailureDiagnostic,
} from './packages/runtime/dist/provider-error-classification.js';

const cases = [
  ['reset_without_status', undefined,
    Object.assign(new Error('socket reset'), { code: 'ECONNRESET' })],
  ['reset_after_200', 200,
    Object.assign(new Error('socket reset'), { code: 'ECONNRESET' })],
  ['socket_after_200', 200,
    Object.assign(new Error('other side closed'), { code: 'UND_ERR_SOCKET' })],
  ['parse_error_after_200', 200,
    new SyntaxError('Unexpected token')],
];

for (const [test, statusCode, cause] of cases) {
  const error = new APICallError({
    message: 'Failed to process successful response',
    url: 'https://example.invalid',
    requestBodyValues: {},
    statusCode,
    cause,
  });
  console.log(JSON.stringify({
    test,
    failure: providerModelFailure(error),
    diagnostic: providerFailureDiagnostic(error),
  }));
}

已实际执行的验证使用已安装版本中真实的 APICallError、RetryError、分类器及其辅助依赖,在内存中加载;没有替换这些依赖的行为。结果如下:

输入 实际分类 实际重试资格 期望
无 HTTP 状态,底层 ECONNRESET network true 保持
HTTP 200,底层 ECONNRESET unknown false 识别网络故障,交由现有安全策略判断
HTTP 200,底层 UND_ERR_SOCKET unknown false 同上
HTTP 200,底层 SyntaxError unknown false 不应仅因 HTTP 200 自动获得网络重试资格

最新源码的相同分支已经静态核对,但尚未在新拉取的源码 checkout 中安装依赖、构建或运行项目测试;上面的源码导入形式是供维护者复核使用,不代表已完成该 checkout 的测试。

Environment

现场环境:

  • Maka: 0.2.0-dev.47.20260922
  • Build / Channel: packaged / nightly
  • Surface: Desktop / local Runtime Host
  • OS: macOS, Darwin 24.6.0, arm64
  • Electron: 43.4.1
  • Chrome: 150.0.7871.224
  • Node: 24.18.1(应用内置运行时)
  • Locale: zh-CN
  • Provider / Model: openai-codex / gpt-6-astra
  • Diagnostic captured at: 2026-09-23T14:27:46.099Z
  • 报告时 local Runtime Host 为 ready,没有捕获到 Runtime Host 进程退出。

后续源码核对基准:

  • main commit: fef5b937a58a8d76c64303fbe4f57e59c20bfe8a
  • 该版本仍保留下述两处提前返回。

Logs, screenshots, or additional context

下面是用户提供的诊断报告及本机数据库中的相关原始字段。已省略会话/任务标识、个人目录和无关日志;未上传数据库、凭据、请求体或完整应用数据。省略字段不表示原始记录中不存在这些字段。

1. Desktop 诊断报告摘录

Maka Desktop diagnostic report
Captured at: 2026-09-23T14:27:46.099Z

Error
Surface: toast
Title: 任务出错
Description: 出错了,暂时无法确定原因。

Reason: unknown
Code: <none>
Recoverable: false
Message: Failed to process successful response (status=200)

Recent local Runtime Host process exits (0)
<none captured>

Runtime Host connections (1)
"local": ready

Runtime Host
State: ready

Runtime Host execution
Duration: 1074033ms
Failure: tool_failed
Failure message: Failed to process successful response (status=200)

该报告的 Failure: tool_failed 与终止模型错误并不一致,因此这里不把它作为网络故障的证据。终止决策以接下来的原始事件为准;诊断归因问题不在本 issue 的修复范围。

同一报告中相关步骤的原始内容摘录(省略步骤 ID 和无关步骤):

model main · openai-codex/gpt-6-astra · completed · 232301ms · 3 attempt(s)
  failed · 120003ms · timeout · provider MODEL_STREAM_TIMEOUT · retryable true
  failed · 1285ms · provider_unavailable · HTTP 503 · provider biscuit_baker_service_me_circuit_open · retryable true
  completed · 107598ms

model main · openai-codex/gpt-6-astra · completed · 102539ms · 2 attempt(s)
  failed · 72259ms · provider_unavailable · HTTP 503 · provider server_is_overloaded · retryable true
  completed · 29163ms

tool Read · failed · 5ms · recovery never_auto_retry
model main · openai-codex/gpt-6-astra · failed · 513693ms · 1 attempt(s)
  failed · 513693ms · unknown · HTTP 200 · provider 200 · retryable false
error · Failed to process successful response (status=200)

前一个 Read 的工具错误是:

Invalid Read continuation. Copy the complete next object from the preceding result. To start again, use the original path and omit offset and limit.

这是工具参数问题,随后仍然发生了模型请求。没有证据证明它导致了最终的响应处理异常。

2. runtime_events 中的终止事件

只读查询原始 payload_json,保留以下字段原值:

{
  "actions": {
    "endInvocation": true,
    "stateDelta": {
      "failureClass": "unknown",
      "stopReason": "error"
    }
  },
  "content": {
    "code": "200",
    "kind": "error",
    "message": "Failed to process successful response (status=200)",
    "reason": "unknown",
    "retry": {
      "because": "policy",
      "decision": "declined"
    }
  },
  "status": "failed",
  "ts": 1790173660162
}

这里记录的是策略拒绝重试,不是重试次数耗尽。

3. core_agent_run_events 中最后一次模型尝试

事件类型 model_call_attempt_recorded,以下为 data 中的原始字段:

{
  "step": 7,
  "attempt": 0,
  "providerId": "openai-codex",
  "modelId": "gpt-6-astra",
  "startedAt": 1790173146458,
  "completedAt": 1790173660151,
  "latencyMs": 513693,
  "timeToFirstTokenMs": 12689,
  "status": "failed",
  "errorClass": "unknown",
  "httpStatus": 200,
  "retryable": false,
  "providerCode": "200"
}

4. 同一轮 model_stream_failed 的原始字段

{
  "errorClass": "unknown",
  "rawErrorName": "object",
  "rawErrorType": "object",
  "redactedErrorMessage": "{\"type\":\"model_failure\",\"kind\":\"unknown\",\"retryable\":false,\"code\":\"200\",\"message\":\"Failed to process successful response (status=200)\"}"
}

这里已经是归一化后的失败对象,没有这次真正的底层 cause,所以不能从这条记录断言具体是哪一种网络或解析错误。

源码证据与建议边界

在上述 main commit 中:

  1. isTransportFailure,179–203 行:if (statusCode) return false,有 HTTP 状态码就不再检查底层传输错误码。
  2. extractProviderErrorFacts,420–442 行:发现状态码后立即返回,也会停止继续读取 cause。
  3. 已有测试,430–459 行:覆盖没有 HTTP 响应和仅有底层错误码的场景,没有覆盖本 issue 的“HTTP 200 + 底层传输故障”组合。

建议只补齐成功响应后的传输证据识别,复用现有重试门槛、次数和退避;不放宽工具副作用保护,不重跑整个任务,也不将普通解析错误一律设为可恢复。

建议回归验证:

  • HTTP 200 + ECONNRESET / UND_ERR_SOCKET 能识别为网络故障,且执行与诊断分类一致。
  • 同一形状下的纯解析错误、无底层证据错误,不被自动提升为网络故障。
  • 首次故障后下一次成功时,安全边界内的任务能继续。
  • 已完成工具不重复执行;用户取消、重试耗尽和不安全边界仍正确阻止后续尝试。

相关但不完全相同:


自动化披露:本 issue 由 Maka(AI 助手)根据用户提供的诊断报告、本机只读排查及分类器验证整理,并按用户授权发布。真实事故、构造复现和源码静态检查已分别标明。

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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions