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

  1. curl <baseURL>/models and compare the payload to OpenAI's {"data":[...]} shape.
  2. Fix the api base so it targets the provider's actual OpenAI-compatible route.
  3. If the provider only mirrors chat completions, use its native model-listing mechanism instead of this helper.
  4. 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

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


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/45f9a7be2d98065e. Report an issue: GitHub.