What went wrong?
When two Kobo devices are registered for the same user account (each with its own device endpoint/token), only the first device to sync receives the library. The second device completes its sync without errors but gets zero books — and never receives any afterwards.
The cause appears to be that Kobo sync progress is tracked per user instead of per device:
kobo_library_snapshots has a UNIQUE constraint on user_id (server/src/db/schema/kobo.ts), so all devices of a user share a single snapshot.
- Each book in
kobo_snapshot_books has one boolean synced flag. When device A syncs, KoboSyncService.getDelta pages through unsynced books and marks them synced = true (server/src/modules/kobo/services/kobo-sync.service.ts). When device B syncs afterwards, the pending query finds nothing and returns only collection tags — no entitlements.
- The incoming
x-kobo-synctoken header is logged but never used in getDelta (server/src/modules/kobo/kobo-sync.controller.ts), so the server can't distinguish a fresh device from a fully-synced one. The device token itself is only used for auth and URL building, not for sync state.
There is already partial handling for this: KoboDeviceService.createDevice calls invalidateSnapshot when a second device is registered, which resets all synced flags. But that's a one-shot, user-global reset — whichever device happens to sync first afterwards consumes the entire delta again. Since Kobos auto-sync on wake/Wi-Fi, the already-synced device usually wins this race, and the new device stays empty.
This also affects every later snapshot invalidation (library changes, settings changes): only one of the user's devices ever receives each delta.
Steps to reproduce
- Create two Kobo devices for the same user under Settings → Kobo, and configure each Kobo e-reader with its own endpoint URL.
- Sync Kobo A → all books arrive correctly.
- Sync Kobo B → sync completes without error, but no books appear.
- Any further syncs on Kobo B also return nothing (verified:
GET /v1/library/sync returns an empty entitlement list because all kobo_snapshot_books rows are already synced = true).
Expected vs actual behavior
Expected: Each registered device independently receives the full library on its first sync, and subsequent deltas (added/changed/removed books) reach every device.
Actual: Only the first device to sync after snapshot creation/invalidation receives the books. All other devices get an empty delta, permanently.
Your setup
- BookOrbit: bookorbit:latest (1.9.0)
- Deployment: Docker (docker-compose, official image)
- OS: Ubuntu
- Devices: 2× Kobo e-readers
Relevant logs (optional)
Screenshots (optional)
No response
Before submitting
What went wrong?
When two Kobo devices are registered for the same user account (each with its own device endpoint/token), only the first device to sync receives the library. The second device completes its sync without errors but gets zero books — and never receives any afterwards.
The cause appears to be that Kobo sync progress is tracked per user instead of per device:
kobo_library_snapshotshas aUNIQUEconstraint onuser_id(server/src/db/schema/kobo.ts), so all devices of a user share a single snapshot.kobo_snapshot_bookshas one booleansyncedflag. When device A syncs,KoboSyncService.getDeltapages through unsynced books and marks themsynced = true(server/src/modules/kobo/services/kobo-sync.service.ts). When device B syncs afterwards, the pending query finds nothing and returns only collection tags — no entitlements.x-kobo-synctokenheader is logged but never used ingetDelta(server/src/modules/kobo/kobo-sync.controller.ts), so the server can't distinguish a fresh device from a fully-synced one. The device token itself is only used for auth and URL building, not for sync state.There is already partial handling for this:
KoboDeviceService.createDevicecallsinvalidateSnapshotwhen a second device is registered, which resets allsyncedflags. But that's a one-shot, user-global reset — whichever device happens to sync first afterwards consumes the entire delta again. Since Kobos auto-sync on wake/Wi-Fi, the already-synced device usually wins this race, and the new device stays empty.This also affects every later snapshot invalidation (library changes, settings changes): only one of the user's devices ever receives each delta.
Steps to reproduce
GET /v1/library/syncreturns an empty entitlement list because allkobo_snapshot_booksrows are alreadysynced = true).Expected vs actual behavior
Expected: Each registered device independently receives the full library on its first sync, and subsequent deltas (added/changed/removed books) reach every device.
Actual: Only the first device to sync after snapshot creation/invalidation receives the books. All other devices get an empty delta, permanently.
Your setup
Relevant logs (optional)
Screenshots (optional)
No response
Before submitting