gastownhall/beads · error
uow: external TLS: %w
Error message
uow: external TLS: %w
What it means
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.
Source
Thrown at internal/storage/uow/external_doltserver_provider.go:56
if rootUser == "" {
return nil, fmt.Errorf("uow: rootUser must not be empty")
}
if err := external.Validate(); err != nil {
return nil, fmt.Errorf("uow: external: %w", err)
}
absServerRootDir, err := filepath.Abs(serverRootDir)
if err != nil {
return nil, fmt.Errorf("uow: resolving server root dir: %w", err)
}
if err := os.MkdirAll(absServerRootDir, config.BeadsDirPerm); err != nil {
return nil, fmt.Errorf("uow: creating server root directory: %w", err)
}
tlsConfigName, err := registerExternalTLSConfig(external)
if err != nil {
return nil, fmt.Errorf("uow: external TLS: %w", err)
}
ep, err := proxy.GetCreateDatabaseProxyServerEndpoint(absServerRootDir, proxy.OpenOpts{
Backend: proxy.BackendExternal,
LogFilePath: serverLogFilePath,
External: external,
IdleTimeout: idleTimeout,
Port: proxyPort,
})
if err != nil {
return nil, fmt.Errorf("uow: get proxy endpoint: %w", err)
}
return openAndInitSchema(ctx, ep, database, rootUser, rootPassword, tlsConfigName, teamServer, expectedProjectID, applyProviderOptions(opts))
}
func registerExternalTLSConfig(external configfile.ExternalDoltConfig) (string, error) {
if !external.TLSRequired {View on GitHub (pinned to 71377f2769)
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.
Example fix
// before
external := configfile.ExternalDoltConfig{Host: "db.internal", Port: 3307, TLSRequired: true, TLSCACert: "ca.pem"} // relative path, wrong cwd
// after
caPath, _ := filepath.Abs("/etc/beads/tls/ca.pem")
if _, err := os.Stat(caPath); err != nil { /* fail fast with a clear message */ }
external := configfile.ExternalDoltConfig{Host: "db.internal", Port: 3307, TLSRequired: true, TLSCACert: caPath, TLSServerName: "db.internal"} Defensive patterns
Strategy: validation
Validate before calling
func validateTLSMaterial(ext configfile.ExternalDoltConfig) error {
if !ext.TLSRequired {
return nil
}
for _, p := range []string{ext.TLSCACert, ext.TLSCert, ext.TLSKey} {
if p != "" {
if _, err := os.Stat(p); err != nil {
return fmt.Errorf("TLS file %s: %w", p, err)
}
}
}
return ext.Validate()
} Type guard
func tlsFilesReadable(ext configfile.ExternalDoltConfig) bool {
for _, p := range []string{ext.TLSCACert, ext.TLSCert, ext.TLSKey} {
if p != "" && !fileReadable(p) {
return false
}
}
return true
} Try / catch
provider, err := uow.NewExternalDoltServerUOWProvider(ctx, root, db, logPath, ext, user, pw, port, 0, false, "")
if err != nil {
if strings.Contains(err.Error(), "uow: external TLS:") {
log.Fatalf("TLS config invalid: check TLSCACert/TLSCert/TLSKey paths and contents: %v", err)
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- not using Dolt backend (configured backend %q)
- multiple .doltcfg directories detected
- dolt directory is required
- ErrExec
- ErrFSCKTimeout
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/3cffc25a18f11d35.
Report an issue: GitHub.