Conversation
Five follow-ups from the review, plus the coverage gap that mattered. `test_serve_stale_html_optin.py` only covered `_stale_fallback`, which needs the source to yield *nothing*. For a PMC-only article PubMed still returns the abstract, so the refresh reaches `_preserve_cached_full_text` instead: the overwrite is refused, and a separate question decides what may be served. That second question is the one the flag exists to answer and the one dismech hits, and it had no test. Verified before writing them, in case the flag did not reach that path -- it does: with the flag on the cached body is served, with it off the fresh abstract is, and the file on disk is unchanged either way. So this was missing coverage rather than a defect, and there are now three tests pinning it. The warning in `_load_from_disk` still told users to retry, which for a PMC-only article is advice they cannot act on -- PMC answers a Python HTTP client with a bot-check interstitial on an HTTP 200. It now says a re-fetch repairs the entry where the source still serves full text, says that does not include PMC-only articles, and names `serve_stale_html_full_text` so the next person finds the option from the log line instead of from a merged PR. Also: - The comment above the second call site said stale HTML is "withheld here exactly as _stale_fallback withholds it". True only by default now, and the comment did not say which path is the common one. It does. - `find_pmc_article_body` loses its underscore and is imported at module level. It is shared by two modules, and a function-local import of a private name read like a circular-import workaround, which it was not. - `PMC_ARTICLE_BODY_CLASSES` records that its order is a priority order. For a legacy page with several `tsec` sections only the first is returned -- the previous code did the same, so it is a known limit, but reordering the tuple would silently change which section that is. - A negative test for a challenge page carrying a classed `<body>`, since that markup is what the selector has to keep out of the cache. Whether to keep the PMC HTML fallback at all is filed separately as #95: it cannot succeed from a Python HTTP client, yet sits in the default provider chain costing a rate-limit delay and a request per reference. just test: 1168 passed, 1 skipped; mypy clean; ruff clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016WUDZDTqMP5ih9rD7QqGJ4
PMC and Cloudflare answer a Python HTTP client with a challenge page carried on
an HTTP 200, so no status check sees it. `URLSource` took its `<title>` as the
reference title and its markup as the content. Downstream that surfaced as
[ERROR] Title mismatch: expected 'Pharmacotherapy for Alcohol Use Disorder...'
but got 'Checking your browser'
on a pull request citing PMC articles by URL (monarch-initiative/dismech#12867).
Those URLs were not cached, so validation fetched them live and compared a
curated title against an interstitial.
`_is_bot_challenge` now treats such a response as a transient failure rather
than an absence: nothing about the reference is known, so a retry from an
unblocked network can still succeed, whereas caching it records an interstitial
as a paper. The log line says so and suggests citing by identifier instead.
The false-positive direction is the one that matters, because a paper *about*
CAPTCHAs contains these words and discarding a real reference is worse than
caching a challenge page. So the check fires on the `<title>`, or on a body
under 60 KB -- PMC's challenge is ~21 KB against 150-240 KB for an article, so
the margin is wide. Negative tests cover a real PMC article, an article whose
subject is bot detection, and plain text.
Verified against the live response rather than a fixture: PMC returns 200 with
21,207 characters titled "Checking your browser - reCAPTCHA", and the guard
recognises it.
This does not make PMC reachable. It stops a page that is not the reference
being stored as though it were.
just test: 1174 passed, 1 skipped; mypy clean; ruff clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @cmungall's task in 56s —— View job Review of PR #96
Overall this is a well-scoped PR. The bot-check guard handles a real failure mode, and returning I have one substantive concern and a few smaller ones. 1. The check can still fire on real references (
|
|
Closing in favour of a much smaller fix. The CAPTCHA-detection guard in here was solving the wrong problem. The reason a bot-check page ever reached the title comparison is that the validator re-fetches a cache entry whose extractor stamp is missing — so the real fix is to stop re-fetching a cache the consumer already trusts, not to teach the fetcher to recognise interstitials. One config flag instead of a phrase list and a size heuristic. Opening that instead. The six review follow-ups from #94 will come with it; they were small and worth keeping. |
Two commits: the review follow-ups from #94, and a guard that stops a bot-check page being cached as a reference.
Do not cache a bot-check page as a reference
PMC and Cloudflare answer a Python HTTP client with a challenge page on an HTTP 200, so no status check sees it.
URLSourcetook its<title>as the reference title. Downstream that surfaced as:on a PR citing PMC articles by URL (monarch-initiative/dismech#12867). Those URLs were not cached, so validation fetched them live and compared a curated title against an interstitial.
_is_bot_challengenow treats such a response as a transient failure rather than an absence — nothing about the reference is known, so a retry from an unblocked network can still succeed, whereas caching it records an interstitial as a paper.The false-positive direction is the one that matters. A paper about CAPTCHAs contains these words, and discarding a real reference is worse than caching a challenge page. So the check fires on the
<title>, or on a body under 60 KB — PMC's challenge is ~21 KB against 150–240 KB for an article, so the margin is wide. Negative tests cover a real PMC article, an article whose subject is bot detection, and plain text.Verified against the live response, not a fixture:
This does not make PMC reachable. It stops a page that is not the reference being stored as though it were.
Review follow-ups from #94
test_serve_stale_html_optin.pyonly covered_stale_fallback, which needs the source to yield nothing. For a PMC-only article PubMed still returns the abstract, so the refresh reaches_preserve_cached_full_textinstead — the overwrite is refused, and a separate question decides what may be served. Verified before writing the tests that the flag does reach that path; three tests now pin it.serve_stale_html_full_text.find_pmc_article_bodyloses its underscore and is imported at module level, since two modules share it.PMC_ARTICLE_BODY_CLASSESrecords that its order is a priority order.<body>.Verification
just test: 1174 passed, 1 skipped; mypy clean; ruff clean.Whether to keep the unreachable PMC HTML route in the default provider chain is still open in #95.
🤖 Generated with Claude Code