github/github-mcp-server · warning

GitHub App authentication requires a private key: set GITHUB

Error message

GitHub App authentication requires a private key: set GITHUB_APP_PRIVATE_KEY_PATH (preferred) or GITHUB_APP_PRIVATE_KEY

What it means

After Activity.ListStarred returns a non-200 status (404 for an unknown username, 401/403 for auth problems), the handler reads resp.Body with io.ReadAll to build the status error response; this error means that read failed. The failure is transport-level and occurs after headers arrived, so the underlying API error text is lost. Causes include mid-body connection resets, proxies closing streams, and already-consumed bodies.

Source

Thrown at cmd/github-mcp-server/main.go:353

	}, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to configure GitHub App authentication: %w", err)
	}
	return provider.AccessToken, nil
}

func loadAppPrivateKey(path, inline string) ([]byte, error) {
	switch {
	case path != "":
		data, err := os.ReadFile(path) //#nosec G304 -- operator-supplied path to their own key
		if err != nil {
			return nil, fmt.Errorf("reading GitHub App private key file: %w", err)
		}
		return data, nil
	case inline != "":
		return []byte(strings.ReplaceAll(inline, `\n`, "\n")), nil
	default:
		return nil, errors.New("GitHub App authentication requires a private key: set GITHUB_APP_PRIVATE_KEY_PATH (preferred) or GITHUB_APP_PRIVATE_KEY")
	}
}

func wordSepNormalizeFunc(_ *pflag.FlagSet, name string) pflag.NormalizedName {
	from := []string{"_"}
	to := "-"
	for _, sep := range from {
		name = strings.ReplaceAll(name, sep, to)
	}
	return pflag.NormalizedName(name)
}

View on GitHub (pinned to 0ea1f775a7)

Solutions

  1. Retry list_starred_repositories - it is a read, safe to repeat
  2. If it recurs, raise proxy/LB idle timeouts or disable aggressive connection reaping on the API path
  3. Log the status code seen before the read failed to distinguish API 404/403 from transport faults
  4. Report persistent occurrences with request ID; truncated error bodies hide the real API message

Example fix

// before: error propagates with no context
repos, err := listStarred(ctx, username)

// after: classify and retry transient body-read failures once
repos, err := listStarred(ctx, username)
if isBodyReadError(err) {
	time.Sleep(250 * time.Millisecond)
	repos, err = listStarred(ctx, username)
}
Defensive patterns

Strategy: retry

Type guard

func isBodyReadError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "failed to read response body")
}

Try / catch

// list_starred_repositories is a read: retry safely.
repos, err := listStarred(ctx, username)
if isBodyReadError(err) {
	time.Sleep(250 * time.Millisecond)
	repos, err = listStarred(ctx, username)
}

Prevention

When it happens

Trigger: ListStarred replies 404 (user does not exist) or 403 and the connection drops before the JSON error body is fully read; a service mesh resets the stream; a keep-alive pooled connection is reaped between status and body.

Common situations: Unstable VPN/proxy links; LB idle timeouts shorter than body transfer; large starred lists where the error body arrives late; retry storms over half-closed connections.

Understand the failure class

Related errors


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