sipeed/picoclaw · error
decode response: unrecognized shape: %s
Error message
decode response: unrecognized shape: %s
What it means
The 200 response body matched neither accepted shape: not the OpenAI {"data":[...]} envelope (where Data must be non-nil after unmarshal) and not a bare JSON array of models. Both json.Unmarshal attempts failed, so the error embeds the first 256 bytes of the body to reveal the actual shape.
Source
Thrown at cmd/picoclaw/internal/model/online.go:79
// from "object without a data key" via Data being non-nil after unmarshal:
// json.Unmarshal sets Data to []modelEntry{} for `{"data":[]}` but leaves
// it as nil when "data" is absent or null.
var envelope modelsAPIResponse
if err := json.Unmarshal(body, &envelope); err == nil && envelope.Data != nil {
return envelope.Data, nil
}
// Bare-array shape, including `[]`.
var arr []modelEntry
if err := json.Unmarshal(body, &arr); err == nil {
return arr, nil
}
preview := body
if len(preview) > 256 {
preview = preview[:256]
}
return nil, fmt.Errorf("decode response: unrecognized shape: %s", strings.TrimSpace(string(preview)))
}
View on GitHub (pinned to 49183d7e8d)
Solutions
- curl <baseURL>/models and compare the payload to OpenAI's {"data":[...]} shape.
- Fix the api base so it targets the provider's actual OpenAI-compatible route.
- If the provider only mirrors chat completions, use its native model-listing mechanism instead of this helper.
- If the shape is a legitimate variant, extend modelsAPIResponse (e.g. add a models field) or pre-transform the payload before decoding.
Example fix
// provider returns {"models":[...]} — extend the envelope struct
// before
type modelsAPIResponse struct {
Data []modelEntry `json:"data"`
}
// after
type modelsAPIResponse struct {
Data []modelEntry `json:"data"`
Models []modelEntry `json:"models"`
} Defensive patterns
Strategy: type-guard
Validate before calling
var probe struct {
Data json.RawMessage `json:"data"`
}
_ = json.Unmarshal(body, &probe)
var arr []json.RawMessage
isEnvelope := probe.Data != nil
isBareArray := probe.Data == nil && json.Unmarshal(body, &arr) == nil
if !isEnvelope && !isBareArray {
return fmt.Errorf("unsupported /models shape: %s", preview(body))
} Type guard
func isOpenAIModelsShape(body []byte) bool {
var env struct {
Data []modelEntry `json:"data"`
}
if err := json.Unmarshal(body, &env); err == nil && env.Data != nil {
return true
}
var arr []modelEntry
return json.Unmarshal(body, &arr) == nil
} Prevention
- Keep a fixture test per supported provider shape in the model package.
- Record each new provider's /models payload as a golden file before shipping its config.
- Surface the embedded body preview in logs — the fastest way to spot gateways that rewrite payloads.
When it happens
Trigger: A 200 response whose JSON is shaped differently — {"object":"list","models":[...]} without a "data" key, a nested/wrapped payload, or an HTML page served with status 200 — reaches the fallthrough at online.go:79.
Common situations: A provider that is OpenAI-compatible for chat but uses a different /models schema, an API gateway that wraps responses, or an api base that lands on a docs/login page.
Related errors
- marshal result: %w
- marshal request: %w
- parse response: %w
- failed to load config: %w
- decode JSON response: %w
AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15).
Data as JSON: /api/errors/45f9a7be2d98065e.
Report an issue: GitHub.