# Checking your setup

doctor invokes every source live with the cache bypassed, and reports readiness in three output modes that share one exit code.

`doctor` does not list what it thinks is configured. It invokes every source live
with the cache bypassed, and reports what actually answered.

```bash
research-scan doctor             # a four-line summary and a verdict
research-scan doctor --verbose   # every check, with timings and paths
research-scan doctor --json      # machine-readable; this is the CI/agent interface
```

The checks and the exit code are identical in all three modes. Only the
presentation differs.

## The summary

```
Research Scan 0.5.2
✓ configuration
✓ OpenAlex   ✓ Semantic Scholar   ✓ Crossref   ✓ arXiv   ✓ PubMed
✓ writable run store
Ready.
```

Progress lines go to stderr; only this block is stdout. `--quiet` silences the
stderr log.

**Exit 0 means go. Exit 3 means a mandatory check failed** — read the check name
and fix that, rather than working around it.

## The full check list

`--verbose` gives every check with its timing, plus the paths in use.

```
research-scan doctor — v0.5.2, python 3.13.13

ok     python                 3.13.13
ok   ! config path            ~/.config/research-scan
ok   ! cache path             ~/.cache/research-scan
ok     OPENALEX_API_KEY       ****  from user-config
ok     OPENALEX_MAILTO        ****  from user-config
ok     S2_API_KEY             ****  from user-config
ok     NCBI_API_KEY           ****  from user-config
ok   ! openalex search        4238497 hits, is_retracted readable, cost $0.001  [1174 ms]
ok     openalex psyarxiv-doi  10.31234/osf.io/mky9j indexed as preprint  [683 ms]
ok   ! s2 search              1 hit(s)  [2088 ms]
ok   ! s2 references          1 reference(s) — graph expansion available  [675 ms]
ok     crossref lookup        10.1038/s41586-021-03819-2 resolves  [368 ms]
ok     arxiv query            parsed a candidate for 'electron' (arXiv:2608.20339)  [81 ms]
ok     pubmed esearch         esearch returned pmid 42625563  [529 ms]

paths:
  config_env: ~/.config/research-scan/.env
  local_env: ~/.env
  cache_db: ~/.cache/research-scan/http.sqlite

ready
(! marks a check that can exit 3)
```

Credential rows show the last four characters of the real value and where it came
from; the values above are masked. Hit counts, arXiv ids, PubMed ids and timings
are whatever the live APIs returned at that moment and will differ on every run.

`--sources openalex,s2` narrows what is checked.

## Reading it from a script or an agent

`doctor --json` is the interface, and its keys are stable.

```bash
research-scan doctor --json
```

```json
{
  "version": "0.5.2",
  "ready": true,
  "providers": {
    "openalex": "ok",
    "s2": "ok",
    "crossref": "ok",
    "arxiv": "ok",
    "pubmed": "ok"
  },
  "config": "ok",
  "run_store": "ok",
  "paths": {
    "config_env": "~/.config/research-scan/.env",
    "local_env": "~/.env",
    "cache_db": "~/.cache/research-scan/http.sqlite"
  },
  "checks": [
    {
      "name": "openalex search",
      "status": "OK",
      "detail": "4238497 hits, is_retracted readable, cost $0.001",
      "mandatory": true,
      "duration_ms": 1205.4
    }
  ]
}
```

Abridged: the real output carries every check in `checks`, plus a `keys` object
with each credential's presence, masked value and origin.

From 0.5.2 this is a stable interface: keys are added, never removed or
repurposed. Read `ready` for the verdict and `version` for the tool version. The
`ok` and `tool_version` keys emitted by 0.5.0 and 0.5.1 were undocumented
duplicates of those two and are gone.

`ready` is the boolean. `providers` maps each checked source to `ok` / `warn` /
`fail` / `skip` — **`skip` means the probe never ran, which is never the same as
passing.** `checks` carries every individual result.

## When a check fails

**A missing `OPENALEX_API_KEY` is a `FAIL`**, and the OpenAlex probes are then
reported as `SKIP` rather than as passing, because they were never run.

**A missing `S2_API_KEY` or `NCBI_API_KEY` is only a `WARN`.** The scan still
runs, throttled.

**`arxiv HTTP 429`** is a `WARN`, not a failure. arXiv rate-limits aggressively —
the client already holds to one request every three seconds — and returns 429
under load. A `cs` scan falls back to OpenAlex and Semantic Scholar, which carry
arXiv preprints anyway: you lose same-day listings and one origin signal, not
coverage. Re-run later if recency matters.

**Semantic Scholar answers 429 or 503 when its queue is deep.** The HTTP client
retries and then returns the last response rather than raising — a bad status is
data, not an exception. Graph expansion falls back to OpenAlex's bare-id reference
list, which works but cannot be ranked, so expansion recall drops. It is recorded
per call in `expansion.log.jsonl`.

## One side effect worth knowing

`doctor` is very nearly read-only, but not entirely: it creates
`~/.config/research-scan` and `~/.cache/research-scan` if they do not exist, and
writes then immediately deletes a probe file in each to prove the run store is
writable. That is the `writable run store` check.
