{"record":{"id":"60cf88fe451fd9dc","repo":"github/github-mcp-server","slug":"failed-to-get-issue-comments-w","errorCode":null,"errorMessage":"failed to get issue comments: %w","messagePattern":"failed to get issue comments: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/github/issues.go","lineNumber":849,"sourceCode":"}\n\nfunc GetIssueComments(ctx context.Context, client *github.Client, deps ToolDependencies, owner string, repo string, issueNumber int, pagination PaginationParams) (*mcp.CallToolResult, error) {\n\tcache, err := deps.GetRepoAccessCache(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get repo access cache: %w\", err)\n\t}\n\tflags := deps.GetFlags(ctx)\n\n\topts := &github.IssueListCommentsOptions{\n\t\tListOptions: github.ListOptions{\n\t\t\tPage:    pagination.Page,\n\t\t\tPerPage: pagination.PerPage,\n\t\t},\n\t}\n\n\tcomments, resp, err := client.Issues.ListComments(ctx, owner, repo, issueNumber, opts)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get issue comments: %w\", err)\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\tif resp.StatusCode != http.StatusOK {\n\t\tbody, err := io.ReadAll(resp.Body)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to read response body: %w\", err)\n\t\t}\n\t\treturn ghErrors.NewGitHubAPIStatusErrorResponse(ctx, \"failed to get issue comments\", resp, body), nil\n\t}\n\tif flags.LockdownMode {\n\t\tif cache == nil {\n\t\t\treturn nil, fmt.Errorf(\"lockdown cache is not configured\")\n\t\t}\n\t\tfilteredComments := make([]*github.IssueComment, 0, len(comments))\n\t\tfor _, comment := range comments {\n\t\t\tuser := comment.User\n\t\t\tif user == nil {","sourceCodeStart":831,"sourceCodeEnd":867,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/pkg/github/issues.go#L831-L867","documentation":"The go-github Issues.ListComments call returned a non-nil error. go-github converts transport failures (DNS, TLS, connection reset) and non-2xx HTTP responses (401 bad credentials, 403 forbidden/SAML/rate limit, 404 not found, 422 validation) into *github.ErrorResponse, which this tool wraps and returns to the MCP caller unchanged.","triggerScenarios":"GET /repos/{owner}/{repo}/issues/{number}/comments fails at transport level (network down, DNS failure, TLS handshake error) or returns 401 (expired/revoked token), 404 (wrong owner/repo or issue number, or no access to a private repo), 403 (missing SAML authorization, secondary rate limit).","commonSituations":"Expired personal access token; typo'd owner/repo arguments from the LLM host; token without access to the target private repo; GitHub secondary rate limits on aggressive comment polling; GitHub outage returning 5xx.","solutions":["Verify the token is valid and not expired (curl -H \"Authorization: Bearer $TOKEN\" https://api.github.com/user)","Confirm owner, repo, and issueNumber are correct and the token can see the repo (private repo access)","Inspect the wrapped *github.ErrorResponse status and message — 401/403 means fix credentials/authorization, 404 means fix arguments","Retry with exponential backoff only for 5xx statuses or transport errors; never retry 4xx"],"exampleFix":"// before — no discrimination between error kinds\ncomments, err := github.GetIssueComments(ctx, client, deps, owner, repo, num, pagination)\n\n// after — branch on the underlying GitHub error\nvar ghErr *github.ErrorResponse\nif errors.As(err, &ghErr) {\n    switch ghErr.Response.StatusCode {\n    case http.StatusUnauthorized, http.StatusForbidden:\n        return fmt.Errorf(\"check token/scopes: %w\", err)\n    case http.StatusNotFound:\n        return fmt.Errorf(\"repo/issue not found or inaccessible: %w\", err)\n    }\n}\nreturn err // transport error — safe to retry","handlingStrategy":"retry","validationCode":"// Validate inputs and credentials before the API call\nif owner == \"\" || repo == \"\" || issueNumber <= 0 {\n    return fmt.Errorf(\"owner, repo and a positive issue number are required\")\n}\nif pagination.PerPage < 1 || pagination.PerPage > 100 {\n    pagination.PerPage = 30 // clamp to API limits\n}","typeGuard":"var ghErr *github.ErrorResponse\nfunc isGitHubAPIError(err error) bool { return errors.As(err, &ghErr) }","tryCatchPattern":"comments, err := githubpkg.GetIssueComments(ctx, client, deps, owner, repo, num, pagination)\nif err != nil {\n    var ghErr *github.ErrorResponse\n    if errors.As(err, &ghErr) {\n        switch ghErr.Response.StatusCode {\n        case http.StatusUnauthorized, http.StatusForbidden:\n            return fmt.Errorf(\"credentials/authorization problem: %w\", err) // no retry\n        case http.StatusNotFound:\n            return fmt.Errorf(\"no such repo/issue (or no access): %w\", err) // no retry\n        }\n    }\n    // transport error or 5xx — bounded retry\n    return retryWithBackoff(ctx, 3, func() error { return retryCall() })\n}","preventionTips":["Use short-lived, auto-rotating tokens so 401s surface as auth events, not mystery failures","Pre-validate owner/repo shape before invoking the tool","Respect X-RateLimit-Remaining headers to avoid 403 secondary limits","Differentiate retryable (5xx, network) from permanent (4xx) failures before retrying"],"tags":["api","auth","network","issues","comments"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}