github/github-mcp-server · error
unexpected status %d: %s
Error message
unexpected status %d: %s
What it means
Synthetic error built in NewGitHubAPIStatusErrorResponse (pkg/errors/error.go:219) when a go-github call returns err == nil but the HTTP status is not the expected success class. The status code and raw body are formatted as "unexpected status %d: %s" and the result is recorded as a GitHubAPIError for observability tracking. It means the transport succeeded while the API semantically refused or returned something surprising.
Source
Thrown at pkg/errors/error.go:219
_, _ = addGitHubGraphQLErrorToContext(ctx, graphQLErr) // Explicitly ignore error for graceful handling
}
return utils.NewToolResultErrorFromErr(message, err)
}
// NewGitHubRawAPIErrorResponse returns an mcp.NewToolResultError and retains the error in the context for access via middleware
func NewGitHubRawAPIErrorResponse(ctx context.Context, message string, resp *http.Response, err error) *mcp.CallToolResult {
rawErr := newGitHubRawAPIError(message, resp, err)
if ctx != nil {
_, _ = addRawAPIErrorToContext(ctx, rawErr) // Explicitly ignore error for graceful handling
}
return utils.NewToolResultErrorFromErr(message, err)
}
// NewGitHubAPIStatusErrorResponse handles cases where the API call succeeds (err == nil)
// but returns an unexpected HTTP status code. It creates a synthetic error from the
// status code and response body, then records it in context for observability tracking.
func NewGitHubAPIStatusErrorResponse(ctx context.Context, message string, resp *github.Response, body []byte) *mcp.CallToolResult {
err := fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
return NewGitHubAPIErrorResponse(ctx, message, resp, err)
}
// StructuredResolutionError is a machine-readable error returned by name-resolution
// helpers (e.g. resolving a project field or single-select option by name). Agents
// can parse the JSON body to self-correct without re-prompting.
//
// Kind values:
// - "field_not_found" — no project field matches the supplied name
// - "field_ambiguous" — more than one project field shares the supplied name
// - "option_not_found" — no option on the resolved single-select field matches
// - "option_ambiguous" — duplicate option names on the resolved field
// - "item_not_in_project" — the issue/PR exists but is not an item on the project
// - "wrong_field_type" — the named field is not the data type the caller expected
type StructuredResolutionError struct {
Kind string `json:"error"`
Name string `json:"name,omitempty"`
Field string `json:"field,omitempty"`View on GitHub (pinned to 0ea1f775a7)
Solutions
- Read the body embedded in the message text - it usually names the real cause (proxy error page, GHES login page)
- Fix GHES base URL config: base REST URL must be like https://ghes.example.com/api/v3, upload URL similarly
- Bypass or configure the proxy for api.github.com traffic
- Update github-mcp-server and go-github if the API contract changed
Example fix
// before
if err != nil {
return err // treats transport errors only
}
// after - also guard the success-status case
if resp != nil && resp.StatusCode < 200 || resp.StatusCode >= 300 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
} Defensive patterns
Strategy: type-guard
Validate before calling
// Before trusting a 'successful' call, assert the status class you expect
if resp != nil && (resp.StatusCode < 200 || resp.StatusCode >= 300) {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
} Type guard
func asUnexpectedStatus(err error) (code int, body string, ok bool) {
var ghErr *ghErrors.GitHubAPIError
if errors.As(err, &ghErr) && strings.HasPrefix(ghErr.Message, "unexpected status") {
// parse code from ghErr.Response or the synthetic message
}
return 0, "", false
} Try / catch
if err != nil {
if code, body, ok := asUnexpectedStatus(err); ok {
if code >= 500 || code == 502 {
backoffAndRetry() // proxy/upstream blip
} else {
log.Printf("api refused: %d body=%s", code, body)
}
}
return err
} Prevention
- Always validate both err and response status on API calls
- Log response bodies for non-2xx statuses - they identify proxies and GHES pages
- Configure GHES base URLs exactly (api/v3 suffix, no trailing path drift)
- Keep reverse proxies from rewriting API responses
When it happens
Trigger: API endpoints that succeed at the transport layer but return unexpected statuses: a reverse proxy injecting a 502 HTML page, a GHES appliance answering with a redirect or login page, an API change returning 3xx/204 where 200 was expected, or content-negotiation failures. Triggered by code paths that call NewGitHubAPIStatusErrorResponse after checking err == nil.
Common situations: Self-hosted deployments behind proxies that rewrite responses; GHES base URL configured without /api/v3 (or with it doubled); GitHub incidents serving error pages with 200-status oddities; API version drift between go-github and GitHub.
Related errors
- installation token request failed: %s: %s
- %s: %w
- failed to download logs: %w
- failed to download logs: HTTP %d
- failed to get base REST URL: %w
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/bae7ac28a82bd643.
Report an issue: GitHub.