juanfont/headscale · error · ErrBusyTimeoutNegative

%w, got %d

Error message

%w, got %d

What it means

Config.Validate rejected the SQLite configuration because BusyTimeout is negative. The sqliteconfig package requires busy_timeout >= 0 milliseconds; this pragma controls how long SQLite waits on a locked database before returning SQLITE_BUSY. The sentinel ErrBusyTimeoutNegative is wrapped with the offending value.

Source

Thrown at hscontrol/db/sqliteconfig/config.go:318

}

// Memory returns a configuration for in-memory databases.
func Memory() *Config {
	return &Config{
		Path:              ":memory:",
		WALAutocheckpoint: -1, // not set, use driver default
		ForeignKeys:       true,
	}
}

// Validate checks if all configuration values are valid.
func (c *Config) Validate() error {
	if c.Path == "" {
		return ErrPathEmpty
	}

	if c.BusyTimeout < 0 {
		return fmt.Errorf("%w, got %d", ErrBusyTimeoutNegative, c.BusyTimeout)
	}

	if c.JournalMode != "" && !c.JournalMode.IsValid() {
		return fmt.Errorf("%w: %s", ErrInvalidJournalMode, c.JournalMode)
	}

	if c.AutoVacuum != "" && !c.AutoVacuum.IsValid() {
		return fmt.Errorf("%w: %s", ErrInvalidAutoVacuum, c.AutoVacuum)
	}

	if c.WALAutocheckpoint < -1 {
		return fmt.Errorf("%w, got %d", ErrWALAutocheckpoint, c.WALAutocheckpoint)
	}

	if c.Synchronous != "" && !c.Synchronous.IsValid() {
		return fmt.Errorf("%w: %s", ErrInvalidSynchronous, c.Synchronous)
	}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Set BusyTimeout to a non-negative millisecond value (DefaultBusyTimeout = 10000 is a safe choice).
  2. If the value comes from a config file, check for a stray minus sign or a bad unit conversion.
  3. Remember: -1 is valid for WALAutocheckpoint but NOT for BusyTimeout.

Example fix

// before
cfg := sqliteconfig.Config{Path: p, BusyTimeout: -5000}
url, err := cfg.ToURL() // ErrBusyTimeoutNegative

// after
cfg := sqliteconfig.Config{Path: p, BusyTimeout: sqliteconfig.DefaultBusyTimeout}
url, err := cfg.ToURL()
Defensive patterns

Strategy: validation

Validate before calling

if cfg.BusyTimeout < 0 {
    return fmt.Errorf("busy_timeout must be >= 0, got %d", cfg.BusyTimeout)
}

Prevention

When it happens

Trigger: Constructing a sqliteconfig.Config with a negative BusyTimeout (e.g. from parsing a config file where the value was set to -1 or computed as a negative duration), then calling Validate() or ToURL().

Common situations: Configuration typos (negative number), or code translating time.Duration in nanoseconds to milliseconds incorrectly producing a negative value; copying a wal_autocheckpoint=-1 convention onto busy_timeout.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/7b818b83bb7c8d2f. Report an issue: GitHub.