benbjohnson/litestream · error
get table list: %w
Error message
get table list: %w
What it means
After opening the database, shrinkDatabase calls c.getTableList(db), which queries sqlite_master for user tables (excluding sqlite_% and load_test). Any error from that query or from scanning rows is wrapped as `get table list: %w`. This indicates the schema read failed — usually a database-level problem such as corruption or a locked/busy database.
Source
Thrown at cmd/litestream-test/shrink.go:77
func (c *ShrinkCommand) shrinkDatabase(ctx context.Context) error {
initialSize, err := getDatabaseSize(c.DB)
if err != nil {
return fmt.Errorf("get initial size: %w", err)
}
slog.Info("Initial database size",
"size_mb", initialSize/1024/1024,
)
db, err := sql.Open("sqlite3", c.DB+"?_journal_mode=WAL")
if err != nil {
return fmt.Errorf("open database: %w", err)
}
defer db.Close()
tables, err := c.getTableList(db)
if err != nil {
return fmt.Errorf("get table list: %w", err)
}
slog.Info("Found tables", "count", len(tables))
totalDeleted := int64(0)
for _, table := range tables {
deleted, err := c.deleteFromTable(db, table)
if err != nil {
slog.Error("Failed to delete from table", "table", table, "error", err)
continue
}
totalDeleted += deleted
slog.Info("Deleted rows from table",
"table", table,
"rows_deleted", deleted,
)
}
View on GitHub (pinned to 4ed7a308f6)
Solutions
- Verify the file is a real SQLite database: `file <path>` or `sqlite3 <path> '.tables'`
- Close or wait for other processes holding locks on the database, then retry
- Add a busy timeout to the DSN (e.g. &_busy_timeout=5000) if concurrent access is expected
- Check the wrapped inner error for 'not a database' vs 'database is locked' and act accordingly
Example fix
// before
db, err := sql.Open("sqlite3", c.DB+"?_journal_mode=WAL")
// after
db, err := sql.Open("sqlite3", c.DB+"?_journal_mode=WAL&_busy_timeout=5000") Defensive patterns
Strategy: retry
Validate before calling
// confirm the file is a valid SQLite database before invoking
if header, err := os.ReadFile(dbPath); err == nil && len(header) >= 16 {
if string(header[:16]) != "SQLite format 3\x00" {
return fmt.Errorf("%s is not a SQLite database", dbPath)
}
} Try / catch
if err := runShrink(args); err != nil {
if strings.Contains(err.Error(), "get table list") {
if strings.Contains(err.Error(), "locked") {
time.Sleep(2 * time.Second)
return runShrink(args)
}
}
return err
} Prevention
- Never point the tool at LTX files, WAL fragments, or renamed non-SQLite files
- Stop concurrent writers or add a busy timeout to the DSN before heavy test runs
- Check the wrapped inner error: 'not a database' vs 'database is locked' need different fixes
When it happens
Trigger: The opened file is not a valid SQLite database (sql.Open succeeded lazily; the first real query fails with 'file is not a database'); the database is locked by another writer with no busy timeout; disk I/O error while reading sqlite_master.
Common situations: Pointing the tool at a non-SQLite file (e.g. an LTX file, WAL fragment, or text file renamed to .db); another process holds an exclusive lock during heavy load testing; reading a database written by a newer SQLite with an unsupported file format.
Understand the failure class
Background: Database query failed: Internal Server Error 500s wrapping SQL, Prisma, and connection failures — what to check first — this error's family across 16 libraries.
Related errors
- ensure test table: %w
- database does not exist: %w
- open database: %w
- create table: %w
- populate database: %w
AI-assisted analysis of benbjohnson/litestream@4ed7a308f6 (2026-09-06).
Data as JSON: /api/errors/db67e56e8f4408d0.
Report an issue: GitHub.