gastownhall/beads · error
unexpected response format: not a text block (type=%s)
Error message
unexpected response format: not a text block (type=%s)
What it means
callWithRetry expects the first content block of the model response to be a "text" block. When the API returns a different block type (e.g. "tool_use", "thinking", "image"), the client refuses it and returns this error. The response arrived and was billed, but its shape does not match what the summarizer can consume.
Source
Thrown at internal/compact/haiku.go:182
// Record token usage and latency.
modelAttr := attribute.String("bd.ai.model", h.model)
if aiMetrics.inputTokens != nil {
aiMetrics.inputTokens.Add(ctx, message.Usage.InputTokens, metric.WithAttributes(modelAttr))
aiMetrics.outputTokens.Add(ctx, message.Usage.OutputTokens, metric.WithAttributes(modelAttr))
aiMetrics.duration.Record(ctx, ms, metric.WithAttributes(modelAttr))
}
span.SetAttributes(
attribute.Int64("bd.ai.input_tokens", message.Usage.InputTokens),
attribute.Int64("bd.ai.output_tokens", message.Usage.OutputTokens),
attribute.Int("bd.ai.attempts", attempt+1),
)
if len(message.Content) > 0 {
content := message.Content[0]
if content.Type == "text" {
return content.Text, nil
}
return "", fmt.Errorf("unexpected response format: not a text block (type=%s)", content.Type)
}
return "", fmt.Errorf("unexpected response format: no content blocks")
}
lastErr = err
if ctx.Err() != nil {
return "", ctx.Err()
}
if !isRetryable(err) {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return "", fmt.Errorf("non-retryable error: %w", err)
}
}
if lastErr != nil {View on GitHub (pinned to 71377f2769)
Solutions
- Verify the configured base URL serves an Anthropic-compatible Messages API (config.DefaultAIBaseURL for the key source)
- Disable extended thinking / tool use for this model call so the first block is text
- Retry — block ordering can vary between responses
- Upgrade beads to a version whose response parsing handles additional block types
Example fix
// before
if content.Type == "text" {
return content.Text, nil
}
return "", fmt.Errorf("unexpected response format: not a text block (type=%s)", content.Type)
// after
for _, block := range message.Content {
if block.Type == "text" {
return block.Text, nil
}
}
return "", fmt.Errorf("unexpected response format: no text block") Defensive patterns
Strategy: fallback
Try / catch
summary, err := client.SummarizeTier1(ctx, issue)
if err != nil && strings.Contains(err.Error(), "not a text block") {
// one retry usually yields a text-first response
summary, err = client.SummarizeTier1(ctx, issue)
} Prevention
- Point the base URL at a genuinely Anthropic-compatible endpoint
- Disable extended thinking/tool-use features for summarization calls
- Pin an API version known to return text blocks first
- Upgrade beads when response-shape parsing changes
When it happens
Trigger: The Anthropic Messages API response's message.Content[0].Type != "text" during callWithRetry — e.g. the model returned a tool-use or thinking block first, or the endpoint/proxy is not actually an Anthric-compatible Messages API.
Common situations: Pointing baseURL at a MiniMax/OpenAI-compatible proxy that returns a non-Anthropic content shape; enabling extended thinking so a "thinking" block comes first; API version drift changing block ordering.
Related errors
- unexpected response format: no content blocks
- failed to parse work item states value: %w
- %w: set ANTHROPIC_API_KEY, MINIMAX_API_KEY, or ai.api_key in
- non-retryable error: %w
- failed after %d retries: %w
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/cc88e84be5b06fd6.
Report an issue: GitHub.