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

  1. Verify the configured base URL serves an Anthropic-compatible Messages API (config.DefaultAIBaseURL for the key source)
  2. Disable extended thinking / tool use for this model call so the first block is text
  3. Retry — block ordering can vary between responses
  4. 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

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


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/cc88e84be5b06fd6. Report an issue: GitHub.