github/github-mcp-server · error

failed to list releases

Error message

failed to list releases: %w

What it means

Returned by list_releases when client.Repositories.ListReleases (GET /repos/{owner}/{repo}/releases) itself fails. Unlike every neighboring call in this file, the handler wraps it with a bare fmt.Errorf instead of ghErrors.NewGitHubAPIErrorResponse, so the structured status/message from go-github's *github.ErrorResponse survives only inside the %w chain. Triggers: 404 (repo unknown or invisible to the token), 401 (bad token), 403 (rate limit or forbidden), and pure network failures where the call never completes.

Solutions

  1. Reproduce with the same token: `gh api repos/OWNER/REPO/releases --jq length` shows the raw 404/403/401 directly.
  2. 404: fix owner/repo spelling or grant the token access to the repo.
  3. 403 rate limit: check `gh api rate_limit`, wait for reset, retry with backoff.
  4. 401: rotate/refresh the token.
  5. Persistent: read the wrapped *github.ErrorResponse.Message in server logs for the exact cause.

Example fix

// before
releases, resp, err := client.Repositories.ListReleases(ctx, owner, repo, opts)
if err != nil {
	return nil, nil, fmt.Errorf("failed to list releases: %w", err)
}

// after: structured API error like every neighboring handler
releases, resp, err := client.Repositories.ListReleases(ctx, owner, repo, opts)
if err != nil {
	return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to list releases", resp, err), nil, nil
}
Defensive patterns

Strategy: try-catch

Validate before calling

# same token as the MCP server uses
gh api 'repos/OWNER/REPO/releases?per_page=1' --jq 'length'  # 404 -> repo invisible to token
gh api rate_limit --jq '.resources.core.remaining'            # 0 -> expect 403 failures

Try / catch

// when embedding the handler's client
releases, resp, err := client.Repositories.ListReleases(ctx, owner, repo, opts)
if err != nil {
	var ghErr *github.ErrorResponse
	if errors.As(err, &ghErr) {
		switch ghErr.Response.StatusCode {
		case http.StatusNotFound: // unknown repo or token lacks access
		case http.StatusUnauthorized: // expired/revoked token
		case http.StatusForbidden: // rate limit - back off until reset
		}
	}
}

Prevention

When it happens

Trigger: Listing releases on a nonexistent repo (404); a fine-grained PAT without access to the private repo (404/403); primary rate limit exhausted (403 'API rate limit exceeded'); expired token (401); DNS/TLS failure reaching api.github.com.

Common situations: Typo'd owner/repo; pointing the tool at a fork or private repo the token cannot see; CI jobs exhausting token quota mid-run; GHES host unreachable.

Related errors


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

Appendix: source

Thrown at pkg/github/repositories.go:1852

			}
			pagination, err := OptionalPaginationParams(args)
			if err != nil {
				return utils.NewToolResultError(err.Error()), nil, nil
			}

			opts := &github.ListOptions{
				Page:    pagination.Page,
				PerPage: pagination.PerPage,
			}

			client, err := deps.GetClient(ctx)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to get GitHub client: %w", err)
			}

			releases, resp, err := client.Repositories.ListReleases(ctx, owner, repo, opts)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to list releases: %w", err)
			}
			defer func() { _ = resp.Body.Close() }()

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

			minimalReleases := make([]MinimalRelease, 0, len(releases))
			for _, release := range releases {
				if release != nil {
					minimalReleases = append(minimalReleases, convertToMinimalRelease(release))
				}
			}

View on GitHub (pinned to 0ea1f775a7)