juanfont/headscale · error
invalid config: %w
Error message
invalid config: %w
What it means
ToURL() calls Validate() first and wraps any validation failure with the 'invalid config: ' prefix before building the modernc.org/sqlite connection string. This error is a wrapper: the real cause is one of the sentinel validation errors (empty path, negative busy_timeout, invalid journal_mode/auto_vacuum/wal_autocheckpoint/synchronous/txlock).
Source
Thrown at hscontrol/db/sqliteconfig/config.go:349
}
if c.Synchronous != "" && !c.Synchronous.IsValid() {
return fmt.Errorf("%w: %s", ErrInvalidSynchronous, c.Synchronous)
}
if c.TxLock != "" && !c.TxLock.IsValid() {
return fmt.Errorf("%w: %s", ErrInvalidTxLock, c.TxLock)
}
return nil
}
// ToURL builds a properly encoded SQLite connection string using _pragma parameters
// compatible with modernc.org/sqlite driver.
func (c *Config) ToURL() (string, error) {
err := c.Validate()
if err != nil {
return "", fmt.Errorf("invalid config: %w", err)
}
// Handle different database types
var baseURL string
if c.Path == ":memory:" {
baseURL = ":memory:"
} else {
baseURL = "file:" + c.Path
}
// Build query parameters
var queryParts []string
// Add _txlock first (it's a connection parameter, not a pragma)
if c.TxLock != "" {
queryParts = append(queryParts, "_txlock="+string(c.TxLock))
}
View on GitHub (pinned to 565fd254d0)
Solutions
- Unwrap the error chain (errors.Is against the sqliteconfig.Err* sentinels) to find the exact field at fault.
- Fix the offending field per its sentinel's contract (non-empty path, valid pragma strings, proper ranges).
- Call cfg.Validate() early at config-load time to get a precise error before ToURL is reached.
Example fix
// before
url, err := cfg.ToURL()
if err != nil { log.Fatal(err) } // 'invalid config: invalid journal_mode: wal'
// after
if err := cfg.Validate(); err != nil { // precise, actionable
log.Fatal().Err(err).Msg("sqlite config")
}
url, err := cfg.ToURL() Defensive patterns
Strategy: try-catch
Validate before calling
// Fail fast with a precise error before building the DSN:
if err := cfg.Validate(); err != nil {
return fmt.Errorf("validating sqlite config: %w", err)
} Try / catch
url, err := cfg.ToURL()
if err != nil {
switch {
case errors.Is(err, sqliteconfig.ErrPathEmpty),
errors.Is(err, sqliteconfig.ErrBusyTimeoutNegative),
errors.Is(err, sqliteconfig.ErrInvalidJournalMode),
errors.Is(err, sqliteconfig.ErrInvalidAutoVacuum),
errors.Is(err, sqliteconfig.ErrWALAutocheckpoint),
errors.Is(err, sqliteconfig.ErrInvalidSynchronous),
errors.Is(err, sqliteconfig.ErrInvalidTxLock):
// config bug: report field-level error, do not retry
}
return err
} Prevention
- Run cfg.Validate() during config parsing so errors point at the exact field.
- Build configs from the package's typed constants instead of raw strings.
- Fail headscale startup on config validation errors rather than falling back to defaults.
When it happens
Trigger: Calling sqliteconfig.Config.ToURL() on any config that fails Validate(): empty Path, negative BusyTimeout, or any invalid enum pragma value. ToURL is the last step before opening the database, so this surfaces at headscale startup.
Common situations: Malformed database section in headscale's config file (typo'd pragma names, negative numbers), or programmatic configs built with unset/zero fields (empty Path triggers ErrPathEmpty).
Related errors
- path cannot be empty
- busy_timeout must be >= 0
- %w, got %d
- %w: %s
- wildcard (*) is not supported as SSH destination
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/4c944a5f1e0db8c9.
Report an issue: GitHub.