{"record":{"id":"45f9a7be2d98065e","repo":"sipeed/picoclaw","slug":"decode-response-unrecognized-shape-s","errorCode":null,"errorMessage":"decode response: unrecognized shape: %s","messagePattern":"decode response: unrecognized shape: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/picoclaw/internal/model/online.go","lineNumber":79,"sourceCode":"\t// from \"object without a data key\" via Data being non-nil after unmarshal:\n\t// json.Unmarshal sets Data to []modelEntry{} for `{\"data\":[]}` but leaves\n\t// it as nil when \"data\" is absent or null.\n\tvar envelope modelsAPIResponse\n\tif err := json.Unmarshal(body, &envelope); err == nil && envelope.Data != nil {\n\t\treturn envelope.Data, nil\n\t}\n\n\t// Bare-array shape, including `[]`.\n\tvar arr []modelEntry\n\tif err := json.Unmarshal(body, &arr); err == nil {\n\t\treturn arr, nil\n\t}\n\n\tpreview := body\n\tif len(preview) > 256 {\n\t\tpreview = preview[:256]\n\t}\n\treturn nil, fmt.Errorf(\"decode response: unrecognized shape: %s\", strings.TrimSpace(string(preview)))\n}\n","sourceCodeStart":61,"sourceCodeEnd":81,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/cmd/picoclaw/internal/model/online.go#L61-L81","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// provider returns {\"models\":[...]} — extend the envelope struct\n// before\ntype modelsAPIResponse struct {\n    Data []modelEntry `json:\"data\"`\n}\n// after\ntype modelsAPIResponse struct {\n    Data   []modelEntry `json:\"data\"`\n    Models []modelEntry `json:\"models\"`\n}","handlingStrategy":"type-guard","validationCode":"var probe struct {\n    Data json.RawMessage `json:\"data\"`\n}\n_ = json.Unmarshal(body, &probe)\nvar arr []json.RawMessage\nisEnvelope := probe.Data != nil\nisBareArray := probe.Data == nil && json.Unmarshal(body, &arr) == nil\nif !isEnvelope && !isBareArray {\n    return fmt.Errorf(\"unsupported /models shape: %s\", preview(body))\n}","typeGuard":"func isOpenAIModelsShape(body []byte) bool {\n    var env struct {\n        Data []modelEntry `json:\"data\"`\n    }\n    if err := json.Unmarshal(body, &env); err == nil && env.Data != nil {\n        return true\n    }\n    var arr []modelEntry\n    return json.Unmarshal(body, &arr) == nil\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["json","decode","api-compatibility","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}