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

  1. 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)
  2. Load the exact file downloaded from the GitHub App settings page: os.ReadFile(path) and pass those bytes — do not paste through env vars
  3. If it must go through an env var, base64-encode it and decode in-process: pemBytes, _ := base64.StdEncoding.DecodeString(v)
  4. 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

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


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