Skip to content

Troubleshooting

vavallee edited this page Aug 14, 2026 · 7 revisions

Troubleshooting

Common problems and how to fix them.


Diagnosing problems with the log viewer

Before diving into specific sections below, check Settings → Logs for recent errors. The log viewer shows the last 200 entries from the in-process buffer (capacity: 1 000 entries) with colour-coded severity and auto-refresh.

If you need more detail:

  1. Open Settings → Logs.
  2. Change the Level selector to DEBUG. This takes effect immediately — no restart required. DEBUG output includes every indexer query, metadata fetch, file-move decision, and scheduler tick.
  3. Reproduce the problem. The new entries appear within 5 seconds (auto-refresh) or immediately after clicking Refresh.
  4. Use the WARN / ERROR filters to isolate the failure.
  5. Set Level back to INFO when done to avoid buffer overflow from high-volume DEBUG output.

If the log buffer doesn't contain enough history (entries were evicted before you looked), restart Bindery with BINDERY_LOG_LEVEL=debug in the environment to also write DEBUG output to the container's stdout from the beginning.

Attaching logs to a bug report: the Download button next to Clear filters saves whatever the current filters select as a plain text file (bindery-logs-<timestamp>.txt), so you don't need a shell in the container. The file records which filters were actually applied, and API keys, tokens and line breaks are stripped from every line before it is written. An export stops at 50,000 entries and says so. Admin-only, like the rest of the Logs tab.


Covers

Book covers are missing

Bindery pulls covers from OpenLibrary first. OpenLibrary attaches cover images to individual editions, not to the work record, so many works arrive without a cover even when editions exist.

As of v0.9.2, Bindery automatically falls back to Google Books and Hardcover when OpenLibrary has no cover for a work. Books added before that update won't have covers until the author is refreshed (Author detail page → Refresh button).

If covers are still missing after a refresh:

  • The book may be obscure enough that none of the three providers have a cover image.
  • The Google Books enricher requires no API key; the optional google_books_api_key setting in Settings → General increases the per-day quota but is not required for covers.

Covers are in the wrong language (Spanish, French, etc.)

OpenLibrary's cover API returns the cover attached to whichever edition it considers canonical for that work. For authors who sell heavily in a particular country — Yuval Noah Harari is a well-known example — the canonical edition in OpenLibrary is often the foreign-language one, so the cover it returns is, say, the Spanish edition.

As of v0.9.2, Bindery now also checks Google Books and Hardcover when a cover is missing. These providers consistently return the dominant-language (usually English) edition cover. If the book already has an OL cover but it's in the wrong language, trigger a refresh:

  1. Open the book detail page.
  2. Bindery will re-run enrichment on load if the cover is present but appears wrong — however, it won't automatically replace a cover it already has. To force it: open the author detail page and click Refresh. This re-fetches the catalogue from OpenLibrary. Because the OL work record may have been updated in the interim, the cover sometimes changes.

For books where the OL cover is persistently wrong and no refresh fixes it: the most reliable workaround is to wait for the underlying OpenLibrary data to be corrected by the community (openlibrary.org lets anyone edit editions).


Downloads

"No download client available"

Bindery found a result on an indexer but has nowhere to send it.

  • Go to Settings → Download Clients and confirm at least one client is enabled.
  • Check that the client's host/port are reachable from the Bindery container. Common issue: using localhost when the download client runs in a separate Docker container — use the container name or a host-accessible IP instead.
  • For NZB grabs, the client must accept the nzb protocol. For torrent grabs, torrent.

Books stay in "Downloading" forever

The download client accepted the file but Bindery's status poll isn't seeing completion.

  • Bindery polls for completed downloads every 15 seconds. Allow time for the download client to finish post-processing (unrar, par-check).
  • Confirm the download client's category matches what Bindery sends (Settings → Download Clients → Category).
  • Check that Bindery has read access to the download client's completed-downloads directory (path remapping may be needed — see below).

Import fails: "path does not exist" / permission denied

Bindery can see the completed download in the client but can't read the files.

The download client and Bindery likely map paths differently. Example: SABnzbd writes to /downloads/complete inside its container; Bindery may see the same share as /data/usenet/complete.

Fix: set Remote Path Mapping in Settings → Download Clients so Bindery translates the client's reported path to its own view.

Before you audit the mapping, rule out the other two causes, because the message used to blame path remapping for all three: the download may simply not have finished yet, or its files may have been moved into the library or deleted along with the book. Since v1.31.0 the message names what was missing and lists all three.

