[Bug]: Unhandled SQLITE_BUSY during library scan crashes entire server
What happened?
audiobookshelf crashed (whole Node process exited) during a scheduled library scan that was running concurrently with ffmpeg jobs embedding cover art/metadata into audio files. The scanner tried to UPDATE libraryItems while a write lock was held elsewhere, SQLite returned SQLITE_BUSY, and that rejection was unhandled — which took down the entire server, not just the in-flight scan.
This is the second time in a few days this has happened. Both times the container had no restart policy set at the Docker level (my mistake, now fixed), so it sat completely offline for hours until I noticed and manually restarted it.
This looks like the same underlying pattern as #4572 (unhandled SQLITE_BUSY rejection = fatal crash), just triggered by the library scanner + ffmpeg metadata-write race instead of the scheduled backup job. Filing separately since the trigger/stack differs, but flagging the relation in case the fix is shared (e.g., a shared SQLite retry/queueing layer for all write paths, not just backups).
What did you expect to happen?
A single SQLITE_BUSY on one scan item update shouldn't be fatal to the whole process. Ideally: retry with backoff, or at minimum catch the rejection and log+skip that item instead of crashing the server.
Steps to reproduce the issue
Not 100% deterministic, but the pattern both times was: a scheduled library scan running while ffmpeg is actively writing embedded cover/metadata to audio files in the same library. Under load (many small files getting tagged back-to-back), a scan's libraryItems UPDATE apparently loses the race for the write lock.
Audiobookshelf version
v2.35.1
How are you running audiobookshelf?
Docker (Unraid Community Applications template, ghcr.io/advplyr/audiobookshelf:latest)
What OS is your Audiobookshelf server hosted from?
Other (Unraid 6.x)
Logs
[2026-07-04 00:54:56.758] ERROR: Error adding cover image and metadata: Error: ffmpeg exited with code 234: Conversion failed!
at ChildProcess._handle.onexit (node:internal/child_process:293:12)
[2026-07-04 00:54:56.759] ERROR: [AudioMetadataManager] Failed to tag audio file "..." Error: ffmpeg exited with code 234: Conversion failed!
...
[2026-07-04 01:30:22.618] FATAL: [Server] Unhandled rejection: Error
at Database.<anonymous> (/app/node_modules/sequelize/lib/dialects/sqlite/query.js:185:27)
at /app/node_modules/sequelize/lib/dialects/sqlite/query.js:183:50
at new Promise (<anonymous>)
at Query.run (/app/node_modules/sequelize/lib/dialects/sqlite/query.js:183:12)
at /app/node_modules/sequelize/lib/sequelize.js:315:28
at async SQLiteQueryInterface.update (/app/node_modules/sequelize/lib/dialects/abstract/query-interface.js:355:12)
at async LibraryItem.save (/app/node_modules/sequelize/lib/model.js:2490:35)
at async LibraryItemScanData.checkLibraryItemData (/app/server/scanner/LibraryItemScanData.js:276:7)
at async LibraryItemScanner.scanLibraryItem (/app/server/scanner/LibraryItemScanner.js:63:34)
at async LibraryScanner.scanFolderUpdates (/app/server/scanner/LibraryScanner.js:604:40) {
name: 'SequelizeTimeoutError',
parent: [Error: SQLITE_BUSY: database is locked] {
errno: 5,
code: 'SQLITE_BUSY',
sql: 'UPDATE `libraryItems` SET `mtime`=$1,`ctime`=$2,`size`=$3,`lastScan`=$4,`libraryFiles`=$5,`updatedAt`=$6 WHERE `id` = $7'
},
original: [Error: SQLITE_BUSY: database is locked] {
errno: 5,
code: 'SQLITE_BUSY',
sql: 'UPDATE `libraryItems` SET `mtime`=$1,`ctime`=$2,`size`=$3,`lastScan`=$4,`libraryFiles`=$5,`updatedAt`=$6 WHERE `id` = $7'
},
sql: 'UPDATE `libraryItems` SET `mtime`=$1,`ctime`=$2,`size`=$3,`lastScan`=$4,`libraryFiles`=$5,`updatedAt`=$6 WHERE `id` = $7',
parameters: {}
}(server exits after this — container has no restart on its own to recover)
Additional Notes
Related possible contributing factor for Unraid users specifically: my /config volume (where absdatabase.sqlite lives) was mounted via Unraid's /mnt/user/... FUSE user-share path rather than the underlying physical disk (/mnt/cache/...), even though the appdata share is "Cache: Only" (i.e. actually on local NVMe, not a network share). I've since remounted /config and /metadata directly against /mnt/cache/appdata/audiobookshelf/... to bypass FUSE, since #4572's thread notes SQLite must be on a real local filesystem and not something layered like a network mount — FUSE's locking semantics seem to fall into a similar bucket even when the backing store is local SSD. This is probably worth a callout in the docs for Unraid users, since the CA template's default Appdata path convention is /mnt/user/appdata/....
Either way, an unhandled SQLITE_BUSY from a scan write shouldn't be able to take the whole server down. Happy to provide more logs/details if useful.
Source: advplyr/audiobookshelf