{"record":{"id":"ab026ad09aa89c40","repo":"charmbracelet/crush","slug":"failed-to-decode-mcp-pending-auth-w","errorCode":null,"errorMessage":"failed to decode MCP pending auth: %w","messagePattern":"failed to decode MCP pending auth: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":344,"sourceCode":"\t\treturn nil, fmt.Errorf(\"failed to decode MCP states: %w\", err)\n\t}\n\treturn states, nil\n}\n\n// MCPPendingAuth retrieves the MCP servers awaiting OAuth authentication\n// for a workspace.\nfunc (c *Client) MCPPendingAuth(ctx context.Context, id string) ([]proto.MCPPendingAuthServer, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/mcp/pending-auth\", id), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get MCP pending auth: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get MCP pending auth: status code %d\", rsp.StatusCode)\n\t}\n\tvar pending []proto.MCPPendingAuthServer\n\tif err := json.NewDecoder(rsp.Body).Decode(&pending); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode MCP pending auth: %w\", err)\n\t}\n\treturn pending, nil\n}\n\n// MCPAuthURL retrieves the current OAuth authorization URL for a named MCP\n// server, if a flow is in progress.\nfunc (c *Client) MCPAuthURL(ctx context.Context, id, name string) (string, error) {\n\tq := url.Values{\"name\": []string{name}}\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/mcp/auth-url\", id), q, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get MCP auth URL: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"failed to get MCP auth URL: status code %d\", rsp.StatusCode)\n\t}\n\tvar resp proto.MCPAuthResponse\n\tif err := json.NewDecoder(rsp.Body).Decode(&resp); err != nil {","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L326-L362","documentation":"After a successful 200 response from /workspaces/{id}/mcp/pending-auth, the client decodes the body into []proto.MCPPendingAuthServer with json.NewDecoder. If the body is not valid JSON or does not match the expected shape, this error wraps the json.Decoder failure. It indicates a client/server contract mismatch rather than an HTTP problem.","triggerScenarios":"Calling MCPPendingAuth when the server returns 200 with a non-JSON body (HTML error page from a misconfigured proxy), truncated JSON, or JSON whose structure cannot unmarshal into []proto.MCPPendingAuthServer (e.g. object instead of array, wrong field types).","commonSituations":"Hitting the wrong port or a dev proxy that injects an HTML login page with status 200; server and client versions out of sync so the payload schema changed; an empty body returned by a stub/mock endpoint.","solutions":["Log rsp.Body (or a tee'd copy) on failure to see what the server actually returned.","Confirm the endpoint is the real API server, not a proxy/captive portal returning HTML — check the Content-Type header is application/json.","Align client and server versions so the MCPPendingAuthServer schema matches the running server.","If you control the server, fix the handler to always emit a JSON array (use [] not null/empty body) for this route."],"exampleFix":"// before\nvar pending []proto.MCPPendingAuthServer\nif err := json.NewDecoder(rsp.Body).Decode(&pending); err != nil {\n    return nil, fmt.Errorf(\"failed to decode MCP pending auth: %w\", err)\n}\n\n// after (capture body for diagnosis and verify content type)\nif ct := rsp.Header.Get(\"Content-Type\"); !strings.Contains(ct, \"application/json\") {\n    body, _ := io.ReadAll(rsp.Body)\n    return nil, fmt.Errorf(\"unexpected content type %q: %s\", ct, body)\n}\nvar pending []proto.MCPPendingAuthServer\nif err := json.NewDecoder(rsp.Body).Decode(&pending); err != nil {\n    return nil, fmt.Errorf(\"failed to decode MCP pending auth: %w\", err)\n}","handlingStrategy":"validation","validationCode":"// Verify the endpoint speaks JSON before trusting the decode.\n// If you own the transport, check headers on the response:\n// if !strings.Contains(rsp.Header.Get(\"Content-Type\"), \"application/json\") { ... bail ... }\n// Caller-side sanity check of the decoded result:\nservers, err := client.MCPPendingAuth(ctx, id)\nif err != nil {\n    return err\n}\nif servers == nil {\n    return errors.New(\"server returned null pending-auth payload\")","typeGuard":"func validPendingAuth(v []proto.MCPPendingAuthServer) bool {\n    for _, s := range v {\n        if s.Name == \"\" {\n            return false\n        }\n    }\n    return true\n}","tryCatchPattern":"servers, err := client.MCPPendingAuth(ctx, id)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to decode MCP pending auth\") {\n        // contract mismatch: log server env/version, fall back to empty list\n        log.Warn(\"pending-auth payload unusable; assuming no pending servers\", \"err\", err)\n        return nil, nil\n    }\n    return err\n}","preventionTips":["Ensure no proxy sits between client and server that can inject HTML with a 200 status.","Keep client and server binaries on matching versions so the MCPPendingAuthServer schema agrees.","Test against the real server, not stubs returning empty 200 bodies.","When you control the server, always emit a JSON array (never null or empty body) for this route."],"tags":["json","decoding","mcp","client"],"backgroundTag":"json-decode-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}