A download sits in the Queue and never imports

Bindery does not spend a retry attempt on a download whose files are not on this host, so a download waiting on its files can sit in the Queue indefinitely without failing. That is deliberate: a real download outlasts any retry budget, and burning attempts while waiting used to kill healthy grabs permanently.

If the files never appear, it does not wait forever. After about 30 minutes of finding nothing the download flips to importBlocked with a message naming the path it checked. From there:

  • Retry import re-runs the import against the files it has, with a fresh retry budget. Use this once you have fixed a path remap.
  • Grabbing the release again from search is allowed on a blocked download and reuses the same Queue row. Use this when the original files are genuinely gone. Before v1.31.0 a blocked download kept its claim on the release and every re-grab answered "already grabbed" with nothing you could click.
  • Match to book works the same on a blocked item as on a failed one, if the files are still there but were never matched.

Genuine import failures get 5 attempts, up from 3.

One format imported and the other silently disappeared

On a book tracked for both an ebook and an audiobook, an import of one format used to mark the other format's still-downloading grab as imported and drop it, because the "already in library" check looked at the book rather than at the format slot the download was filling. Nothing failed and nothing retried, so there was no error to find.

Fixed in v1.31.0. If you have books stuck missing one format from before the upgrade, re-search that format; the missing slot is eligible again.

NZBs stuck in "Waiting" (SABnzbd) or rejected (NZBGet)

This used to happen when the download client couldn't reach the indexer — common in containerized setups where SAB/NZBGet sits on a different network than the indexer.

Bindery now fetches the NZB itself and uploads the NZB content to the client (SAB via mode=addfile, NZBGet via a base64 append) instead of handing over the indexer URL. The client never needs to reach the indexer, so this class of failure is fixed.

If items still stall after the grab, the failure has moved to the Bindery→indexer hop: Bindery couldn't download the NZB. Enable DEBUG (Settings → Logs → Level → DEBUG) and look for the NZB fetch attempt.

Hardlinks are not actually hardlinking

Picking Hardlink as the import mode when your download folder and your library sit on different filesystems does not fail — imports quietly fall back to copying, which is why this usually surfaces as "my disk is filling up" rather than as an error. The usual cause is two Docker volume mounts that look like sibling paths inside the container but are separate mounts on the host.

Since v1.31.0 the Import Mode selector shows the warning and the specific reason inline, under the mode buttons where you make the choice, instead of only in the Storage section further down. Auto is unaffected; it already picks per download.

The fix is on the host side: mount a single volume that contains both the download directory and the library, so the two paths are on one filesystem.

Hardcover list sync is slow, or stops partway

A manual Sync now used to run inside the web request, so the server's 60 second request timeout cut it off: on a large shelf roughly a third of the books imported and the rest were lost to context deadline exceeded, silently. Since v1.31.0 the sync is a background job, the button answers immediately, and the import list row reports progress and result. One sync runs at a time.

The automatic cadence is no longer hardcoded to 24 hours either — Settings → General → Hardcover list sync interval takes anything from 1 hour to 7 days, and applies on the next restart.

Download client test fails with "connection refused"

Bindery appends a diagnostic hint to this error. Two non-obvious causes:

  1. The service is bound to 127.0.0.1 only and refuses a LAN-IP URL. Bind the download client to 0.0.0.0 (or its LAN interface), or run Bindery and the client in the same network namespace (host networking).
  2. A host firewall is rejecting the traffic on a Docker bridge network. Check the host's firewall rules for the client's port.

This is an interface-binding / firewall problem, not a container-IP or Docker-subnet problem — host-networked deployments have no Docker subnet to get wrong.


History tab empty for auto-grabs

Fixed in #938. Scheduler-initiated auto-grabs now record a Grabbed history event, the same as manual grabs. Previously only manual grabs wrote history, so monitored-author auto-search left the History tab empty even though downloads were happening.


Library scan

Existing books not being detected

The library scanner matches files to books using a fuzzy title + author heuristic. It won't match if:

  • The filename diverges significantly from the title (subtitles stripped, edition markers, etc.).
  • The file is in a directory not under any configured root folder.
  • The author's root folder path hasn't been set.

After fixing any of the above, go to Settings → Import and click Scan Now.

