{"record":{"id":"4c9cfe2a70cd01a6","repo":"JuliusBrussee/caveman","slug":"postgres-set-only-s-or-s-not-both","errorCode":null,"errorMessage":"postgres: set only %s or %s, not both","messagePattern":"postgres: set only (.+?) or (.+?), not both","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"shared/platform/postgresconfig/postgresconfig.go","lineNumber":83,"sourceCode":"\tfor _, fallback := range config.ConnConfig.Fallbacks {\n\t\tif fallback.TLSConfig == nil {\n\t\t\tif production {\n\t\t\t\treturn nil, errors.New(\"postgres: production connection includes a plaintext fallback\")\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tfallback.TLSConfig.RootCAs = roots\n\t\tfallback.TLSConfig.InsecureSkipVerify = false\n\t\tfallback.TLSConfig.ServerName = fallback.Host\n\t}\n\treturn config, nil\n}\n\nfunc caPEMFromEnvironment() (string, error) {\n\tfilePath := strings.TrimSpace(os.Getenv(caFileEnvironment))\n\tdirect := strings.TrimSpace(os.Getenv(caEnvironment))\n\tif filePath != \"\" && direct != \"\" {\n\t\treturn \"\", fmt.Errorf(\"postgres: set only %s or %s, not both\", caFileEnvironment, caEnvironment)\n\t}\n\tif filePath == \"\" {\n\t\treturn direct, nil\n\t}\n\tinfo, err := os.Stat(filePath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"postgres: %s: %w\", caFileEnvironment, err)\n\t}\n\tif !info.Mode().IsRegular() {\n\t\treturn \"\", fmt.Errorf(\"postgres: %s must point to a regular file\", caFileEnvironment)\n\t}\n\tcontents, err := os.ReadFile(filePath)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"postgres: read %s: %w\", caFileEnvironment, err)\n\t}\n\treturn strings.TrimSpace(string(contents)), nil\n}\n","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/shared/platform/postgresconfig/postgresconfig.go#L65-L101","documentation":"caPEMFromEnvironment reads the CA either from a file path env var or from a direct PEM env var. Setting both is ambiguous, so the library rejects the configuration outright rather than guessing precedence.","triggerScenarios":"Calling ParsePoolConfig/NewPool while both the CA-file environment variable and the direct-PEM CA environment variable are set to non-empty values.","commonSituations":"A leftover CA path in the environment combined with an inline PEM injected by a secret manager; docker-compose or Helm chart exporting both variables; CI config accreting both settings over time.","solutions":["Unset one of the two environment variables — keep either the file path or the inline PEM, not both.","Standardize on the file variant (mount the CA as a file) in containerized deployments and remove the inline var from manifests.","Audit the deployment environment (compose file, k8s env, shell profile) for both variable names."],"exampleFix":"// before\nPOSTGRES_CA_FILE=/certs/ca.crt\nPOSTGRES_CA=\"-----BEGIN CERTIFICATE-----...\"\n// after\nPOSTGRES_CA_FILE=/certs/ca.crt\n# POSTGRES_CA removed","handlingStrategy":"validation","validationCode":"func caEnvConflict() bool {\n\tfile := os.Getenv(\"POSTGRES_CA_FILE\")\n\tdirect := os.Getenv(\"POSTGRES_CA\")\n\treturn strings.TrimSpace(file) != \"\" && strings.TrimSpace(direct) != \"\"\n}","typeGuard":null,"tryCatchPattern":"if err := checkCAEnvSanity(); err != nil {\n\tlog.Fatalf(\"CA config: %v\", err)\n}","preventionTips":["Pick one convention (file path recommended) and enforce it in deployment manifests.","Lint docker-compose/k8s env blocks for both CA variable names in CI.","Document the mutual exclusion where credentials are configured."],"tags":["postgres","tls","environment","config-conflict"],"backgroundTag":"conflicting-config-options","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"}