Troubleshooting¶
Start with the local facts before retrying a job:
papio daemon status checks the background service without starting it. Most
other commands start the service automatically when they need it. Use
papio jobs get <job-id> to see one job's events and actions, and use
papio batch report <batch-id-or-latest> --markdown to see a research set's
joined outcome.
The Chrome extension is outdated¶
Store-installed extensions update automatically — this section is for unpacked development builds, which can keep running outdated code even after you change the files on disk. The fix is an explicit extension reload; a Chrome restart, killing the process, or reloading the extension's folder alone is not enough.
Preferred UI reload¶
- Open
chrome://extensions. - Turn on Developer mode if it is not already enabled.
- Find papio and use its reload arrow.
- Open the papio details page and confirm its optional host permissions still cover only the publisher sites you intend to use.
- Open the popup or run
papio actions listto give the worker a reason to reconnect.
Automated Chrome reload¶
Where local Chrome automation is already authorized, use Chrome's
developerPrivate.reload operation for the papio extension. This is the
programmatic equivalent of the reload arrow; it is not a papio CLI command and
must target the installed papio extension, not an arbitrary extension.
Last resort: purge the service-worker cache¶
Use this only with Chrome completely closed:
- Close every Chrome process.
- In the affected Chrome profile, remove
Service Worker/ScriptCacheandService Worker/Database(for the default profile these are beneathDefault/Service Worker/). - Reopen Chrome and use the
chrome://extensionsreload procedure above.
This purge also clears service-worker state for web PWAs in that profile; those PWAs re-register on their next use. Do not delete the papio data directory or its database as a substitute for reloading the extension.
Restarting the background service¶
Stopping the background service is explicit:
The second command confirms it is stopped without starting it again; the last command is a normal call and can start a fresh service. On a healthy recovery, the extension recognizes the fresh service, closes its stale connection, reconnects, and re-offers your saved browser jobs. It re-offers them within about two seconds and does not duplicate browser tabs.
If a handoff is still stale after the service is back, reload the extension using
the procedure above, then inspect papio actions list and papio status.
Do not cancel a job merely because its old popup row is stale.
Version mismatches and updates¶
Run papio doctor first when an update or browser integration seems wrong. It
checks papio first, then the pieces it depends on:
PASS access_mode explicit access mode configured
PASS pdftotext Poppler semantic extraction available
...
PASS config parsed /Users/me/.config/papio/config.toml
PASS daemon reachable; version 0.18.0
WARN extension extension has not connected since daemon start
PASS native host (Chrome) manifest allows configured extension
PASS native host (Firefox) manifest allows configured extension
PASS native host (version) matches the daemon (0.18.0)
PASS zotio version 0.16.1; required capabilities available
To update papio, build or install the new version, then stop the running service:
The next command that needs it starts the new service; there is no
papio daemon restart command. If the command-line tool and the service are
different versions, the tool prints a warning. An unknown-field configuration
error means your config was written by a newer papio; install a matching or
newer version before continuing.
The extension popup reports daemon unreachable, papio daemon out of
date, and extension out of date when it needs attention; the toolbar shows
! in those states. When healthy, the popup shows the daemon version, and its
options page shows extension and daemon versions together. Extension updates
arrive through the browser store, while the daemon is updated manually, so an
extension newer than its daemon is the common direction.
Store-installed extensions update automatically. Manually loaded builds from
about:debugging or an unpacked dist/ do not; download the new ZIP from the
release bundle instead. The daemon's extension floor and the popup states flag
an extension that is too old.
Learning about new releases¶
papio never installs updates on its own, and it never contacts a server without being told to. Two mechanisms tell you a newer release exists:
- Through the extension (no network use by papio). Store-delivered
extension updates carry the daemon version they were released with. When the
popup notices the connected daemon is older, its version line changes to
papio <new> is availablewith the upgrade command. papio itself sends nothing anywhere; the browser's normal store update is the only network activity involved. - Opt-in release check. With
check = trueunder[updates]in the configuration (thepapio initprompt offers this, defaulting to yes), the daemon asks the papio and zotio GitHub releases APIs for their latest versions at most once a day each. The requests carry no identifying payload beyond the connection itself, and GitHub already hosts the binaries you would download. Results appear inpapio doctor, in daemon status, and as a single standard-error hint (at most once per day). Configurations without the[updates]section never check.
You are asked to sign in again¶
The browser extension keeps one pinned, muted tab while handoff jobs are still open. If a reload lands on your institution's login page, it stops reloading, brings the tab forward, and flags a sign-in request.
- Open the extension popup and use Focus for the needs-you job, or run
papio actions opento open the current handoff queue. - Sign in on the ordinary Chrome page, including any two-factor or institution step required by that site.
- Return to the provider page. The extension detects your return and resumes.
Do not put credentials in papio configuration, in messages to the extension, or in an MCP tool call. papio is designed to reuse the ordinary browser session, not to automate authentication.
The sign-in page says the request is stale or expired¶
Institutional sign-ins are time-boxed. If login plus a two-factor step takes
long enough, the identity provider may reject the original handoff link with a
"stale request" or "expired" page even though your session is now valid. Sign
in first, then re-run papio actions open. A compatible extension re-drives
the tracked tab through its retained resolver URL when it is on an authentication
page or has been marked sign-in pending; it only focuses a provider page that is
already progressing a download.
The extension recognizes the common OpenAthens/Shibboleth failure pages. When
it does, it brings the work window to the front so the dead page is visible,
records a browser.handoff_failed event on the job (visible in
papio jobs get <id>), and retries the handoff tab on its own. Retries are
capped by the handoff's authentication budget; past it the tab is left on the
failure page for you and the job stays parked.
Two browsers fight over papio¶
With the extension enabled in more than one browser or profile, only one
browser holds the offer/handoff flow. A browser that is not the holder says so
in its popup — another browser is holding your papio session — and its
toolbar icon shows a grey !. That is not a daemon problem: papio daemon
status answers ok, and the remedy is switching the session, not restarting
anything. Other symptoms: handoff tabs open in the "wrong" browser, or
doctor reports "N other browser(s) waiting".
papio browser sessions # holder + pending, versions, last contact
papio browser use --latest # hand the session to the newest pending browser
papio browser use <session-id> # or pick one explicitly
Quitting the holding browser releases the session immediately; a crashed holder yields within about ten seconds. If you never want a browser to hold the session, disable the papio extension there. Until a waiting browser is given the session, its popup and inbox only report which browser has it: the daemon negotiates page acquisition and the inbox with the browser it acknowledged, and it acknowledges one at a time. Both surfaces resume by themselves in the browser you claim.
Read doctor output¶
doctor prints stable PASS, WARN, and FAIL rows. Any FAIL makes the
report not OK. The checks below explain every check the command can emit.
| Check | PASS means | WARN or FAIL: what to do |
|---|---|---|
access_mode |
An explicit allowed access mode is configured. | Set access_mode to conservative, assisted, or delegated; papio init creates a conservative profile. |
fetch_policy |
HTTPS-only fetch policy is active. | A warning means fetch.allow_http_loopback is on; disable it outside loopback fixture work. |
data_dir |
The data directory is private and writable. | Correct ownership or permissions so papio can create and write the configured directory. |
config_permissions |
The config is user-only. | A missing config is a warning; create it with papio init. A group/world-readable config is a failure; set it to mode 0600. |
database |
The local database passed its integrity and version checks. | If unavailable in this run, run doctor through the background service. For an integrity failure, restore a verified backup before acquiring more work. |
pdf_worker |
The current papio can run its isolated PDF worker. | Reinstall or rebuild papio and retry doctor. |
pdftotext |
Poppler semantic extraction is available. | Install Poppler; this is a failure. |
pdfinfo |
Poppler's independent page-count check is available. | Install Poppler for the structural cross-check; this is a warning. |
ocr |
OCR dependencies are available when OCR is enabled. | Install Poppler and Tesseract, or explicitly disable OCR. A disabled OCR fallback is a warning because image-only papers need review. |
source_unpaywall |
An enabled Unpaywall source has a contact email. | Set email or disable the source. |
source_openalex |
An enabled OpenAlex source has email and API key. | Set email and sources.openalex.api_key, or disable the source. |
source_core |
An enabled CORE source has an API credential. | Configure sources.core.api_key, or disable the source. |
source_crossref-tdm |
An enabled Crossref TDM source has an API credential. | Configure sources.crossref_tdm.api_key, or disable the source. |
credits_<source> |
Today's metered spend is named, with the ceiling and the credentials that ceiling is shared by: openalex has committed 12 of 5000 credits today, from 10000 reported by the provider (shared by anonymous, key-05e71a0b2f38b802). The fuse keeps one row per source per day, so the credential list is what the allowance is shared by, not a per-credential breakdown of the spend. Reported for every source whose daily_credit_fraction is non-zero, healthy or not. |
A warning means today's allowance is spent and that source is parked until 00:00 UTC — papio is waiting, not broken. Lower the reserve by raising daily_credit_fraction, or add capacity on the provider. A warning naming a changed cost schedule means the provider's prices no longer match what papio committed against, and egress stays closed until the new schedule is acknowledged. Before the day's first response arrives the ceiling reads as a conservative cap rather than a fraction of the provider's figure. |
See config-reference.md for exact keys and allowed
values.
zotio-boundary error classes¶
papio stores and prints the following stable, privacy-safe error classes for
zotio-boundary failures. Their hints are sanitized and truncated; use the class
and hint from papio batch report, papio jobs get, or JSON output rather than
copying credentials or filesystem paths into a ticket.
| Error class | Meaning | What to do |
|---|---|---|
zotero_http_4xx |
zotio reported a Zotero HTTP 4xx response. | Check the local zotio/Zotero authorization and the operation shown by the sanitized hint, correct it there, then make and inspect a new zotio plan. |
zotero_field_validation |
zotio rejected an item field, such as an unknown item field. | Update the incompatible field mapping or compatible zotio version, then create a new plan rather than reusing the failed one. |
mirror_sync_failed |
Synchronizing the zotio mirror failed. | Restore local zotio connectivity and synchronization, then retry planning. |
zotio_exec_timeout |
A zotio command exceeded its deadline. | Confirm the executable works; if the operation legitimately needs more time, set [zotio].timeout_seconds within 5–600 and retry. |
zotio_not_configured |
papio has no usable zotio integration. | Run papio init with the correct --zotio-path, or set [zotio].executable to a usable command. |
plan_confirmation_mismatch |
The supplied confirmation SHA-256 did not match the immutable plan. | Run papio zotio plan again, inspect its preview, and pass that plan's exact SHA-256 to papio zotio apply. |
reservation_conflict |
The apply reservation conflicted or was not finalized. | Let any concurrent apply finish, then make a new plan and apply it; do not force a stale reservation. |
local_db_locked |
A local database was locked. | Let the competing local process finish or close it, then retry the zotio operation. |
network |
Network connection setup failed. | Restore network or DNS/TLS connectivity and repeat the affected operation. |
unknown |
No stable classifier matched the failure. | Inspect the job events and sanitized hint, run papio doctor, and retain the class in any bug report. |
A manual papio zotio plan and papio zotio apply recovery is preview-first:
Do not use a SHA-256 from a different preview, and do not treat an import error as evidence that the validated PDF itself failed.
Browser handoff or review is still parked¶
Use the report reason to choose the next step:
institutional: the ordinary Chrome OpenURL handoff needs the user's institution session.oa_browser: an open-access URL needs the browser after papio's own download did not complete it.terms: the user must read and decide on publisher terms; papio does not accept them.needs_review: inspect the quarantine path in the openverify_identityaction, then explicitly accept or reject it withpapio actions resolve.no_identifier: no DOI, PMID, or arXiv id could be confirmed, so the job settledunavailablerather than opening an institutional handoff. Books, chapters, reports, and theses usually land here — no sign-in makes an identifier-less request fetchable. Find a DOI and re-submit a manual request withpapio acquire --doi <doi>; for a Zotero item, applyzotio --yes items enrich --missing-doiand then re-runpapio acquire --from-zotio.doi_not_registered: the request carries a DOI, but the DOI system has no such handle — it resolves to a "DOI NOT FOUND" page and no link resolver can match it. This is almost always a typo or a mangled copy-paste (one transposed digit is enough). Signing in will not help. Check the DOI on the article's own page and re-submit withpapio acquire --doi <doi>.
The exact report reason is preferable to a blind papio jobs retry; browser and
identity states are intentionally parked for a human decision.
If the report reason does not explain it, ask the job itself:
That names the typed terminal reason, which access tiers the job actually reached,
and any components it holds. The attempted tiers are the useful part when a paper
should have been available: none means no source was ever reached, and a list
containing only open_access when you expected an institutional pass means the
institutional route was never tried rather than tried and refused.
The browser link drops while a page is being captured¶
A handoff or papio adapter capture that ends in browser session disconnected
during page capture means papio's link to the browser dropped while the
extension was sending a captured page back. Jobs bounce straight back to waiting
and the extension reconnects a few seconds later, so it can repeat in a loop.
Large provider pages could trigger this in 0.17.0 and earlier. Upgrade, then confirm the browser connector is the same version as the background service:
The native host (version) check must pass. If it does not, your browser is
still starting an older connector even though the service is current: run
papio native-host install from the papio you want it to use, then quit the
running connector process so the browser starts the new one.
A parked job with no open action¶
A job can sit in awaiting_human after the action it was waiting for is gone —
resolved elsewhere, or cancelled. papio actions list shows nothing to do, so
there is nothing to resolve, and a retry is the wrong instrument because the job
is parked rather than failed. The background sweep clears these on its own
schedule; to clear one immediately:
It returns the job to resolving and reports what it found, so an outcome of
not_parked or has_open_actions means the job is not actually orphaned and the
parked state is genuine — resolve the open action instead. It is deliberately
narrow: it will not reopen a job that is legitimately waiting on you.
A handoff has been inactive for more than seven days¶
After a week, an open human action is no longer offered automatically when a session goes live, and the reminder schedule stops raising it. This prevents one uncompleted handoff from creating repeated tabs and notifications.
This does not expire the action. It remains open, listed by papio actions
list, and available through papio actions open. That command opens the
handoff explicitly.
WARN quiesced_actions 3 human action(s) have gone quiet after waiting more than 7 days; oldest opened 40 days ago
fix: papio no longer offers these on its own — run 'papio actions open' to
drive them, or 'papio actions dismiss' to clear the ones you are done with
If a handoff becomes inactive again, the library may not hold the title, the
provider may have changed its login, or the DOI may be wrong. Check
papio jobs receipt <job-id> before reopening it.