Skip to content

Troubleshooting

Start with the local facts before retrying a job:

papio doctor
papio daemon status
papio status
papio actions list

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

  1. Open chrome://extensions.
  2. Turn on Developer mode if it is not already enabled.
  3. Find papio and use its reload arrow.
  4. Open the papio details page and confirm its optional host permissions still cover only the publisher sites you intend to use.
  5. Open the popup or run papio actions list to give the worker a reason to reconnect.

Automated Chrome reload

Prefer papio browser reload: it reloads the connected development-mode extension from disk and then waits for the new session to reconnect. It refuses store-installed extensions by design, so use it only with an unpacked development install. Where local Chrome automation is already authorized, Chrome's developerPrivate.reload operation for the papio extension is the programmatic equivalent of the reload arrow 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:

  1. Close every Chrome process.
  2. In the affected Chrome profile, remove Service Worker/ScriptCache and Service Worker/Database (for the default profile these are beneath Default/Service Worker/).
  3. Reopen Chrome and use the chrome://extensions reload 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:

papio daemon stop
papio daemon status
papio status

papio daemon stop returns when the service process has exited. The service can take up to half a minute to finish its work, and the command then prints papio: waiting for the daemon (pid N) to finish stopping. 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:

papio daemon stop

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 first start after an update can upgrade the database. On a large database that takes a minute or more: the command prints papio: upgrading the database; this can take a minute and waits, and other commands and the browser extension wait for the same upgrade instead of starting a second service. Let it finish. If it takes longer than ten minutes, the command stops waiting and says so, and the upgrade carries on in the background; run the command again later. A database check can also take a while on a large database, and the command then prints papio: checking the database; this can take a minute. A command run while the previous service is still stopping prints papio: waiting for the previous daemon to stop and starts the new service once the old one has exited.

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 available with 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 = true under [updates] in the configuration (the papio init prompt 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 in papio 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.

  1. Open the extension popup and use Focus for the needs-you job, or run papio actions open to open the current handoff queue.
  2. Sign in on the ordinary Chrome page, including any two-factor or institution step required by that site.
  3. 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.

The resolver opens the wrong paper or an unusable page

Inspect papio jobs diagnose <job-id> and papio actions list --json. For a manual-download action diagnosed as a wrong work, a missing adapter, or adapter drift, you can ask papio to try the paper's DOI route:

papio actions retry-publisher <action-id> --revision <revision>

Use the action's current revision from the listing. This requires a connected extension and delegated mode. It keeps the failed action and its evidence, then starts one publisher attempt on the same job. A pending sign-in, challenge, terms prompt, download, or unresolved browser effect blocks the retry. The DOI route may still require institutional access; it does not establish entitlement.

Only one publisher retry is allowed per job. If that route also fails, Open keeps the publisher page available for manual work without starting another download attempt.

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. Only daemon-initiated offers and handoffs are holder-routed: a waiting browser is acknowledged with its own connection, and anything you initiate yourself — Acquire this page, Send PDF, reading the inbox — still works where you click. Its popup and inbox stay usable; the holder switch only changes where automatic handoffs land. 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 the common ones; run papio doctor --json for every check the current build 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.
retraction The latest Retraction Watch fetch succeeded, and the last successful sweep is less than 48 hours old. SKIP means the source is disabled or no sweep has completed. WARN means the latest fetch failed, the last success is older than 48 hours, or the local status is invalid. Check access to api.labs.crossref.org, keep the daemon running through its next daily maintenance sweep, or follow the row's cache repair instruction.
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.
config_ignored_settings Only emitted as WARN. The config still sets a key this papio no longer supports, such as browser.native_viewer_helper. papio ignores the value. Delete the key from the config file.
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.
credential:<target> Whether the selected OS-store or environment record resolves, without revealing it. Ready means local resolution, not provider authentication. Run papio config credentials status; repair the binding or unlock the user credential store, then restart the daemon. Missing optional keys use keyless quotas; required-key integrations remain unavailable.
source_openalex An enabled OpenAlex source has email and API key. Set email; use papio config credentials set sources.openalex for account capacity.
source_core An enabled CORE source has an API credential. Run papio config credentials set sources.core, or disable the source.
source_crossref-tdm An enabled Crossref TDM source has an API credential. Run papio config credentials set sources.crossref_tdm, 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.
metadata_unresolved zotio found no registry record for the paper, and papio could not describe it from its own record. The hint says why: for example, the DOI that zotio read from the PDF is not the job's DOI. Compare the hint with the job's identifiers in papio jobs show <job-id>. If an identifier is wrong, submit the paper again with the correct one. Otherwise keep the class and the hint for a bug report.
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_apply_unconfirmed An earlier zotio import ended without a result (a timeout, a shutdown, or a crash), so papio cannot yet tell whether Zotero saved the paper. papio does not repeat the save until it can check. Wait about two minutes for Zotero desktop to sync, then retry; papio looks the paper up first. It records the paper as filed if the library holds it, attaches the PDF to the item that the earlier save created, or runs the import again if the library has nothing. Keep Zotero desktop open and signed in, since a stalled sync keeps this class on every 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:

papio zotio plan <job-id>
papio zotio apply <plan-id> --confirm-sha256 <exact-sha256>

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. Without recorded auto-accept consent, papio does not accept them. With explicit persistent consent and a supported accept rule, papio can accept them on your behalf; see the terms row of the user guide.
  • needs_review: inspect the quarantine path in the open verify_identity action, then explicitly accept or reject it with papio actions resolve.
  • no_identifier: no DOI, PMID, or arXiv id could be confirmed, so the job settled unavailable rather 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 with papio acquire --doi <doi>; for a Zotero item, apply zotio --yes items enrich --missing-doi and then re-run papio 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 with papio 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:

papio jobs receipt <job-id>

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.

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:

papio doctor
papio native-host status

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:

papio jobs repair-awaiting-human <job-id>

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.

The paced drive opens nothing

Ask the drive what blocks it:

papio drive status

The blocked by: list names each reason; the user guide explains every name. The common cases:

  • disabled: [drive] enabled = true is missing, or the background service has not restarted since you added it. Run papio daemon stop; the next command starts it with the new setting.
  • paused: you paused it, or the sign-ins for two papers from different providers at the same library did not come back. Sign in to your library in the browser; the drive continues when any sign-in returns. Or run papio drive resume.
  • holder_absent: no browser is connected. Open the browser that has the extension; papio browser sessions shows which browser receives handoffs.
  • no_eligible_job: nothing parked can be opened now. The drive leaves a paper alone for 6 hours after it opens it, skips providers that refuse this browser, and never opens an openurl_available advisory.

papio closed a tab you wanted

From extension 0.15.0, papio closes the tab of a paper once it filed the paper or the job ended, including a PDF tab. For a filed paper, choose Reopen in the toast to get the tab back, or open the history page. To keep a tab in future, pin it or move it out of papio's work window or tab group before papio is finished with it. Looking at a tab only delays the close. See When papio closes its tabs.