[Bug]: Unhandled SQLITE_BUSY during library scan crashes entire server

Author: ktwingstromCreated Jul 5, 2026Updated Sep 16, 2026

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.