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

  1. 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.
  2. 404: verify the repository owner/name and release tag in the plugin manifest.
  3. 403 from artifact hosts: re-pin the manifest URL to a fresh, valid link.
  4. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/9306121759e7796c. Report an issue: GitHub.