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

  1. Read the body embedded in the message text - it usually names the real cause (proxy error page, GHES login page)
  2. Fix GHES base URL config: base REST URL must be like https://ghes.example.com/api/v3, upload URL similarly
  3. Bypass or configure the proxy for api.github.com traffic
  4. 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

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


AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15). Data as JSON: /api/errors/bae7ac28a82bd643. Report an issue: GitHub.