{"record":{"id":"68d96e8b05f1ab9a","repo":"gastownhall/beads","slug":"pypi-api-returned-status-d","errorCode":null,"errorMessage":"pypi api returned status %d","messagePattern":"pypi api returned status (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"cmd/bd/doctor/claude.go","lineNumber":621,"sourceCode":"\t\tTimeout: 5 * time.Second,\n\t}\n\n\treq, err := http.NewRequest(\"GET\", url, nil)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\t// Set User-Agent\n\treq.Header.Set(\"User-Agent\", \"beads-cli-doctor\")\n\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"pypi api returned status %d\", resp.StatusCode)\n\t}\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\n\tvar data struct {\n\t\tInfo struct {\n\t\t\tVersion string `json:\"version\"`\n\t\t} `json:\"info\"`\n\t}\n\n\tif err := json.Unmarshal(body, &data); err != nil {\n\t\treturn \"\", err\n\t}\n\n\treturn data.Info.Version, nil","sourceCodeStart":603,"sourceCodeEnd":639,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/cmd/bd/doctor/claude.go#L603-L639","documentation":"fetchLatestPyPIVersion queries the PyPI JSON API for the latest beads plugin version. If the HTTP response status is anything other than 200 OK, it returns this error with the actual status code, aborting the version lookup.","triggerScenarios":"fetchLatestPyPIVersion (during Claude plugin update checks in `bd doctor`) when the PyPI API responds with a non-200 status — 404 if the package name is wrong/renamed, 429 rate-limit, 5xx server errors — at cmd/bd/doctor/claude.go:621.","commonSituations":"Corporate proxies or captive portals returning error pages (403/502); PyPI outages or rate limiting; the package being renamed or unpublished; DNS resolving to a wrong endpoint.","solutions":["Retry later — transient 429/5xx from PyPI usually resolves on its own","Check the package exists at pypi.org/pypi/<package>/json (404 means renamed/removed)","Verify network/proxy settings (HTTPS_PROXY) and that a corporate proxy isn't injecting error responses","Check status.pypi.org for ongoing incidents; plugin update checking is non-fatal, so the rest of bd doctor still works"],"exampleFix":"// before\n$ curl -i https://pypi.org/pypi/beads-plugin/json   # HTTP 404\n// after\n# package renamed; check correct name\n$ curl -i https://pypi.org/pypi/beads/json   # HTTP 200","handlingStrategy":"retry","validationCode":"import \"net/http\"\nfunc pypiReachable(pkg string) error {\n\tresp, err := http.Get(\"https://pypi.org/pypi/\" + pkg + \"/json\")\n\tif err != nil { return err }\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"pypi status %d\", resp.StatusCode)\n\t}\n\treturn nil\n}","typeGuard":null,"tryCatchPattern":"ver, err := fetchLatestPyPIVersion(pkg)\nif err != nil {\n\tvar statusErr interface{ Error() string }\n\t_ = statusErr\n\tlog.Printf(\"latest-version lookup skipped: %v\", err) // non-fatal\n\treturn \"unknown\", nil\n}\n// retry with backoff on 429/5xx before giving up","preventionTips":["Treat plugin update checks as best-effort; don't fail workflows on them","Respect PyPI rate limits (429) with exponential backoff","Pin expected versions instead of relying on live lookups in CI","Check proxy/firewall rules that could return 403/502 for pypi.org"],"tags":["network","http","pypi","version-check"],"backgroundTag":"http-non-200-response","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}