{"record":{"id":"3cffc25a18f11d35","repo":"gastownhall/beads","slug":"uow-external-tls-w","errorCode":null,"errorMessage":"uow: external TLS: %w","messagePattern":"uow: external TLS: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/uow/external_doltserver_provider.go","lineNumber":56,"sourceCode":"\tif rootUser == \"\" {\n\t\treturn nil, fmt.Errorf(\"uow: rootUser must not be empty\")\n\t}\n\tif err := external.Validate(); err != nil {\n\t\treturn nil, fmt.Errorf(\"uow: external: %w\", err)\n\t}\n\n\tabsServerRootDir, err := filepath.Abs(serverRootDir)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"uow: resolving server root dir: %w\", err)\n\t}\n\n\tif err := os.MkdirAll(absServerRootDir, config.BeadsDirPerm); err != nil {\n\t\treturn nil, fmt.Errorf(\"uow: creating server root directory: %w\", err)\n\t}\n\n\ttlsConfigName, err := registerExternalTLSConfig(external)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"uow: external TLS: %w\", err)\n\t}\n\n\tep, err := proxy.GetCreateDatabaseProxyServerEndpoint(absServerRootDir, proxy.OpenOpts{\n\t\tBackend:     proxy.BackendExternal,\n\t\tLogFilePath: serverLogFilePath,\n\t\tExternal:    external,\n\t\tIdleTimeout: idleTimeout,\n\t\tPort:        proxyPort,\n\t})\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"uow: get proxy endpoint: %w\", err)\n\t}\n\n\treturn openAndInitSchema(ctx, ep, database, rootUser, rootPassword, tlsConfigName, teamServer, expectedProjectID, applyProviderOptions(opts))\n}\n\nfunc registerExternalTLSConfig(external configfile.ExternalDoltConfig) (string, error) {\n\tif !external.TLSRequired {","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/uow/external_doltserver_provider.go#L38-L74","documentation":"NewExternalDoltServerUOWProvider wraps any failure from registerExternalTLSConfig as \"uow: external TLS\". That helper only runs when external.TLSRequired is set; it either fails to build a tls.Config from the ExternalDoltConfig (via external.TLSClientConfig()) or fails to register it with the go-sql-driver mysql package. It is a configuration/data problem with the TLS material, not a network failure — the connection hasn't been attempted yet.","triggerScenarios":"Calling NewExternalDoltServerUOWProvider with an ExternalDoltConfig where TLSRequired=true and: TLSCACert points to a missing/unreadable file, the CA PEM contains no parseable certificates, TLSCert/TLSKey are missing or invalid (tls.LoadX509KeyPair fails), or mysql.RegisterTLSConfig rejects the config name.","commonSituations":"Typo'd or relative paths for TLSCACert/TLSCert/TLSKey in config.yaml that don't resolve from the process working directory; server rotated to a CA file that doesn't exist locally; client cert/key file permissions deny read; empty or malformed PEM exported from a secrets manager.","solutions":["Verify every TLS file path in the ExternalDoltConfig is absolute and readable by the process (ls/stat the TLSCACert, TLSCert, TLSKey files).","Validate the CA PEM parses (openssl x509 -in ca.pem) and the cert/key pair matches (openssl x509 -noout -modulus).","If TLS is not actually required by the server, unset TLSRequired so registerExternalTLSConfig is skipped.","Re-run config validation (external.Validate()) before constructing the provider to catch earlier misconfigurations."],"exampleFix":"// before\nexternal := configfile.ExternalDoltConfig{Host: \"db.internal\", Port: 3307, TLSRequired: true, TLSCACert: \"ca.pem\"} // relative path, wrong cwd\n// after\ncaPath, _ := filepath.Abs(\"/etc/beads/tls/ca.pem\")\nif _, err := os.Stat(caPath); err != nil { /* fail fast with a clear message */ }\nexternal := configfile.ExternalDoltConfig{Host: \"db.internal\", Port: 3307, TLSRequired: true, TLSCACert: caPath, TLSServerName: \"db.internal\"}","handlingStrategy":"validation","validationCode":"func validateTLSMaterial(ext configfile.ExternalDoltConfig) error {\n\tif !ext.TLSRequired {\n\t\treturn nil\n\t}\n\tfor _, p := range []string{ext.TLSCACert, ext.TLSCert, ext.TLSKey} {\n\t\tif p != \"\" {\n\t\t\tif _, err := os.Stat(p); err != nil {\n\t\t\t\treturn fmt.Errorf(\"TLS file %s: %w\", p, err)\n\t\t\t}\n\t\t}\n\t}\n\treturn ext.Validate()\n}","typeGuard":"func tlsFilesReadable(ext configfile.ExternalDoltConfig) bool {\n\tfor _, p := range []string{ext.TLSCACert, ext.TLSCert, ext.TLSKey} {\n\t\tif p != \"\" && !fileReadable(p) {\n\t\t\treturn false\n\t\t}\n\t}\n\treturn true\n}","tryCatchPattern":"provider, err := uow.NewExternalDoltServerUOWProvider(ctx, root, db, logPath, ext, user, pw, port, 0, false, \"\")\nif err != nil {\n\tif strings.Contains(err.Error(), \"uow: external TLS:\") {\n\t\tlog.Fatalf(\"TLS config invalid: check TLSCACert/TLSCert/TLSKey paths and contents: %v\", err)\n\t}\n\treturn err\n}","preventionTips":["Always use absolute paths for TLS files in config.yaml.","Run external.Validate() early at startup, not just before connecting.","Keep CA/cert/key files under a permission-checked directory the process can read.","Test TLS material changes in CI before rotating production certs."],"tags":["tls","configuration","dolt","storage"],"backgroundTag":"tls-certificate-load-failed","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}