{"record":{"id":"0b374bf627f8b782","repo":"juanfont/headscale","slug":"busy-timeout-must-be-0","errorCode":null,"errorMessage":"busy_timeout must be >= 0","messagePattern":"busy_timeout must be >= 0","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/sqliteconfig/config.go","lineNumber":15,"sourceCode":"// Package sqliteconfig provides type-safe configuration for SQLite databases\n// with proper enum validation and URL generation for modernc.org/sqlite driver.\npackage sqliteconfig\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"slices\"\n\t\"strings\"\n)\n\n// Errors returned by config validation.\nvar (\n\tErrPathEmpty           = errors.New(\"path cannot be empty\")\n\tErrBusyTimeoutNegative = errors.New(\"busy_timeout must be >= 0\")\n\tErrInvalidJournalMode  = errors.New(\"invalid journal_mode\")\n\tErrInvalidAutoVacuum   = errors.New(\"invalid auto_vacuum\")\n\tErrWALAutocheckpoint   = errors.New(\"wal_autocheckpoint must be >= -1\")\n\tErrInvalidSynchronous  = errors.New(\"invalid synchronous\")\n\tErrInvalidTxLock       = errors.New(\"invalid txlock\")\n)\n\nconst (\n\t// DefaultBusyTimeout is the default busy timeout in milliseconds.\n\tDefaultBusyTimeout = 10000\n)\n\n// JournalMode represents SQLite journal_mode pragma values.\n// Journal modes control how SQLite handles write transactions and crash recovery.\n//\n// Performance vs Durability Tradeoffs:\n//\n// WAL (Write-Ahead Logging) - Recommended for production:","sourceCodeStart":1,"sourceCodeEnd":33,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/sqliteconfig/config.go#L1-L33","documentation":"Exported validation error from hscontrol/db/sqliteconfig rejecting a negative busy_timeout in the SQLite DSN. busy_timeout (ms) controls how long SQLite waits on a locked database before returning SQLITE_BUSY; negative values are meaningless and are rejected by Validate(). The default is DefaultBusyTimeout = 10000 ms.","triggerScenarios":"Setting database.sqlite.busy_timeout to a negative number in config, or passing a negative BusyTimeout when constructing the config programmatically, then generating the DSN.","commonSituations":"Env var override parsed with a stray '-' sign; config templating arithmetic producing -1 as a sentinel; misunderstanding 0 (disable) vs negative.","solutions":["Set busy_timeout to a non-negative value — 0 disables waiting, the default 10000 (ms) is a good baseline","Increase it (e.g. 10000-30000) rather than making it negative if you are fighting 'database is locked' errors","Audit env overrides like HEADSCALE_DB_SQLITE_BUSY_TIMEOUT for parsing bugs"],"exampleFix":"# before\ndatabase:\n  sqlite:\n    busy_timeout: -1\n\n# after\ndatabase:\n  sqlite:\n    busy_timeout: 10000","handlingStrategy":"validation","validationCode":"if cfg.BusyTimeout != nil && *cfg.BusyTimeout < 0 {\n    return fmt.Errorf(\"invalid busy_timeout %d: %w\", *cfg.BusyTimeout, sqliteconfig.ErrBusyTimeoutNegative)\n}","typeGuard":"func validBusyTimeout(ms *int) bool { return ms == nil || *ms >= 0 }","tryCatchPattern":"if err := cfg.Validate(); err != nil {\n    if errors.Is(err, sqliteconfig.ErrBusyTimeoutNegative) {\n        return errors.New(\"busy_timeout must be >= 0 (0 disables waiting; default 10000 ms)\")\n    }\n    return err\n}","preventionTips":["Parse numeric config with strict validators; reject sign errors at load time","Use 0 to disable busy-waiting, never a negative sentinel","For 'database is locked' errors raise busy_timeout instead of mutating it blindly"],"tags":["sqlite","config","validation","busy-timeout","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}