github/github-mcp-server · error

failed to get issue: %w

Error message

failed to get issue: %w

What it means

GetIssue (pkg/github/issues.go:724) wraps a failure of client.Issues.Get — the REST call GET /repos/{owner}/{repo}/issues/{number} returned a transport error or an HTTP error status. Unlike the structured NewGitHubAPIErrorResponse path used elsewhere, this branch returns a plain wrapped Go error, so the GitHub error details (404 body, rate-limit message) live inside err.

Source

Thrown at pkg/github/issues.go:724

			case "get_labels":
				result, err := GetIssueLabels(ctx, gqlClient, owner, repo, issueNumber)
				return attachIFC(result), nil, err
			default:
				return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
			}
		})
}

func GetIssue(ctx context.Context, client *github.Client, deps ToolDependencies, owner string, repo string, issueNumber int) (*mcp.CallToolResult, error) {
	cache, err := deps.GetRepoAccessCache(ctx)
	if err != nil {
		return nil, fmt.Errorf("failed to get repo access cache: %w", err)
	}
	flags := deps.GetFlags(ctx)

	issue, resp, err := client.Issues.Get(ctx, owner, repo, issueNumber)
	if err != nil {
		return nil, fmt.Errorf("failed to get issue: %w", err)
	}
	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		body, err := io.ReadAll(resp.Body)
		if err != nil {
			return nil, fmt.Errorf("failed to read response body: %w", err)
		}
		return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get issue", resp, body), nil
	}

	if flags.LockdownMode {
		if restricted, err := authorLockdownResult(ctx, cache, owner, repo, issue.GetUser().GetLogin(), lockdownIssueRestrictedMessage); restricted != nil || err != nil {
			return restricted, err
		}
	}

	// Sanitize title/body on response

View on GitHub (pinned to 0ea1f775a7)

Solutions

  1. Verify owner/repo/issue_number exactly (issue, not PR number where semantics matter) and that the token can see the repo
  2. Check rate limits: on 403/429 inspect x-ratelimit-remaining and retry-after, then back off
  3. Handle 301 moved: follow the redirect target or update stored owner/repo after renames/transfers
  4. For auth errors, refresh the token and confirm SSO authorization for the org

Example fix

// before
issue, resp, err := client.Issues.Get(ctx, owner, repo, n)
if err != nil {
    return err // opaque
}

// after
issue, resp, err := client.Issues.Get(ctx, owner, repo, n)
if err != nil {
    var ghErr *github.ErrorResponse
    if errors.As(err, &ghErr) {
        switch ghErr.Response.StatusCode {
        case http.StatusNotFound:
            return fmt.Errorf("issue %s/%s#%d not found or not visible", owner, repo, n)
        case http.StatusUnauthorized, http.StatusForbidden, http.StatusTooManyRequests:
            return fmt.Errorf("auth/rate limit: %v", ghErr.Message)
        }
    }
    return err
}
defer func() { _ = resp.Body.Close() }()
Defensive patterns

Strategy: try-catch

Validate before calling

if issueNumber <= 0 {
    return errors.New("issue number must be positive")
}
// optional cheap existence check for private repos
if _, resp, err := client.Repositories.Get(ctx, owner, repo); err != nil {
    return fmt.Errorf("repo %s/%s not visible to token: %w", owner, repo, err)
} else {
    _ = resp.Body.Close()
}

Try / catch

issue, resp, err := client.Issues.Get(ctx, owner, repo, issueNumber)
if err != nil {
    var ghErr *github.ErrorResponse
    if errors.As(err, &ghErr) && ghErr.Response != nil {
        switch ghErr.Response.StatusCode {
        case http.StatusNotFound:
            return fmt.Errorf("%s/%s#%d not found or not visible to this token", owner, repo, issueNumber)
        case http.StatusUnauthorized:
            return fmt.Errorf("token invalid/expired")
        case http.StatusForbidden, http.StatusTooManyRequests:
            time.Sleep(retryAfter(ghErr.Response)) // honor Retry-After
            issue, resp, err = client.Issues.Get(ctx, owner, repo, issueNumber)
            if err != nil {
                return err
            }
        default:
            return err
        }
    }
    return err
}
defer func() { _ = resp.Body.Close() }()

Prevention

When it happens

Trigger: 404 because owner, repo, or issue number is wrong (or the repo is private and invisible to the token); 401/403 for expired/insufficient token or blocked by SSO enforcement; 403/429 primary or secondary rate limits; 301 after a repo rename/transfer; network/DNS failure to api.github.com or the GHES host.

Common situations: Wrong owner/repo spelling in tool args; tokens missing repo scope for private repos; bulk automation hitting secondary rate limits; renamed/transferred repositories with stale references; GHES base URL misconfiguration.

Related errors


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