Skip to content

[Bug/错误]: signature_delta attached to wrong block in Anthropic response #1105

@orzFly

Description

@orzFly

Pre-submission Checklist / 提交前检查

  • I have searched the existing issues and this bug has not been reported / 我已搜索现有 issues,此 Bug 尚未被报告
  • I have read the documentation / 我已阅读文档
  • I am using the latest version of AxonHub / 我正在使用 AxonHub 的最新版本

Bug Description / Bug 描述

通过 AxonHub 使用 Claude Code,发现经常一个 prompt 发起两个请求,一个流式,一个非流式。

claude --debug 的日志中可以看到 streaming error 的错误:

2026-03-19T11:34:48.362Z [DEBUG] Stream started - received first chunk
2026-03-19T11:34:49.881Z [ERROR] Error streaming, falling back to non-streaming mode: Content block is not a thinking block

最近的 Claude Code 更新,支持了显示 streaming 的内容,经常会发现常常流式输出完后,突然整个文本块变了。
即虽然流式产生了错误,但客户端仍在继续显示,并在后续同步请求完成后替换了这个文本快。

Steps to Reproduce / 复现步骤

  1. 在 Claude Code 里通过 AxonHub 使用 Opus,可以使用 claude --debug 打开调试日志
  2. /effort high
  3. prompt 「请帮我看看这个目录下有什么」
  4. 稍等片刻,在 AxonHub 里看到至少两个请求,一个是流式的,一个是非流式的。此时,claude --debug 的日志文件中也可见到错误。
2026-03-19T11:34:48.362Z [DEBUG] Stream started - received first chunk
2026-03-19T11:34:49.881Z [ERROR] Error streaming, falling back to non-streaming mode: Content block is not a thinking block

Expected Behavior / 期望行为

有个空的 thinking block, signature 在这个 thinking block,然后才是 tool_use。

event: message_start
data: {"type":"message_start","message":{"model":"claude-opus-4-6","id":"msg_111111111111111111111111","type":"message","role":"assistant","content":[],"stop_reason":null,"stop_sequence":null,"usage":{"input_tokens":3,"cache_creation_input_tokens":0,"cache_read_input_tokens":24857,"cache_creation":{"ephemeral_5m_input_tokens":0,"ephemeral_1h_input_tokens":0},"output_tokens":11,"service_tier":"standard","inference_geo":"not_available"}}             }

event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"thinking","thinking":"","signature":""}     }

event: ping
data: {"type": "ping"}

event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}               }

event: content_block_stop
data: {"type":"content_block_stop","index":0            }

event: content_block_start
data: {"type":"content_block_start","index":1,"content_block":{"type":"tool_use","id":"toolu_AAAAAAAAAAAAAAAAAAAAAAAA","name":"Bash","input":{},"caller":{"type":"direct"}}     }

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":""} }

--- 省略若干 content_block_delta  ---

event: content_block_delta
data: {"type":"content_block_delta","index":1,"delta":{"type":"input_json_delta","partial_json":"tory\"}"}         }

event: content_block_stop
data: {"type":"content_block_stop","index":1        }

Actual Behavior / 实际行为

空的 thinking block 丢了,signature 跑到后面的 tool_use block 去了。

event:message_start
data: {"type":"message_start","message":{"id":"msg_AAAAAAAAAAAAAAAAAAAAAAAA","type":"message","role":"assistant","content":[],"model":"claude-opus-4-6","usage":{"input_tokens":3,"output_tokens":11,"cache_creation_input_tokens":14929,"cache_read_input_tokens":10107,"cache_creation":{"ephemeral_5m_input_tokens":14929,"ephemeral_1h_input_tokens":0}}}}

event:content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"tool_use","id":"toolu_BBBBBBBBBBBBBBBBBBBBBBBB","name":"Bash","input":{}}}

event:content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":""}}

--- 省略若干 content_block_delta  ---

event:content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"input_json_delta","partial_json":"ory\"}"}}

event:content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"signature_delta","signature":"QVhOMTAxAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"}}

Operating System / 操作系统

Linux (Other) / Linux(其他)

AxonHub Version / AxonHub 版本

v0.9.18

Usage Scenario / 使用场景

Claude Code

API Format / API 格式

Anthropic - Messages (/v1/messages) / Anthropic Messages

Channel Type / 渠道类型

Anthropic / Anthropic

Logs / 日志

redacted_axonhub_response.txt
redacted_upstream_response.txt

Additional Context / 其他补充信息

Claude Opus 4.6 对 @anthropic-ai/claude-agent-sdk 的分析

根据代码分析,错误信息 "Content block is not a thinking block" 来自 streaming 事件处理中的 content_block_delta 分支。具体在这段逻辑中:

case "content_block_delta": {
    let j8 = $6[J8.index]; // 根据 index 获取之前 content_block_start 创建的 block
    
    switch (J8.delta.type) {
        // ...
        case "signature_delta":
            if (j8.type !== "thinking")
                throw Error("Content block is not a thinking block");
            j8.signature = J8.delta.signature;
            break;
        case "thinking_delta":
            if (j8.type !== "thinking")
                throw Error("Content block is not a thinking block");
            j8.thinking += J8.delta.thinking;
            break;
    }
}

触发原因

API 在 streaming 过程中发送了一个 thinking_deltasignature_delta 事件,但该事件对应的 content_block index 指向的块不是 thinking 类型

也就是说:

  1. content_block_start 事件为某个 index 创建了一个非 thinking 类型的块(比如 texttool_use
  2. 随后收到的 thinking_delta / signature_delta 却引用了同一个 index

这是一个 API 服务端 streaming 的 bug——content block 的 index 或类型出现了不一致。客户端的类型校验发现了这个不匹配,抛出异常后走到了 catch 分支:

} catch (t) {
    // ...
    k(`Error streaming, falling back to non-streaming mode: ${s6(t)}`, {level: "error"});
    // --> 这就是你看到的那条日志
    // 然后发起 non-streaming fallback 请求 (CNq)
}

总结

项目 详情
错误来源 客户端 streaming 类型校验
直接原因 收到 thinking_deltasignature_delta,但对应 index 的 content block 不是 thinking 类型
根因 API 服务端 streaming 返回了 index/类型不匹配的事件(服务端 bug 或瞬态错误)
后续行为 客户端捕获异常,回退到 non-streaming 模式重新发送请求

这不是你这边能修的问题,是 Anthropic API streaming 端的一个偶发异常。fallback 机制正是为了应对这类情况——streaming 失败后用 non-streaming 兜底,保证请求最终能成功。

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions