You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Your duplicate author theory is right, and I found the mechanism. You worked it out from log symptoms, which was a good call.
Aggregator.SearchAuthors fans out across every configured provider and only returns an error when none of them succeeded. So when OpenLibrary is timing out and DNB answers, the search reports success and the top match can be a DNB record carrying a DNB foreign id. The CSV importer then sets the provider on the new author unconditionally:
full.MetadataProvider = "openlibrary"
regardless of which provider actually produced the match. The row gets stored with a DNB key while claiming to be an OpenLibrary author. Later, once OpenLibrary is answering again, the same person resolves to a real OL key, the existing row lookup by that key misses the DNB keyed one, and you get a second author. Precisely what you described.
The mislabelling is the part that bothers me most. Anything that reads MetadataProvider to work out how to interpret ForeignID is now being lied to, so the duplicate is the visible symptom rather than the whole problem.
Yes please on splitting that out, since you offered. It's a distinct root cause from the rate limiting and it'll get fixed on its own timeline. Dropping needs-triage here.
On the outage theory: plausible as a trigger and it may well be why this surfaced now, but it doesn't change the diagnosis. Nineteen authors fanning out per work and per edition with no cap and no backoff was always going to storm them eventually. A tighter limit upstream just moved the point where it broke.
Add Steps to reproduce (minimal numbered sequence), the Bindery version from Settings → About, and a Trace logs section captured with the runtime log level set to DEBUG in Settings → Logs (or BINDERY_LOG_LEVEL=debug). Issues without logs may be closed without investigation.
— 🤖 bindhelper (automated). Reply to correct me; a human reviews every change.
Add Steps to reproduce (minimal numbered sequence), the Bindery version from Settings → About, and a Trace logs section captured with the runtime log level set to DEBUG in Settings → Logs (or BINDERY_LOG_LEVEL=debug). Issues without logs may be closed without investigation.
— 🤖 bindhelper (automated). Reply to correct me; a human reviews every change.
You are right and the bot should not have asked. Your trace logs were on #2075 the whole time, you said so, and then this went quiet. Sorry about that.
I traced it. The mechanism is two separate defects stacking:
Aggregator.SearchAuthorsWithOutcome (internal/metadata/aggregator.go:74) fans out across providers and only returns an error when every one of them fails (:90). So with OpenLibrary timing out and DNB answering, the search reports success and hands back a DNB record. #2271 added a guard for exactly this, SearchOutcome.SafeToBind, which refuses to write a permanent provider link when the primary dropped out of the search. It is wired into the Audiobookshelf import and the author relink paths. It was never wired into the CSV, Readarr or Goodreads importers.
On top of that, those three importers hardcode the provider label: internal/migrate/csv.go:175, internal/migrate/readarr.go:133 and internal/migrate/goodreads.go:349 all set MetadataProvider = "openlibrary" regardless of which provider actually answered. So the row ends up with a dnb: foreign ID while claiming to be an OpenLibrary author. Since catalogue fetches route off the foreign ID prefix rather than the label, the two disagree and the label is the one that is wrong.
That is your duplicates. Once OpenLibrary is answering again, a lookup by the real OpenLibrary key misses the row bound under the DNB key and creates a second author for the same person.
Filed as #2332 with both halves. The reconciliation question, meaning what to do about rows already written this way, I have left out of that issue deliberately, because it needs a different answer from the prevention. Until then, Merge on the author page is the way to collapse a pair: open the one you want to keep, Merge, pick the duplicate as the source. It moves the books over and records the source's name as an alias, so it stays merged across future imports.
Fixed in v1.41.0. An author import no longer binds to a fallback provider while OpenLibrary is down (#2610), and re-running an import now skips an author you already have under another provider instead of adding a second one (#3106). Duplicates made before this stay until merged: open the one to keep, Merge, and pick the other as the source. Thanks for the clear report and the logs.
Activity
TheOpsMgr commentedon Aug 20, 2026
Related Issue and analysis
vavallee commentedon Aug 20, 2026
Add Steps to reproduce (minimal numbered sequence), the Bindery version from Settings → About, and a Trace logs section captured with the runtime log level set to DEBUG in Settings → Logs (or
BINDERY_LOG_LEVEL=debug). Issues without logs may be closed without investigation.— 🤖 bindhelper (automated). Reply to correct me; a human reviews every change.
TheOpsMgr commentedon Aug 20, 2026
trace logs are available on the linked issue.
vavallee commentedon Sep 5, 2026
You are right and the bot should not have asked. Your trace logs were on #2075 the whole time, you said so, and then this went quiet. Sorry about that.
I traced it. The mechanism is two separate defects stacking:
Aggregator.SearchAuthorsWithOutcome(internal/metadata/aggregator.go:74) fans out across providers and only returns an error when every one of them fails (:90). So with OpenLibrary timing out and DNB answering, the search reports success and hands back a DNB record. #2271 added a guard for exactly this,SearchOutcome.SafeToBind, which refuses to write a permanent provider link when the primary dropped out of the search. It is wired into the Audiobookshelf import and the author relink paths. It was never wired into the CSV, Readarr or Goodreads importers.On top of that, those three importers hardcode the provider label:
internal/migrate/csv.go:175,internal/migrate/readarr.go:133andinternal/migrate/goodreads.go:349all setMetadataProvider = "openlibrary"regardless of which provider actually answered. So the row ends up with adnb:foreign ID while claiming to be an OpenLibrary author. Since catalogue fetches route off the foreign ID prefix rather than the label, the two disagree and the label is the one that is wrong.That is your duplicates. Once OpenLibrary is answering again, a lookup by the real OpenLibrary key misses the row bound under the DNB key and creates a second author for the same person.
Filed as #2332 with both halves. The reconciliation question, meaning what to do about rows already written this way, I have left out of that issue deliberately, because it needs a different answer from the prevention. Until then, Merge on the author page is the way to collapse a pair: open the one you want to keep, Merge, pick the duplicate as the source. It moves the books over and records the source's name as an alias, so it stays merged across future imports.
fix(migrate): guard CSV, Readarr and Goodreads author binds against a…
fix(api): refuse an ISBN resolved Add Book when the primary provider …
fix(abs): refuse a fallback ISBN relink while the primary is down (#2642
fix(migrate): skip authors already in the library under another provi…
fix(metadata): author middle initials, cross provider author duplicat…
vavallee commentedon Oct 8, 2026
Fixed in v1.41.0. An author import no longer binds to a fallback provider while OpenLibrary is down (#2610), and re-running an import now skips an author you already have under another provider instead of adding a second one (#3106). Duplicates made before this stay until merged: open the one to keep, Merge, and pick the other as the source. Thanks for the clear report and the logs.