{"record":{"id":"ece7dc22b0075ec0","repo":"charmbracelet/crush","slug":"failed-to-connect-to-database-w-ece7dc","errorCode":null,"errorMessage":"failed to connect to database: %w","messagePattern":"failed to connect to database: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/cmd/stats.go","lineNumber":174,"sourceCode":"\t\t\treturn fmt.Errorf(\"failed to gather stats from projects: %w\", err)\n\t\t}\n\tdefault:\n\t\tcfg, err := config.Init(\"\", dataDir, false)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to initialize config: %w\", err)\n\t\t}\n\t\tif dataDir == \"\" {\n\t\t\tdataDir = cfg.Config().Options.DataDirectory\n\t\t}\n\t\tif shouldEnableMetrics(cfg.Config()) {\n\t\t\tevent.Init()\n\t\t}\n\n\t\tevent.StatsViewed()\n\n\t\tconn, err := db.Connect(ctx, dataDir)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to connect to database: %w\", err)\n\t\t}\n\t\tdefer conn.Close()\n\n\t\tstats, err := gatherStats(ctx, conn)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"failed to gather stats: %w\", err)\n\t\t}\n\n\t\tprojectStats = []ProjectStats{{ProjectPath: \"\", Stats: stats}}\n\t}\n\n\tif len(projectStats) == 0 {\n\t\treturn fmt.Errorf(\"no data available: no projects found\")\n\t}\n\n\t// Merge stats from all projects.\n\tmergedStats := mergeStats(projectStats)\n","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/cmd/stats.go#L156-L192","documentation":"runStats wraps any error from db.Connect, which opens (or fetches from a pool) the SQLite database at <dataDir>/crush.db, pings it, and applies migrations. This error means the database could not be opened or pinged: the data directory could not be created, the data-dir lock could not be acquired (another crush process holds it), the SQLite file is corrupt/unreadable, or the ping failed (bad driver state, cancelled context, file locked).","triggerScenarios":"Running `crush stats` where: (1) the data dir path is invalid or permissions deny os.MkdirAll; (2) another crush process holds the data-dir lock (WithDataDirLock); (3) crush.db is corrupted (e.g. SQLITE_NOTADB from prior WAL desync) or truncated; (4) the context is cancelled or the filesystem disallows opening/writing crush.db; (5) dataDir resolved to empty (db.Connect returns \"data.dir is not set\").","commonSituations":"Running stats while another crush instance is open on the same data dir; running with XDG_DATA_HOME/CRUSH_DATA pointed at a read-only or nonexistent mount; copying a crush.db without its -wal/-shm files causing SQLITE_NOTADB; disk full; running in a sandbox with restricted HOME.","solutions":["Check no other crush process is running against the same data dir (lock contention) and that <dataDir> is writable: ls -ld \"$HOME/.local/share/crush\".","Inspect crush.db integrity with `sqlite3 crush.db 'PRAGMA integrity_check;'`; restore from backup or remove corrupted crush.db (and -wal/-shm) if acceptable.","Verify env vars controlling the data dir (XDG_DATA_HOME, HOME) point to a writable directory; unset overrides or fix the path.","If migrations or lock logs appear in stderr, read the wrapped %w cause shown by the error and address it specifically.","Re-run the command; transient ping/IO failures (network home dirs, EFS/NFS) may resolve on retry."],"exampleFix":"// before\nconn, err := db.Connect(ctx, dataDir)\nif err != nil {\n\treturn fmt.Errorf(\"failed to connect to database: %w\", err)\n}\n// after\nif dataDir == \"\" {\n\tdataDir = defaultDataDir() // fall back to the standard data dir\n}\nconn, err := db.Connect(ctx, dataDir)\nif err != nil {\n\tvar lockErr *db.DataDirLockError\n\tif errors.As(err, &lockErr) {\n\t\treturn fmt.Errorf(\"another crush process is using %s; close it and retry\", dataDir)\n\t}\n\treturn fmt.Errorf(\"failed to connect to database: %w\", err)\n}","handlingStrategy":"validation","validationCode":"if info, err := os.Stat(filepath.Join(dataDir, \"crush.db\")); err == nil && info.Size() == 0 {\n\treturn fmt.Errorf(\"database file %s is empty/corrupt\", dataDir)\n}\nif err := unix.Flock(lockFile, unix.LOCK_EX|unix.LOCK_NB); err != nil {\n\treturn fmt.Errorf(\"another crush instance holds the data dir\")\n}","typeGuard":null,"tryCatchPattern":"conn, err := db.Connect(ctx, dataDir)\nif err != nil {\n\tvar lockErr *fs.PathError\n\tif errors.As(err, &lockErr) {\n\t\treturn fmt.Errorf(\"data dir unusable (%s): %w\", dataDir, err)\n\t}\n\treturn err\n}","preventionTips":["Keep the data dir on a local writable filesystem (not NFS) so SQLite locks and pings work.","Close other crush instances before running stats against the same data dir.","Back up crush.db together with its -wal and -shm files.","Verify HOME/XDG_DATA_HOME before invoking the command."],"tags":["database","sqlite","go","cli"],"backgroundTag":"sqlite-open-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}