Skip to content

Fix Kobo sync in fully-local mode (no Kobo account) on modern firmware#1367

Open
thijsdezoete wants to merge 2 commits into
crocodilestick:mainfrom
thijsdezoete:fix/kobo-fully-local-sync
Open

Fix Kobo sync in fully-local mode (no Kobo account) on modern firmware#1367
thijsdezoete wants to merge 2 commits into
crocodilestick:mainfrom
thijsdezoete:fix/kobo-fully-local-sync

Conversation

@thijsdezoete
Copy link
Copy Markdown

Summary

On a fully-local setup (Kobo Store proxying off, no real Kobo account), Kobo
sync silently fails on current firmware (reproduced on 4.45.23684). The web
UI works and curl-ing the sync endpoints directly returns 200, but pressing
Sync on the device reports "Sync failed" and no books/collections appear.

Two independent causes, both only triggered once the device's 1-hour access
token expires (so it can look like it "just stopped working" without any config
change):

1. OAuth token refresh can't discover the token endpoint

When proxying is off, CWA points oauth_host at itself but HandleOauthRequest
returns a dummy token for every /oauth/* path — including
/oauth/.well-known/openid-configuration. Modern firmware does OIDC discovery
there before refreshing an expired token; with no token_endpoint it logs:

oauth.debug  access token expired -- refreshing now
OAuthDiscoveryCommand::execute()
requestNewToken(...) requesting a new token from ""      <- empty
QDebugSyncErrorFilter::applyFilter "WebRequestErr"
QueuedSyncCommand::cancel() ... SyncLibraryCommand        <- whole queue cancelled

So /v1/library/sync is never even called. (This is why direct curl works —
it carries the path token and skips the OAuth gate the device runs first.)

Fix: serve a minimal OIDC discovery document whose token_endpoint points at
the /oauth/token route CWA already implements.

2. Cloud reading-services 401 aborts the whole sync

With local OAuth, the device authenticates its direct calls to Kobo's cloud
reading-services with CWA's dummy token and gets 401s:

"https://<host>/api/UserStorage/Metadata" => "Host requires authentication" (401)
QueuedSyncCommand::cancel() ... SyncNotebookCommand
applyFilter "WebRequestErr"  -> finished sync client    (Sync failed)

Kobo sync is transactional, so this aborts the batch and even shelves never
commit. Note the device treats reading_services_host as scheme+host only
it strips the path and calls /api/... at the site root.

Fix: point reading_services_host at CWA and add a before_app_request hook
returning benign empty responses for the reading-services paths
(/api/v3/content/checkforchanges, /api/v3/content/<uuid>/annotations,
/api/UserStorage/*, /api/internal/notebooks). Gated to non-Hardcover setups.

Result

Device log after the fixes: SyncLibraryCommand finishedfinished sync client
with no WebRequestErr; the device reports "Sync complete" and books +
collections sync.

Notes / out of scope

  • Reproduced on CWA v4.0.6; main has the same code paths.
  • Verified end-to-end against a real Kobo (Clara HD, fw 4.45.23684).
  • Separately, failed syncs still record books in kobo_synced_books (via
    add_synced_books during response generation) even though the device discards
    the aborted batch, so they're permanently excluded from NewEntitlement. Once
    sync succeeds this no longer accumulates; existing stale rows need a one-time
    kobo_synced_books clear (force full re-sync). Could be hardened separately by
    only recording synced books on a committed sync.

thijsdezoete and others added 2 commits May 24, 2026 08:29
When Kobo Store proxying is disabled, CWA points oauth_host at itself but only
returns a dummy token for every /oauth/* path. Modern Kobo firmware performs
OpenID Connect discovery (GET oauth_host/.well-known/openid-configuration) before
refreshing an expired access token; with no token_endpoint it requests "a new
token from ''", which fails and cancels the entire sync queue before
/v1/library/sync runs -- the device just reports "Sync failed".

Serve a minimal OIDC discovery document advertising the token endpoint CWA
already implements (/oauth/token), so the device can refresh and sync.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
With local OAuth, the device authenticates to Kobo's cloud reading-services
(annotations, notebooks, UserStorage, checkforchanges) using CWA's dummy token
and gets 401s. The notebook 401 becomes a web request error that aborts the sync
batch, so even shelves/collections never commit and the device reports
"Sync failed".

Point reading_services_host at CWA (the device uses only its scheme+host) and add
a before_app_request hook returning benign empty responses for those root /api
paths, so the sync can complete. Gated to non-Hardcover, non-proxy setups.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant