router-for-me/CLIProxyAPI · error
unexpected status %d: %s
Error message
unexpected status %d: %s
What it means
The unauthenticated counterpart of 617: readPluginStoreResponse appends up to 4KB of the response body to the status error ('unexpected status %d: %s') when no credentials were attached, since an unauthenticated error body carries nothing sensitive. This is the error you see for public endpoints like api.github.com without a token — most notably GitHub rate limiting.
Source
Thrown at internal/pluginstore/github.go:273
}
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
}
func pluginStoreRequestError(requestURL string, err error) error {
parsed, errParse := url.Parse(strings.TrimSpace(requestURL))
safeURL := "plugin store url"View on GitHub (pinned to 78f0c4079e)
Solutions
- Read the body snippet in the error — for rate limit, configure a GitHub token in plugin store auth so requests are authenticated (5000/hr) and take the 617 path instead.
- 404: verify the repository owner/name and release tag in the plugin manifest.
- 403 from artifact hosts: re-pin the manifest URL to a fresh, valid link.
- Add retry-with-backoff for transient 5xx bodies.
Example fix
# before # 'unexpected status 403: API rate limit exceeded for 203.0.113.9.' # after # configure credentials so metadata requests are authenticated: # auth: # - match: https://api.github.com/ # kind: metadata # token: <github-pat>
Defensive patterns
Strategy: retry
Try / catch
data, err := client.get(ctx, requestURL, accept, kind, 0)
if err != nil {
msg := err.Error()
if strings.Contains(msg, "unexpected status 403") && strings.Contains(msg, "rate limit") {
time.Sleep(time.Hour) // or configure a token to move to the authenticated path
} else if strings.Contains(msg, "unexpected status 5") {
time.Sleep(backoff) // exponential backoff, few attempts
}
} Prevention
- Configure a GitHub token for plugin store metadata requests to escape the 60/hr anonymous limit.
- Cache release metadata between installs to cut API calls.
- Read the body snippet the error carries — it states the server's reason.
When it happens
Trigger: Unauthenticated GETs to api.github.com or artifact hosts returning non-2xx: 403 with an API rate-limit exceeded message, 404 for unknown repos, or artifact hosts returning error pages. The body snippet shows the server's own explanation (e.g. 'API rate limit exceeded for 1.2.3.4').
Common situations: Anonymous GitHub API rate limits (60 req/hour per IP) hit in CI or shared NATs; typos in owner/repo; deleted releases; artifact hosts returning 403 for hotlinking or expired signed URLs.
Related errors
- decode release: %w
- unexpected status %d
- invalid release tag %q
- asset %q missing download url
- stopped after %d redirects
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/9306121759e7796c.
Report an issue: GitHub.