github/github-mcp-server · error
invalid GitHub App private key: %w
Error message
invalid GitHub App private key: %w
What it means
NewProvider failed to load the GitHub App private key; the %w wrap carries one of the specific parse errors — 'no PEM block found', 'parsing private key (want PKCS#1 or PKCS#8 RSA)', or 'private key is %T, want an RSA key'. It fires once at construction (internal/githubapp/githubapp.go:194-197), so it is a startup-time configuration failure with an actionable chain describing exactly how the PEM is malformed.
Source
Thrown at internal/githubapp/githubapp.go:196
}, nil
}
// Provider caches and refreshes GitHub App installation access tokens.
type Provider struct {
source oauth2.TokenSource
logger *slog.Logger
mu sync.Mutex
errLogged bool
}
func NewProvider(cfg Config, logger *slog.Logger) (*Provider, error) {
if err := cfg.validate(); err != nil {
return nil, err
}
privateKey, err := parsePrivateKey(cfg.PrivateKeyPEM)
if err != nil {
return nil, fmt.Errorf("invalid GitHub App private key: %w", err)
}
if logger == nil {
logger = slog.Default()
}
source := oauth2.ReuseTokenSource(nil, newInstallationTokenSource(cfg, privateKey, nil))
return &Provider{source: source, logger: logger}, nil
}
// AccessToken returns a cached token or refreshes it before expiry.
func (p *Provider) AccessToken() string {
tok, err := p.source.Token()
if err != nil {
p.mu.Lock()
if !p.errLogged {
p.errLogged = true
p.logger.Error("failed to obtain GitHub App installation token", "error", err)
}
p.mu.Unlock()View on GitHub (pinned to 0ea1f775a7)
Solutions
- Match the error tail: 'no PEM block found' means the bytes are not PEM at all (check for base64, path-instead-of-contents, stripped headers)
- Load the exact file downloaded from the GitHub App settings page: os.ReadFile(path) and pass those bytes — do not paste through env vars
- If it must go through an env var, base64-encode it and decode in-process: pemBytes, _ := base64.StdEncoding.DecodeString(v)
- Verify with: openssl rsa -in app.pem -check -noout
Example fix
// before
cfg := githubapp.Config{PrivateKeyPEM: []byte(os.Getenv("GITHUB_APP_PRIVATE_KEY"))}
// env contains "-----BEGIN RSA PRIVATE KEY-----\\n..." with literal backslash-n
// -> invalid GitHub App private key: no PEM block found in private key
// after
cfg := githubapp.Config{PrivateKeyPEM: mustReadPEM()}
func mustReadPEM() []byte {
if p := os.Getenv("GITHUB_APP_PRIVATE_KEY_PATH"); p != "" {
b, err := os.ReadFile(p) // raw file bytes, headers intact
if err != nil { log.Fatal(err) }
return b
}
b, err := base64.StdEncoding.DecodeString(os.Getenv("GITHUB_APP_PRIVATE_KEY"))
if err != nil { log.Fatal(err) }
return b
} Defensive patterns
Strategy: validation
Validate before calling
func loadPEM() ([]byte, error) {
if p := os.Getenv("GITHUB_APP_PRIVATE_KEY_PATH"); p != "" {
return os.ReadFile(p)
}
if v := os.Getenv("GITHUB_APP_PRIVATE_KEY"); v != "" {
return base64.StdEncoding.DecodeString(v) // transport-safe, decode in-process
}
return nil, errors.New("no private key configured")
}
// guard: pem.Decode(block) != nil && strings.HasPrefix(block.Type, "PRIVATE KEY") Type guard
func looksLikePEM(b []byte) bool {
block, _ := pem.Decode(b)
return block != nil && strings.Contains(block.Type, "PRIVATE KEY")
} Try / catch
if _, err := githubapp.NewProvider(cfg, logger); err != nil {
return fmt.Errorf("boot: GitHub App credentials rejected: %w", err) // fail the deploy early
} Prevention
- Prefer the file path (GITHUB_APP_PRIVATE_KEY_PATH) over env-transported PEM bodies
- If env transport is required, base64-encode the whole file — never paste raw PEM with \n escapes
- Validate lookLikePEM at config-load time with a clear error naming the variable at fault
When it happens
Trigger: Config.PrivateKeyPEM is: base64-encoded instead of raw PEM; a file path string rather than file contents; mangled by env-var transport (\n literals not converted to newlines, shell quoting stripped the header); a PKCS#8 EC/Ed25519 key; an empty/truncated file after a failed mount.
Common situations: Passing GITHUB_APP_PRIVATE_KEY via docker -e with literal '\n' escapes never expanded; Kubernetes secret mounted as the path instead of the value; copying the .pem out of a browser window truncating the final line; CI secret scanner stripping the BEGIN/END lines; Windows line endings or a BOM prefixing the block.
Related errors
- private key is %T, want an RSA key
- failed to unmarshal toolsets: %w
- failed to unmarshal tools: %w
- failed to build inventory: %w
- building installation token URL: %w
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/3257e8564fa6e708.
Report an issue: GitHub.