github/github-mcp-server · warning
no PEM block found in private key
Error message
no PEM block found in private key
What it means
Activity.Unstar returns 204 on success; on any other status the handler drains resp.Body via io.ReadAll to construct the status error, and this error indicates the drain failed. The transport broke after the status line arrived, so the actual API error (404 unknown repo, 401, 403) is unrecoverable from the response. Same failure class as other body-read errors: resets, proxy closes, consumed streams.
Source
Thrown at internal/githubapp/githubapp.go:67
func (c Config) validate() error {
switch {
case c.AppID == "":
return errors.New("GitHub App ID or client ID is required (GITHUB_APP_ID)")
case c.InstallationID == "":
return errors.New("GitHub App installation ID is required (GITHUB_APP_INSTALLATION_ID)")
case len(c.PrivateKeyPEM) == 0:
return errors.New("GitHub App private key is required (GITHUB_APP_PRIVATE_KEY_PATH or GITHUB_APP_PRIVATE_KEY)")
case c.BaseRESTURL == "":
return errors.New("GitHub App REST base URL is required")
}
return nil
}
func parsePrivateKey(pemBytes []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(pemBytes)
if block == nil {
return nil, errors.New("no PEM block found in private key")
}
if key, err := x509.ParsePKCS1PrivateKey(block.Bytes); err == nil {
return key, nil
}
parsed, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("parsing private key (want PKCS#1 or PKCS#8 RSA): %w", err)
}
key, ok := parsed.(*rsa.PrivateKey)
if !ok {
return nil, fmt.Errorf("private key is %T, want an RSA key", parsed)
}
return key, nil
}
func mintJWT(appID string, privateKey *rsa.PrivateKey, now time.Time) (string, error) {
header := map[string]string{"alg": "RS256", "typ": "JWT"}
claims := map[string]any{View on GitHub (pinned to 0ea1f775a7)
Solutions
- Retry unstar_repository once - it is idempotent (204 whether it removed a star or there was none)
- Confirm owner/repo still exists (renames produce 404) with a read call
- Tune proxy/LB timeouts if recurrent
- Capture status codes to distinguish genuine 404s from transport faults
Example fix
// before
err := unstarRepo(ctx, owner, repo)
// after: classify and retry once
err := unstarRepo(ctx, owner, repo)
if isBodyReadError(err) {
time.Sleep(250 * time.Millisecond)
err = unstarRepo(ctx, owner, repo)
} 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
// unstar_repository is idempotent (204 whether or not a star existed).
err := unstarRepo(ctx, owner, repo)
if isBodyReadError(err) {
time.Sleep(250 * time.Millisecond)
err = unstarRepo(ctx, owner, repo)
} Prevention
- A retried unstar cannot remove anything extra - retry safely
- Handle renamed/deleted repos (404) separately from transport faults
- Keep bulk unstar scripts resilient to transient network resets
- Cap retries and log the pre-read status code for diagnosis
When it happens
Trigger: Unstar replies 404 (repo deleted or renamed) and the connection drops mid-error-body; proxy resets the stream; pooled keep-alive connection reaped between status and body.
Common situations: Bulk unstar scripts over unstable networks; proxies with short idle timeouts; repos renamed between listing and unstarring.
Related errors
- GitHub App authentication requires a private key: set GITHUB
- GitHub App private key is required (GITHUB_APP_PRIVATE_KEY_P
- failed to read log content: %w
- failed to read response body: %w
- failed to read response body: %w
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/ac18e234d367e60f.
Report an issue: GitHub.