Use when checking a .bib or reference list for incorrect or fabricated entries — wrong titles, invented co-authors, wrong DOI/volume/pages, preprints cited instead of the published venue, or references that do not exist. Triggers on "verify this bibliography", "check my references", "are these citations real", a refs.bib alongside a paper, or suspicion that a bibliography was LLM-generated.
Installs into .claude/skills of the current project.
Are you the author of Verifying Bibliography?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/chgagne-verifying-bibliography)
---
name: verifying-bibliography
description: Use when checking a .bib or reference list for incorrect or fabricated entries — wrong titles, invented co-authors, wrong DOI/volume/pages, preprints cited instead of the published venue, or references that do not exist. Triggers on "verify this bibliography", "check my references", "are these citations real", a refs.bib alongside a paper, or suspicion that a bibliography was LLM-generated.
---
# Verifying Bibliography
## Overview
**Resolve a stable identifier first, then compare the record back against the `.bib`.**
Everything else in this skill follows from that one inversion.
The obvious design — take the title in the `.bib`, search for it, fuzzy-match the authors —
cannot detect the single most important defect. A fabricated title that accurately describes
the right paper retrieves that paper and passes. Searching *by* the thing you are trying to
verify is circular.
Two measured examples, both cleared by a title-search tool and both caught by reverse lookup:
- `moritz2019draco` carried the title *"Draco: An Approach to Generating Visualizations with
a Computational Design Process"*. Its DOI is correct and resolves to *"Formalizing
Visualization Design Knowledge as Constraints: Actionable and Extensible Models in Draco"*.
- `wongsuphasawat2016voyager` carried DOI `10.1109/TVCG.2015.2467251`, which resolves to
*"High-Quality Ultra-Compact Grid Layout of Grouped Networks"* — a different paper by six
different authors.
## Run it
```sh
python3 ~/.claude/skills/verifying-bibliography/assets/run-bibcheck.py \
refs.bib --bbl main.bbl --out review-assets/
```
Run it by absolute path from the paper directory. Stdlib only — no install, no venv,
no dependencies. (`python3 -m bibcheck` also works, but only from `<skill>/assets/`.)
- `--bbl main.bbl` restricts checking to entries actually cited (the `.bbl` is ground truth
for "cited", because it reflects the built document rather than every `\cite` including
commented-out ones)
- `--out DIR` writes `bibcheck-report.md` and `bibdiff.csv`
- Exit code `2` means source coverage was degraded — see *When a source is down*
Optional: `S2_API_KEY` in the environment, or `~/.config/scholarly/s2_key`. Semantic Scholar
sits last in the ladder; on two real bibliographies it changed nothing. Absent, it is
silently skipped and nothing breaks.
## The ladder
```
1. DOI -> Crossref /works/{doi} authoritative, reverse check
2. arXiv DOI -> arXiv API 10.48550/* is DataCite, absent from Crossref
3. arXiv id -> arXiv API
4. ACL DOI -> aclanthology.org via Crossref
5. no identifier -> DBLP by title the CS/ML spine
6. still nothing -> OpenAlex, then Semantic Scholar
```
Once a **non-preprint** identifier resolves, the weak rungs are skipped — they cost four
requests to four free APIs for no new information. A **preprint** record is the exception:
an arXiv hit says nothing about whether the work was later published, so the search
continues (see *Prefer the venue of record*).
DBLP is queried before OpenAlex because it is near-complete for NeurIPS/ICLR/ICML/ACL
proceedings, which frequently carry no Crossref DOI at all.
## Report author findings as set differences, never as a score
A scalar author-match score is worse than useless — it looks like evidence while hiding the
finding. The Voyager entry above scored **0.83** while carrying two invented co-authors and
one dropped real one. This tool prints:
```
+Bach, Bill, +Dragicevic, Pierre -Bill Howe
```
Unmissable. Apply the same discipline in prose: name the authors, do not summarise the
disagreement.
## Severity
| Level | Meaning |
|---|---|
| `CRITICAL` | Wrong title under a resolved identifier, an author in the `.bib` absent from the record, or a DOI that does not resolve. Treat as fabrication until shown otherwise. |
| `MAJOR` | Metadata disagrees with a record reached by a stable identifier — venue, year, volume, issue, pages. |
| `MINOR` | Name form, punctuation, a field the venue does not use, or a preprint cited where a published version exists. |
| `WEAK` | Matched by title search only. **Not verified.** A human must look. |
| `UNVERIFIED` | No source returned a record. **This is a finding, not a pass.** |
| `SKIP` | Non-paper entry (blog, web page, anonymous artifact). Check the URL by hand. |
**`WEAK` and `UNVERIFIED` are not clean bills of health.** They are the entries most likely
to be fabricated, because a fabricated reference is exactly the one no database can find.
## What is compared against what
Identity must be established before any field comparison means anything.
- **Volume, issue, pages, year** are compared *only* against a record reached by a stable
identifier. A title-search hit may be a different edition, a reprint, or a review of the
same work. DBLP matched a *SIAM Review* review of Strogatz's textbook against a *Physics
Today* review of it; comparing their page numbers is meaningless.
- **Year** additionally requires a non-preprint record. Indexes routinely pair the published
venue with the preprint year — Semantic Scholar reports *DMLR 2022* for a paper published
in DMLR in 2024 — so a weak year cannot distinguish an error from the normal preprint gap.
- **A title mismatch against a title-search record stops all further comparison.** If the
titles disagree, we never identified the work, and reporting its year or authors would
describe a different paper.
## Prefer the venue of record
When a paper has both an official publication and a preprint, the entry must cite the
**published version**. The preprint may stay in the entry as a supplementary `url`/`eprint`;
it never replaces the venue.
The check fires only when the entry cites the preprint *instead of* a venue —
`journal = {arXiv preprint arXiv:1808.04819}`, or no venue at all. An entry that names its
venue **and** carries `eprint`/`archivePrefix` is the desired state and is never flagged.
The claim "a published version exists" is only made on an identifier-resolved record or a
DBLP record. OpenAlex title matches invent confident-looking venues — it offered *"Int. J.
Neural Syst."* for the InfoNCE preprint and *"Open MIND"* for an unrelated 2026 paper.
## When a source is down
Sources fail, and a silent failure changes the findings. Two consecutive runs once produced
8 findings and then 7, purely because DBLP dropped connections.
- Per-host throttles (arXiv 3.0s, DBLP 2.5s, Semantic Scholar 1.1s, others 1.0s), 3 retries
with exponential backoff, `Retry-After` honoured on 429
- **A circuit breaker**: after 3 consecutive failures a host is dropped for the rest of the
run. Retrying is right for one flaky request and catastrophic for a host that is down —
when arXiv began answering 429, 3 attempts × 15s on *every* entry turned a 12-second run
into a **6.5-hour** one. A 404 does not trip the breaker: it means the host is healthy and
the record is simply absent.
- Hosts that fail after retries are counted, reported on stderr, and set **exit code 2**;
hosts dropped by the breaker are named separately
- Responses are cached in `~/.cache/scholarly/`; definitive 404/410 are cached too, so an
unresolvable DOI is not re-requested every run. Transient failures are never cached.
**If you see the degraded-coverage warning, re-run before trusting the report.** A warm
cache makes consecutive runs byte-identical.
## Limits
State these rather than implying completeness.
- **Bib-style defects are invisible.** A non-normalised `booktitle` ("The Fourteenth
International Conference on Learning Representations") is a real defect no record
comparison can see. It surfaces as `WEAK` at best.
- **Given-name checks compare first initials only.** `Hoggan, Eve` → `Hoggan, Eve E.` is
missed. Full given-name comparison was tried and reintroduced false alarms on the far more
common initials-only form (`Dupré, B.` for `Benoit Dupré`), which is a worse trade.
- **`SKIP` entries are unchecked**, not clean. Anonymous artifacts and blog posts need a
human to open the URL.
- The tool verifies that a reference *exists and is described correctly*. It says nothing
about whether the citation supports the claim attached to it — that is selective citation,
and it is handled in `reviewing-paper-sources`.
## Measured results
Against two bibliographies whose ground truth was established by prior manual review:
| | Entries | TP | FP | FN | Precision | Recall |
|---|---|---|---|---|---|---|
| VIS paper | 18 | 7 | **0** | 2 | 1.00 | 0.78 |
| ML paper | 57 | 3 | **0** | 2 | 1.00 | 0.60 |
| HaRC 0.2.0, same files | 75 | 4 | 9 | 9 | 0.31 | 0.31 |
Acceptance benchmark (`tests/test_acceptance.py`, 6 must-fire and 10 must-stay-silent keys):
**precision 1.00, recall 0.83**.
It also found a defect the manual pass had missed: `caetano2023trees`, an *uncited* entry
whose DOI resolves correctly but whose pages read 410–418 against the record's 411–419.
Uncited entries are unaudited — check the whole file.
HaRC missed both fabricated titles described above, and false-alarmed on correct ACL entries,
LaTeX-escaped names (`Dupr\'e`), DBLP disambiguation suffixes (`Elena Rossi 0001`),
preprint-vs-published years, and textbook editions. Every normalisation rule in
`normalize.py` exists because one of those false alarms was observed on real data. See
`reference/fabrication-signatures.md`.
Timing: a cold 57-entry run takes minutes; warm runs take **10–20 seconds**, and consecutive
warm runs are byte-identical.
## See also
- `reference/fabrication-signatures.md` — the LLM-generated bibliography signature, the
BibTeX comment traps, and the `.bbl` cross-check
- `reviewing-paper-sources` — consumes this report, writes the audit table and
`refs-corrected.bib`, and checks selective citation