{"record":{"id":"15ef7fe9d9a9d06c","repo":"router-for-me/CLIProxyAPI","slug":"unexpected-status-d","errorCode":null,"errorMessage":"unexpected status %d","messagePattern":"unexpected status (.+?)","errorType":"http","errorClass":null,"httpStatus":502,"severity":"error","filePath":"internal/pluginstore/github.go","lineNumber":270,"sourceCode":"\tnext, errNext := base.Parse(location)\n\tif errNext != nil {\n\t\treturn \"\", fmt.Errorf(\"parse redirect location: %w\", errNext)\n\t}\n\tif next.Scheme == \"\" || next.Host == \"\" {\n\t\treturn \"\", fmt.Errorf(\"redirect location is not absolute\")\n\t}\n\treturn next.String(), nil\n}\n\nfunc readPluginStoreResponse(resp *http.Response, maxSize int64, authenticated bool) ([]byte, error) {\n\tdefer func() {\n\t\tif errClose := resp.Body.Close(); errClose != nil {\n\t\t\tlog.WithError(errClose).Debug(\"failed to close plugin store response body\")\n\t\t}\n\t}()\n\tif resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {\n\t\tif authenticated {\n\t\t\treturn nil, fmt.Errorf(\"unexpected status %d\", resp.StatusCode)\n\t\t}\n\t\tbody, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))\n\t\treturn nil, fmt.Errorf(\"unexpected status %d: %s\", resp.StatusCode, strings.TrimSpace(string(body)))\n\t}\n\treader := io.Reader(resp.Body)\n\tif maxSize > 0 {\n\t\treader = io.LimitReader(resp.Body, maxSize+1)\n\t}\n\tdata, errRead := io.ReadAll(reader)\n\tif errRead != nil {\n\t\treturn nil, fmt.Errorf(\"read response: %w\", errRead)\n\t}\n\tif maxSize > 0 && int64(len(data)) > maxSize {\n\t\treturn nil, fmt.Errorf(\"response exceeds maximum allowed size of %d bytes\", maxSize)\n\t}\n\treturn data, nil\n}\n","sourceCodeStart":252,"sourceCodeEnd":288,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/pluginstore/github.go#L252-L288","documentation":"readPluginStoreResponse checks the status of every completed hop; outside 2xx it raises an error. The 'unexpected status %d' variant (without body) is used only when authenticated == true — i.e. the request carried resolved plugin-store credentials — so the response body is deliberately not read into the error to avoid leaking any account-identifying information an authenticated endpoint might return.","triggerScenarios":"An authenticated GET to the GitHub API or a private artifact URL that returns 4xx/5xx — 401/403 for bad or expired tokens, 404 for a missing repo/release/asset, or 5xx — with credentials attached (matchingResolvedAuthConfig matched the URL and request kind).","commonSituations":"Expired or revoked GitHub personal access token configured in plugin store auth; token lacking access to a private repository; a pinned release tag or asset that was deleted.","solutions":["Check which status you got (the number is in the message): 401/403 → refresh the stored credential; 404 → verify owner/repo/tag/asset exist; 5xx → retry later.","Rotate the token if it was revoked and update it in the plugin store auth configuration.","Ensure the token's scopes cover the private repo if the plugin repository is private.","Confirm the plugin manifest's repository field (owner/repo) is correct."],"exampleFix":"# before\n# expired token in plugin store auth -> 'unexpected status 401'\n\n# after\n# rotate the GitHub token and update plugin store auth config, then retry the install","handlingStrategy":"try-catch","validationCode":"if item, ok := matchingResolvedAuthConfig(client.ResolvedAuth, requestURL, kind); ok && !resolvedAuthConfigured(item) {\n    return errors.New(\"credentials for this URL are configured but empty — update plugin store auth\")\n}","typeGuard":null,"tryCatchPattern":"data, err := client.get(ctx, requestURL, accept, kind, 0)\nif err != nil {\n    msg := err.Error()\n    switch {\n    case strings.Contains(msg, \"unexpected status 401\"), strings.Contains(msg, \"unexpected status 403\"):\n        return fmt.Errorf(\"auth failed for %s — rotate the token: %w\", requestURL, err)\n    case strings.Contains(msg, \"unexpected status 404\"):\n        return fmt.Errorf(\"not found — check owner/repo/tag: %w\", err)\n    }\n    return err\n}","preventionTips":["Rotate tokens before expiry and store them via the plugin store auth config, not hardcoded.","Verify repo/tag existence when pinning plugins.","Note: with auth attached the error omits the body, so decide based on the status number."],"tags":["http-status","authentication","github-api","plugin-store"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}