{"record":{"id":"bdb0c6d9ee8b02f7","repo":"juanfont/headscale","slug":"getting-db-from-gorm-w","errorCode":null,"errorMessage":"getting DB from gorm: %w","messagePattern":"getting DB from gorm: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/db/db.go","lineNumber":1018,"sourceCode":"\tcurrentVersion := types.GetVersionInfo().Version\n\tif !isDev(currentVersion) {\n\t\terr = setDatabaseVersion(dbConn, currentVersion)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\n\t\t\t\t\"storing database version: %w\",\n\t\t\t\terr,\n\t\t\t)\n\t\t}\n\t}\n\n\t// Validate that the schema ends up in the expected state.\n\t// This is currently only done on sqlite as squibble does not\n\t// support Postgres and we use our sqlite schema as our source of\n\t// truth.\n\tif cfg.Database.Type == types.DatabaseSqlite {\n\t\tsqlConn, err := dbConn.DB()\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"getting DB from gorm: %w\", err)\n\t\t}\n\n\t\t// or else it blocks...\n\t\tsqlConn.SetMaxIdleConns(maxIdleConns)\n\n\t\tsqlConn.SetMaxOpenConns(maxOpenConns)\n\t\tdefer sqlConn.SetMaxIdleConns(1)\n\t\tdefer sqlConn.SetMaxOpenConns(1)\n\n\t\tctx, cancel := context.WithTimeout(context.Background(), contextTimeout)\n\t\tdefer cancel()\n\n\t\topts := squibble.DigestOptions{\n\t\t\tIgnoreTables: []string{\n\t\t\t\t// Litestream tables, these are inserted by\n\t\t\t\t// litestream and not part of our schema\n\t\t\t\t// https://litestream.io/how-it-works\n\t\t\t\t\"_litestream_lock\",","sourceCodeStart":1000,"sourceCodeEnd":1036,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/db/db.go#L1000-L1036","documentation":"headscale extracts the underlying *sql.DB from the GORM handle (dbConn.DB()) to run squibble schema validation on sqlite. This error means GORM could not return its raw database handle — which effectively only happens when the GORM handle is uninitialized or the underlying connection pool is already closed/invalid.","triggerScenarios":"cfg.Database.Type == sqlite and dbConn.DB() returns an error: the gorm.DB was built against a Dialector that failed silently during Open, or the pool was closed between Open and this call.","commonSituations":"Almost never seen in practice because gorm.Open errors earlier; can appear if the sqlite file becomes inaccessible immediately after Open (deleted file, unmounted volume) or with exotic embedded setups.","solutions":["Verify the sqlite database path still exists and is readable after gorm.Open (no concurrent deletion/unmount).","Check earlier log lines for a swallowed gorm.Open warning; fix the connection issue there.","Re-run startup — this is a transient-handle symptom, not a schema problem."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"// Retry by restarting: if gorm.Open partially failed, the earlier error is\n// the real cause. If using the db package programmatically:\n//   sqlDB, err := gormDB.DB()\n//   if err != nil { return fmt.Errorf(\"getting DB from gorm: %w\", err) }\n// treat as connection-layer failure, re-dial.","preventionTips":["Check gorm.Open's error immediately and fail fast.","Keep the sqlite file on stable local storage."],"tags":["database","gorm","sqlite","connection"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}