github/github-mcp-server · error · GitHubAPIError

%s: %w

Error message

%s: %w

What it means

Error() method of GitHubAPIError (pkg/errors/error.go:32), the wrapper this server creates for every failed go-github REST API call. The rendered text is "<message>: <cause>"; seeing it means a REST request (Actions, Issues, Repos, ...) already failed and the wrapper is being printed or logged. The struct retains the *github.Response for status/header inspection and the embedded Err supports errors.Is/errors.As unwrapping.

Source

Thrown at pkg/errors/error.go:32

)

type GitHubAPIError struct {
	Message  string           `json:"message"`
	Response *github.Response `json:"-"`
	Err      error            `json:"-"`
}

// NewGitHubAPIError creates a new GitHubAPIError with the provided message, response, and error.
func newGitHubAPIError(message string, resp *github.Response, err error) *GitHubAPIError {
	return &GitHubAPIError{
		Message:  message,
		Response: resp,
		Err:      err,
	}
}

func (e *GitHubAPIError) Error() string {
	return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
}

type GitHubGraphQLError struct {
	Message string `json:"message"`
	Err     error  `json:"-"`
}

func newGitHubGraphQLError(message string, err error) *GitHubGraphQLError {
	return &GitHubGraphQLError{
		Message: message,
		Err:     err,
	}
}

func (e *GitHubGraphQLError) Error() string {
	return fmt.Errorf("%s: %w", e.Message, e.Err).Error()
}

View on GitHub (pinned to 0ea1f775a7)

Solutions

  1. Do not string-match the message - unwrap with errors.As(err, *GitHubAPIError) and switch on Response.StatusCode
  2. 401: refresh/replace the PAT; 403: check X-RateLimit-Remaining headers and back off; 404: verify owner/repo and resource IDs
  3. For fine-grained tokens, grant the specific repository permissions the tool needs
  4. If e.Err is nil you may see '%!w(<nil>)' - treat it as an error-assembly bug and report it upstream

Example fix

// before
result, err := callTool("list_workflow_runs", args)
if err != nil {
    log.Printf("failed: %v", err) // opaque string
}

// after
var ghErr *ghErrors.GitHubAPIError
if errors.As(err, &ghErr) {
    switch ghErr.Response.StatusCode {
    case 401:
        refreshToken() // token expired or invalid
    case 403:
        waitForRateLimitReset(ghErr.Response.Header) // or missing scope
    case 404:
        fixOwnerRepo() // wrong owner/repo or resource id
    default:
        log.Printf("github api error: %s (status %d)", ghErr.Message, ghErr.Response.StatusCode)
    }
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Cheap pre-flight: verify the token before running REST tools
if _, _, err := client.Users.Get(ctx, ""); err != nil {
    return fmt.Errorf("token preflight failed: %w", err)
}

Type guard

func asGitHubAPIError(err error) (*ghErrors.GitHubAPIError, bool) {
    var ghErr *ghErrors.GitHubAPIError
    if errors.As(err, &ghErr) {
        return ghErr, true
    }
    return nil, false
}

Try / catch

if err != nil {
    if ghErr, ok := asGitHubAPIError(err); ok && ghErr.Response != nil {
        switch ghErr.Response.StatusCode {
        case 401: // rotate token
        case 403: // check rate-limit headers / scopes, back off
        case 404: // fix owner/repo/id
        default: // log ghErr.Message and inner ghErr.Err
        }
    }
    return err
}

Prevention

When it happens

Trigger: Any REST API failure surfaced through NewGitHubAPIErrorResponse: 401 invalid/expired PAT, 403 primary or secondary rate limit, 404 wrong owner/repo/resource, 5xx GitHub outages, or network-level errors from go-github. Formatting this error is the last step of every failed REST tool call.

Common situations: Expired personal access token; fine-grained PAT missing the 'Actions: Read' or 'Contents: Read' permission; rate-limit exhaustion in tight agent loops; GHES base URL misconfigured so API calls hit the wrong host; token scopes stripped by a proxy.

Related errors


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