🐳 Docker Image
Pull the Docker image:
docker pull ghcr.io/drallgood/audiobookshelf-hardcover-sync:v4.0.0Or use the latest tag for the most recent release:
docker pull ghcr.io/drallgood/audiobookshelf-hardcover-sync:latest🔍 Image Details
- Digest: ``
- Source: View on GitHub
Changelog
[v4.0.0] - 2026-09-21
Added
- Account for per-run book outcomes: Account for each attempted Audiobookshelf book with one per-run outcome so processed totals reconcile with the outcome categories. This prepares the backend for a subsequent live-status change while preserving the existing status API. By @Snuffy2 (#183)
- Current-run sync outcome status: Per-profile status and summary responses now identify the run and its start time, state, candidate total, and processed count. Each processed item has one of seven mutually exclusive outcomes (
synced,already_current,skipped,needs_review,not_found,failed, or dry-runwould_sync), whose counts reconcile to the processed count. Needs-review, not-found, and failed records are available immediately as attention items. By @Snuffy2 (#184) - Secure on-demand sync details: Sync Status now loads authorized, book-level results for the current run on demand, while aggregate polling remains lightweight and profile access follows owner, viewer, administrator, and legacy-ownerless permissions. Profile API responses no longer echo Audiobookshelf or Hardcover tokens, regardless of authentication or role; with authentication enabled, pre-existing ownerless profiles are administrator-only and cannot currently be assigned to a user. Each result includes its Audiobookshelf cover, format, and available series position and links to its Audiobookshelf item and, when known, Hardcover; needs-review results also show the candidate's available series position and link the distinct candidate title, Audiobookshelf ASIN to Audible, and Audiobookshelf ISBN to Goodreads. By @Snuffy2. (#185)
- Run lifecycle and durable Sync Status history: Track accepted syncs through queued, running, finalizing, completed, canceled, and failed phases with generation-safe per-profile persistence, truthful partial outcomes, startup recovery for interrupted runs, and configurable terminal-report retention (default: 10). The streamlined status API and UI provide lightweight polling, clear phase-specific run timestamps, graceful cancellation and shutdown, isolated per-profile filesystem work, and compatible versioned state files that retain unversioned and v2 checkpoints. By @Snuffy2. (#186)
Fixed
- Preserve Audiobookshelf URL on partial profile updates: Updating a profile's config without an
audiobookshelf_url(for example a token-only update) cleared the saved endpoint, breaking the next sync against an empty URL. Omitted URLs are now preserved like omitted tokens. - Ebook detection and matching: Audiobookshelf reports
bookas the media type for ebooks as well as audiobooks, so ebooks were shown as "Audiobook" in Sync Status, were never skipped wheninclude_ebooksis off, and were matched only against Hardcover audiobook editions. Ebook-only items (an ebook file and no audio) are now recognized from their media content, using the same audio rule Audiobookshelf itself applies (audio files marked excluded do not count), honorinclude_ebooks, and are matched to Hardcover ebook editions by ASIN, ISBN, and title/author candidate details, including the lookups made while enriching mismatch records. Behavior change: with the defaultinclude_ebooks: false, ebook-only items are now skipped; they were previously synced against Hardcover audiobook editions, so users who relied on that should enableinclude_ebooks. By @Snuffy2 (#190) - Finished read dates for newly matched books: Create new Hardcover user books as
WANT_TO_READbefore adding the Audiobookshelf-dated finished read, preventing Hardcover from auto-creating a completion dated at sync time. For a book that would be newly matched, an item explicitly marked finished or computed at 100% progress withoutfinished_atis excluded from Hardcover matching until a finished date is available. This fix applies to new matches and does not repair existing reads created with the old sync-time synthetic date. Dry runs report this case as skipped rather than as a planned finish. By @Snuffy2 (#188) - Make sync status refresh unobtrusive: Refresh Sync Status without page flashing or interrupting interaction, and retry temporary profile-load failures. By @Snuffy2. (#180)
- Sync Status fetch errors: Remove misplaced Hardcover mismatch-display code from status loading so successful profile status requests no longer log an undefined-variable error by @Snuffy2. (#178)
- Move logging outside mutexes: Rate limiter, current-user lookup, sync state, mismatch collection, sync summary, and OIDC state cleanup no longer write log entries while holding a lock, so a slow or blocked log writer cannot stall other requests. Concurrent Hardcover current-user lookups share one in-flight request, which is released, along with the Hardcover request concurrency permit, even if the request panics. By @Snuffy2. (#191)
- Limit raw API response logging to debug: Routes the full Audiobookshelf library-items API response through debug logging instead of writing it directly to standard error. (#174)
- Prevent Hardcover mutations during dry runs: dry_run was not working correctly. Prevent dry-run syncs from calling Hardcover mutation APIs while preserving the read operations needed to report intended changes, and identify active dry runs on the Sync Status page. (#175)
- Correct rate limiter pacing and recovery: Corrects Hardcover API pacing so rate-limit handling remains bounded, responds to authoritative server guidance, and recovers cleanly after throttling. This prevents concurrent waits from accelerating requests and prevents incomplete or unguided rate-limit responses from leaving syncs indefinitely stalled or incorrectly paced. Removes the obsolete burst setting and unused token-bucket state; admission is governed by request pacing and concurrency. (#176)
- Suppress misleading request logs during a daily quota pause: While an exhausted Hardcover daily quota holds requests until its reset, request-intent logs (GraphQL requests, Hardcover searches, and the sync service's per-book search and read-status messages) are no longer written for requests that are not being sent. The client now acquires its rate-limiter permit before logging an executed GraphQL request. Pacing and admission behavior are unchanged. By @Snuffy2. (#192)
Changed
- Moved and updated repository guidance from
.github/copilot-instructions.mdto rootAGENTS.md, including contributor workflow and behavior-focused testing guidance by @Snuffy2. (#177) - Incremental sync checkpoints: Persist per-book sync state after each processed book so completed work survives cancellation or process termination before the full library run finishes. State-file replacement is now atomic, dry runs remain non-persistent, and checkpoint failures stop the sync instead of reporting false success. Failed Hardcover status updates leave the affected book uncheckpointed for retry, even if its read progress was saved; a persistent status failure can therefore recur while other books continue. An unavailable status on an existing read is not blindly changed or checkpointed after a read update. By @Snuffy2. (#179)
- Run gofmt across repo by @Snuffy2 (#187)
- Reduce noisy info-level logging: Log startup and configuration dumps, request and client detail, cache statistics, and mismatch enrichment at debug instead of info, since info is the default log level. Per-book outcomes and skip reasons, run and library progress, dry-run confirmations, rate limiter pacing, the web UI port, lifecycle events, and the sync summary stay at info. By @Snuffy2. (#189)