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

  1. 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.
  2. Rotate the token if it was revoked and update it in the plugin store auth configuration.
  3. Ensure the token's scopes cover the private repo if the plugin repository is private.
  4. 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

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


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