{"record":{"id":"98c7cbca47ea444a","repo":"JuliusBrussee/caveman","slug":"githubapp-app-id-is-required","errorCode":null,"errorMessage":"githubapp: app id is required","messagePattern":"githubapp: app id is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/githubapp/githubapp.go","lineNumber":69,"sourceCode":"}\n\n// App holds the parsed App identity and the SSRF-guarded HTTP client.\ntype App struct {\n\tappID         string\n\tslug          string\n\tprivateKey    *rsa.PrivateKey\n\twebhookSecret string\n\tbaseURL       string\n\thttpClient    *http.Client\n}\n\n// New parses the private key, validates the base URL, and builds the App. It\n// returns an error (not a half-built App) on any misconfiguration, so callers\n// fail closed — an unconfigured deployment leaves the App nil and the connect\n// endpoints answer a clean \"disabled\" rather than a fabricated success.\nfunc New(cfg Config) (*App, error) {\n\tif strings.TrimSpace(cfg.AppID) == \"\" {\n\t\treturn nil, fmt.Errorf(\"githubapp: app id is required\")\n\t}\n\tif len(cfg.PrivateKeyPEM) == 0 {\n\t\treturn nil, fmt.Errorf(\"githubapp: private key PEM is required\")\n\t}\n\tkey, err := parseRSAPrivateKey(cfg.PrivateKeyPEM)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tbase := strings.TrimRight(strings.TrimSpace(cfg.BaseURL), \"/\")\n\tif base == \"\" {\n\t\tbase = defaultBaseURL\n\t}\n\tclient := cfg.HTTPClient\n\tif client == nil {\n\t\t// Production path: SSRF-guarded client + pre-flight host check on a custom\n\t\t// (GHE) base. When a caller injects a client (tests), it owns the host policy,\n\t\t// so we skip the pre-flight — but production never injects one.\n\t\tif base != defaultBaseURL {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/githubapp/githubapp.go#L51-L87","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)"],"exampleFix":"// before\napp, err := githubapp.New(githubapp.Config{PrivateKeyPEM: pem})\n// after\nif cfg.GitHubAppID == \"\" {\n    return nil, nil // integration disabled\n}\napp, err := githubapp.New(githubapp.Config{AppID: cfg.GitHubAppID, PrivateKeyPEM: pem})","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(cfg.GitHubAppID) == \"\" {\n    // treat as integration disabled or fail config load here\n    return ErrGitHubAppUnconfigured\n}\napp, err := githubapp.New(githubapp.Config{AppID: cfg.GitHubAppID, PrivateKeyPEM: keyPEM})","typeGuard":"func appConfigured(app *githubapp.App) bool { return app != nil }","tryCatchPattern":"app, err := githubapp.New(cfg)\nif err != nil {\n    if strings.Contains(err.Error(), \"app id is required\") {\n        log.Info(\"github app not configured; connect endpoints disabled\")\n        return nil // intentional disabled state\n    }\n    return err\n}","preventionTips":["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"],"tags":["go","github-app","config","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}