Every file that misses is listed in the Unmatched files table after the scan, and since v1.31.0 each row carries the reason it missed rather than one blanket suggestion: the parsed author is not in your library (fix the file's tags or its folder name), the author matched but has no book waiting for a file (populate that author's catalogue and refresh), no title matched, or no title could be read from the file at all.

An audiobook will not attach, no matter how many times you scan

Two causes, both fixed in v1.31.0, both of which produced exactly this symptom.

A folder holding both formats. A scan claimed a book the moment any one file matched it, so a folder with Title.epub and Title.m4b attached one and left the other unmatched until you ran a second scan. Claims are per format now, so one pass attaches both. A PDF, TXT, RTF or CBZ sitting alongside audio is treated as an audiobook supplement (the companion booklet Audible-style releases ship) and is not attached as the ebook; the same file in a folder with no audio in it is an ebook as usual.

An Artist tag carrying a contributor list. An m4b tagged Álvaro Enrigue, Natasha Wimmer - translator, Gabriel Porras could never reconcile: the tag overwrote the author your Author/Title/ folders had already resolved correctly, and no author in your library matches a whole contributor list. Those files returned to Unmatched on every scan, forever. The tag is still preferred when it matches an author you have; when it matches nobody, the scan now falls back to the folder author, then to each credited name in the list.

If you hit this before upgrading, the files are still on disk and untouched — run a scan on v1.31.0 and they attach.

Scanner matched a file to the wrong book

The scanner uses a similarity threshold; near-duplicate titles can occasionally collide. If a file is assigned to the wrong book:

  1. Go to the incorrectly matched book's detail page and click the Delete file button (this removes the path association, not the file on disk).
  2. Go to Settings → Import → Scan Now and let the scanner re-examine the file with the corrected state.

Author search returns no results / error

The Add Author search goes directly to OpenLibrary. If OpenLibrary is experiencing an outage or rate-limiting, the modal will show an error banner. Wait a few minutes and try again — OL outages are typically brief.


Authentication

Locked out / forgot password

Bindery stores passwords as argon2id PHC strings (OWASP 2024 parameters). There is no reset-by-email flow, and no "master password" or backdoor — an unrecoverable hash means the user row must be re-created.

Recovery: delete the users row and let the setup wizard re-run on the next page load.

  • Bare metal / shell access:
    sqlite3 /config/bindery.db "DELETE FROM users;"
    
  • Docker:
    docker exec -it <container> sqlite3 /config/bindery.db "DELETE FROM users;"
    
  • Kubernetes:
    kubectl exec -it deploy/bindery -- sqlite3 /config/bindery.db "DELETE FROM users;"
    

Reload the UI — Bindery detects zero users and shows the initial setup wizard so you can set a new username + password. API keys and stored settings are untouched.


Proxy SSO (v0.23.0+)

Bindery refuses to start: proxy mode requires BINDERY_TRUSTED_PROXY

BINDERY_TRUSTED_PROXY is empty. Proxy mode will not start without it — this prevents any LAN host from forging X-Forwarded-User: admin. Set it to your proxy's IP or CIDR (e.g. 172.20.0.0/16 for a Docker bridge network).

Every request returns 401 Unauthorized in proxy mode

Two common causes:

  1. Source IP not in trusted list. Check startup logs for trusted proxies: [...]. The IP Bindery sees for the request must match. In Docker, use the bridge network CIDR, not the container IP. Enable BINDERY_LOG_LEVEL=debug to log the source IP on each request.
  2. Header name mismatch. Authelia uses Remote-User; Authentik uses X-Authentik-Username. Set BINDERY_PROXY_AUTH_HEADER to match your proxy's header. Inspect headers with BINDERY_LOG_LEVEL=debug.

Login page still shows password form when proxy mode is active

Auth mode is not set to proxy. Confirm with GET /api/v1/auth/status — the response should include "mode": "proxy". If not, set it via Settings → General → Security or:

PUT /api/v1/auth/mode
{"mode": "proxy"}

New Bindery user created every time the IdP renames a user

Auto-provisioning ties the user row to the username in the header. If the IdP changes the username (e.g. email rename), a new row is created and the old user's data becomes inaccessible. Switch to a stable identifier — see docs/auth-proxy.md. An admin can merge orphaned users from Settings → Users.

Forged header accepted from an untrusted host

BINDERY_TRUSTED_PROXY is too broad (e.g. 0.0.0.0/0). Tighten it to only your proxy's IP or pod subnet. Confirm the effective list in startup logs.


