router-for-me/CLIProxyAPI · error
unexpected status %d
Error message
unexpected status %d
What it means
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.
Source
Thrown at internal/pluginstore/github.go:270
next, errNext := base.Parse(location)
if errNext != nil {
return "", fmt.Errorf("parse redirect location: %w", errNext)
}
if next.Scheme == "" || next.Host == "" {
return "", fmt.Errorf("redirect location is not absolute")
}
return next.String(), nil
}
func readPluginStoreResponse(resp *http.Response, maxSize int64, authenticated bool) ([]byte, error) {
defer func() {
if errClose := resp.Body.Close(); errClose != nil {
log.WithError(errClose).Debug("failed to close plugin store response body")
}
}()
if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
if authenticated {
return nil, fmt.Errorf("unexpected status %d", resp.StatusCode)
}
body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
}
reader := io.Reader(resp.Body)
if maxSize > 0 {
reader = io.LimitReader(resp.Body, maxSize+1)
}
data, errRead := io.ReadAll(reader)
if errRead != nil {
return nil, fmt.Errorf("read response: %w", errRead)
}
if maxSize > 0 && int64(len(data)) > maxSize {
return nil, fmt.Errorf("response exceeds maximum allowed size of %d bytes", maxSize)
}
return data, nil
}
View on GitHub (pinned to 78f0c4079e)
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.
Example fix
# before # expired token in plugin store auth -> 'unexpected status 401' # after # rotate the GitHub token and update plugin store auth config, then retry the install
Defensive patterns
Strategy: try-catch
Validate before calling
if item, ok := matchingResolvedAuthConfig(client.ResolvedAuth, requestURL, kind); ok && !resolvedAuthConfigured(item) {
return errors.New("credentials for this URL are configured but empty — update plugin store auth")
} Try / catch
data, err := client.get(ctx, requestURL, accept, kind, 0)
if err != nil {
msg := err.Error()
switch {
case strings.Contains(msg, "unexpected status 401"), strings.Contains(msg, "unexpected status 403"):
return fmt.Errorf("auth failed for %s — rotate the token: %w", requestURL, err)
case strings.Contains(msg, "unexpected status 404"):
return fmt.Errorf("not found — check owner/repo/tag: %w", err)
}
return err
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- unexpected status %d: %s
- decode release: %w
- invalid release tag %q
- asset %q missing download url
- plugin_manifest_invalid
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/15ef7fe9d9a9d06c.
Report an issue: GitHub.