JuliusBrussee/caveman · error
githubapp: app id is required
Error message
githubapp: app id is required
What it means
githubapp.New validates its Config and refuses to build the App when cfg.AppID is empty (after trimming whitespace). The constructor is deliberately fail-closed: no half-built App, so connect endpoints can answer a clean "disabled" instead of fabricating success.
Solutions
- Set the GitHub App ID in configuration (env var or config field) and pass it into Config.AppID
- Trim and validate the value at config-load time; treat empty as "GitHub integration disabled" and skip calling New
- Check deployment secrets/env were actually mounted (e.g. missing secret in the orchestrator)
- Verify the config key name matches what the loader reads (typos yield empty strings silently)
Example fix
// before
app, err := githubapp.New(githubapp.Config{PrivateKeyPEM: pem})
// after
if cfg.GitHubAppID == "" {
return nil, nil // integration disabled
}
app, err := githubapp.New(githubapp.Config{AppID: cfg.GitHubAppID, PrivateKeyPEM: pem}) Defensive patterns
Strategy: validation
Validate before calling
if strings.TrimSpace(cfg.GitHubAppID) == "" {
// treat as integration disabled or fail config load here
return ErrGitHubAppUnconfigured
}
app, err := githubapp.New(githubapp.Config{AppID: cfg.GitHubAppID, PrivateKeyPEM: keyPEM}) Type guard
func appConfigured(app *githubapp.App) bool { return app != nil } Try / catch
app, err := githubapp.New(cfg)
if err != nil {
if strings.Contains(err.Error(), "app id is required") {
log.Info("github app not configured; connect endpoints disabled")
return nil // intentional disabled state
}
return err
} Prevention
- Fail fast at config-load time for empty required identifiers
- Assert secret/env mounts in deploy templates
- Use keyed Config fields to avoid positional mistakes
When it happens
Trigger: Calling githubapp.New with Config{AppID: ""} — e.g. the GitHub App ID env var or config file value was never set, or contains only spaces.
Common situations: Deploying without the GitHub App integration configured; an env-var lookup returning "" with no default; YAML/TOML key present but empty; config struct assembled before loading settings.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- CAVEMAN_CCR_MAX_BYTES must be a positive integer
- ccr max storage bytes must be positive
- compat upstream name
- kms: unsupported provider
- provider upstream URL scheme is not allowed
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/98c7cbca47ea444a.
Report an issue: GitHub.
Appendix: source
Thrown at shared/platform/githubapp/githubapp.go:69
}
// App holds the parsed App identity and the SSRF-guarded HTTP client.
type App struct {
appID string
slug string
privateKey *rsa.PrivateKey
webhookSecret string
baseURL string
httpClient *http.Client
}
// New parses the private key, validates the base URL, and builds the App. It
// returns an error (not a half-built App) on any misconfiguration, so callers
// fail closed — an unconfigured deployment leaves the App nil and the connect
// endpoints answer a clean "disabled" rather than a fabricated success.
func New(cfg Config) (*App, error) {
if strings.TrimSpace(cfg.AppID) == "" {
return nil, fmt.Errorf("githubapp: app id is required")
}
if len(cfg.PrivateKeyPEM) == 0 {
return nil, fmt.Errorf("githubapp: private key PEM is required")
}
key, err := parseRSAPrivateKey(cfg.PrivateKeyPEM)
if err != nil {
return nil, err
}
base := strings.TrimRight(strings.TrimSpace(cfg.BaseURL), "/")
if base == "" {
base = defaultBaseURL
}
client := cfg.HTTPClient
if client == nil {
// Production path: SSRF-guarded client + pre-flight host check on a custom
// (GHE) base. When a caller injects a client (tests), it owns the host policy,
// so we skip the pre-flight — but production never injects one.
if base != defaultBaseURL {View on GitHub (pinned to 3ee70a1026)