OIDC (v0.24.0+)

invalid_client or unauthorized_client error on callback

Client ID / secret mismatch, or redirect URI not registered in the IdP. Verify the redirect URI exactly matches <BINDERY_OIDC_REDIRECT_BASE_URL>/api/v1/auth/oidc/<provider-id>/callback.

state mismatch or nonce mismatch error on callback

The state cookie set during login redirect was lost before the callback arrived. Usually caused by the reverse proxy stripping Set-Cookie response headers, or the callback being served on a different domain. Ensure your proxy passes Set-Cookie headers unchanged.

Two IdP users from different providers map to the same Bindery account

This cannot happen by design — Bindery's mapping key is the composite (oidc_issuer, oidc_sub), not oidc_sub alone. Two providers can both emit sub=1234 for different people, but because the issuer URL is different, they map to different Bindery users. If you observe cross-provider collision, file a bug.

OIDC client secret is visible to anyone with database read access

Client secrets are stored as plaintext in the settings table — the same posture as indexer API keys and download-client passwords. This is a known limitation. Protect bindery.db with chmod 600, restrict kubectl exec / docker exec access, and treat database backup files as sensitive. An env-var secret-ref pattern (reading secrets from environment variables instead of storing them in the DB) is planned for a future release.

OIDC login button does not appear on login page

The provider is not configured or not saved. Check Settings → Security → OIDC Providers. Confirm the provider has issuer, client_id, and client_secret set.

issuer mismatch in token validation

The issuer in the Bindery provider config does not match the iss claim in the ID token. For Keycloak this includes the realm: https://keycloak.example.com/realms/<realm>. For Authentik it includes the application slug: https://auth.example.com/application/o/<slug>/.

OIDC logout from IdP doesn't log out of Bindery

The Bindery session cookie is independent of the IdP session. To force global logout: regenerate the session secret in Settings → General → Security → Rotate session secret. Per-session revocation is planned for a future release.

Bindery cannot reach IdP discovery URL

Bindery must be able to reach <issuer>/.well-known/openid-configuration from inside the container or pod. Check network policy or firewall rules. Use BINDERY_LOG_LEVEL=debug to see the fetch attempt URL.


Multi-user (v1.0+)

Migration fails at startup: orphaned rows detected

Rows in downloads or other tables have broken foreign keys from earlier bugs. The migration prints a DELETE or UPDATE repair query — run it against the database, then restart Bindery.

Migration fails mid-run and Bindery exits

The migration runs in a transaction. If it fails, the database is not left in a half-migrated state — Bindery exits cleanly. Restore from the pre-upgrade backup, fix the reported issue, and retry. Always take a backup before upgrading to v1.0 — see docs/auth-multiuser.md.

403 Forbidden on API mutations that worked before v1.0

CSRF tokens are now required for session-cookie-authenticated mutations. Either:

  • Switch to API-key auth (X-Api-Key), which is CSRF-exempt.
  • Fetch a token: GET /api/v1/auth/csrf, then include X-CSRF-Token: <token> on mutations.

User A can see User B's data via the API

This is the documented default, not a bug. Per-user data isolation is opt-in via the BINDERY_ENFORCE_TENANCY environment variable, which defaults off — with it unset, every authenticated user shares one library view (single-user behaviour regardless of how many accounts exist). To partition library data per user, set BINDERY_ENFORCE_TENANCY=true and restart. Admins can see all users' data regardless of this flag, by design.

OIDC user auto-provisioned but needs the admin role

Set allowed_admin_groups in the OIDC provider config to the IdP group name. Existing provisioned users need a manual role update:

PUT /api/v1/auth/users/{id}/role
{"role": "admin"}

See also

  • docs/troubleshooting-auth.md — consolidated symptom→cause→fix table covering every risk mitigation across all auth phases (proxy bypass, OIDC sub collision, session rotation, CSRF, data isolation, migration failures)
  • docs/auth-proxy.md — Proxy SSO setup with Traefik/Authelia and Caddy/Authentik
  • docs/auth-oidc.md — OIDC setup for Google, Authelia, Authentik, Keycloak
  • docs/multi-user.md — Multi-user role model, capability matrix, user management
  • docs/upgrade-v2.md — v1.0 upgrade guide with backup, dry-run, and rollback steps
  • Reverse proxy and SSO — Traefik / Caddy / Nginx reverse proxy recipes

Clone this wiki locally