Skip absent test dependencies, and stop unit runs deleting the integration cache - #27
Merged
Conversation
shared_downloader probes Concord.parquet and skips the session on a 404, but a release can publish one DuckDB file without the other — 2026jul22 does exactly that, serving a 4.6 GB Concord.parquet with no Identifiers.parquet beside it. The downloaded_identifiers fixture had no equivalent guard, so four tests ended in MissingBabelFileError out of fixture setup rather than skipping. That reads as a broken test environment when it is the documented, expected outcome for a release that does not publish the file. Catch the error the downloader already raises for exactly this case and skip on it, which also surfaces the downloader's own message about setting BABEL_RELEASES_URL. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NCIT:C55060 has no rows at all in Concord.parquet as of Babel 2026jul22, so the four tests parametrized over it asserted len(results) > 0 against an empty list and failed. The other two CURIEs are unaffected. Commented out rather than deleted, with the evidence beside it: NodeNorm still resolves the CURIE — from Babel 2025sep1, with a clique of 2 — so this is not a typo or a retired identifier, and something changed between those two releases. Deleting the line would throw away the only record that it used to work, so it stays in the file with a note to restore it once the cause is known. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…to see The skip guard added in 584931a had no test, for the same reason the bug existed: it only shows up in a run against a real Babel release, and those skip entirely for anyone without one configured. Call the fixture function past its decorator with a stubbed downloader, so the behaviour is exercised by the unit suite that runs on every push — including that a non-404 failure still fails the run rather than being swallowed as a skip. Record two things in CLAUDE.md that cost time this session: a release can publish Concord.parquet without Identifiers.parquet, so "does this release have the Parquet files?" is two questions and not one; and pytest_sessionfinish deletes data/test, so every full run re-pays the multi-gigabyte download. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two mechanisms had grown for one rule: shared_downloader HEADed Concord.parquet and skipped the session on a 404, while downloaded_identifiers caught the downloader's own MissingBabelFileError. The HEAD was also the wrong shape for the question — it answered for the session what has to be answered per file, since a release can publish Concord.parquet without Identifiers.parquet. Route every DuckDB file through _download_or_skip, which skips on the error the downloader already raises for exactly this case. shared_downloader keeps only the guard that is genuinely session-wide: whether the server can be reached at all. A release publishing Metadata.parquet but not Concord.parquet now runs the tests that need only Metadata, rather than skipping them along with everything else. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The nodenorm fixture just constructed a client, so a connection error surfaced inside each test body and failed it. CI runs integration-test on every push to main and on a weekly cron, so a RENCI outage turned the build red for reasons unrelated to any code change — the exact failure the Babel-side skip was written to avoid. Give the fixture the same treatment: probe `status` once, skip on a RequestException. This cannot cover an outage that begins mid-run, but it covers the one that recurs. Closes #19. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…r 2 GB The assertion was `size > 2 GB`, a hard figure picked against a release long since superseded — the kind of number CLAUDE.md says drifts silently and then misleads, and one that fails for a legitimately smaller future release while passing for a 2.5 GB truncated download. Check the PAR1 marker at both ends instead. That tests what actually goes wrong: a download cut short, or an error page saved under a .parquet name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The probe added in ae423c3 had no test, for the reason that made the bug it fixes worth fixing: these fixtures only do anything during an integration run, and CI's integration job skips, so nothing would notice the probe being dropped. Call the fixture function past its decorator with requests.get patched — unreachable and 5xx both skip, a reachable API still returns a client. Confirmed non-vacuous: removing the probe fails the first two. Also record in CLAUDE.md that addopts already carries `-n auto`, so a serial run needs `-n0`; `-p no:xdist` leaves the parsed `-n` behind and pytest exits with `unrecognized arguments: -n`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
shared_downloader HEADed duckdb/Concord.parquet and, until the per-file skip landed, read the status to decide whether the release published the Parquet files. That second job is now _download_or_skip's, and only the status told the two apart — so what is left reads like a publication check that forgot to check anything. Point it at the release root instead: reachability is a question about the server, not about a file, and a 404 from a reachable server is still a reachable server. Also warn in CLAUDE.md about running two full suites at once. data/test is a fixed path shared by every run and pytest_sessionfinish deletes it unconditionally, so the second run tears down the first one's download; the FileLock does not help, because it guards one file rather than a session. The symptom is `du -sh data/test` going down instead of up while a run that should take ~9 minutes never finishes, which is easy to misread as a bug in the downloader. Recorded with how to detect, avoid and recover from it, and the two stale descriptions of the old Concord probe are corrected in the same pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pytest_sessionfinish removed data/test unconditionally, so any pytest invocation did it — including `-m "not integration"`, the fast loop run constantly while editing. That silently destroyed the multi-gigabyte download of an integration run in another terminal, and the integration run simply started over, which looks like a downloader defect rather than an unrelated pytest two windows away. It was diagnosed as one twice during this work: first as a resume bug in the new "no validator, no resume" rule, then as two concurrent full suites. Both were wrong; the fast unit suite was doing it. Skip the cleanup when the session selected no integration tests. The unit suite never creates or reads data/test, so it has no business deleting it. Two concurrent integration runs still clash — unavoidable while the path is shared, and now documented with how to detect, avoid and recover from it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
It was commented out with the evidence inline, pending a decision. The decision is that this is not a blocker for the test-infrastructure work: whether Babel was right to drop the cross-references is a question about Babel's data, and if a good xref went for a bad reason it belongs upstream rather than here. Delete the CURIE and keep a two-line pointer to #29, which carries the evidence and covers restoring a third CURIE — with only MONDO:0004979 and MONDO:0005044 left, the integration tests no longer exercise any non-MONDO namespace, which is the coverage NCIT:C55060 was actually providing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gaurav
added a commit
that referenced
this pull request
Sep 1, 2026
…t assumes (#31) `CLAUDE.md` had grown to 396 lines by accretion — each hard-won rule from a review round appended wherever it fit. It was accurate about this repository's internals and silent about the system those internals exist to read: it never said what Babel is, what a clique or a concord is, or why NodeNorm's version matters. An agent arriving at the `Concord.parquet` query code had no model of the data. This renames the file to `AGENTS.md`, gives it that missing context, moves the deep caching invariants to `docs/`, and removes what was being said twice. **Documentation only — no change to `src/`.** ## What's here **Renamed to `AGENTS.md`**, the filename coding agents other than Claude Code look for; the guidance was never Claude-specific. `CLAUDE.md` remains as a four-line pointer, so Claude Code still finds it. Four test docstrings that referenced the old name are updated. The rename is the first commit, so the substantive edits that follow read as edits rather than as a delete plus an add. **A Domain context section** covering only the terms this codebase actually uses — clique, preferred identifier, concord, conflation, Biolink type — and NodeNorm's relationship to a specific Babel release. It is a summary with links rather than copied upstream prose, so it cannot drift from upstream unnoticed. The part that earns the most space is the boundary between concords and cliques. Babel's own `AGENTS.md` says to answer clique-membership questions from a finished build *"never from the concords that fed it"* — and concords are exactly what this tool reads. That is not a defect, it is the purpose: `xrefs` reports the evidence Babel read, which is what you want when a merge looks wrong, while NodeNorm reports the verdict. Written down explicitly so that nobody later "improves" `xrefs` into a clique oracle. **The caching invariants moved to [`docs/Downloading.md`](docs/Downloading.md)** — near-verbatim, because the wording is hard-won and each rule records a specific failure that actually happened. `AGENTS.md` keeps a four-sentence summary and a pointer. That is ~90 lines an agent needs rarely, which had been sitting above the orientation it needs every session. **Deduplication.** The DuckDB spill directory was described in three places, the caching model in three, and the never-commit-the-internal-URL rule in three. Each is now stated once, where it belongs. `## Important Notes` is deleted outright — all three of its bullets restated earlier sections. The command examples now point at `README.md`, which documents every flag and is the user-facing reference. **Four factual corrections:** - `MissingBabelFileError` is raised on **any** 404, not only for `duckdb/` paths, as the file claimed in two places. `downloader.py:546` has no path condition. - CI budgeting pointed only at #18; #28 covers the same ground more precisely. - "recreated as #20-#24" predated #20 merging. - `README.md`'s configuration table was missing `BABEL_ALLOW_VERSION_MISMATCH`, which `env.default` has carried since #27. ## What upstream does not document Worth recording, because it shaped what could honestly be written. Neither Babel's `README.md` nor its `releases/ARTIFACTS.md` describes `Concord.parquet`, `Identifiers.parquet` or `Metadata.parquet`. `conflate` / `drug_chemical_conflate` and the `/status` `babel_version` field are absent from NodeNormalization's README. So the file schema in the new section is what **this repository's code assumes**, verified against real files — and it is labelled that way rather than presented as a published contract. Release naming, `latest` and `VERSION.txt` are undocumented upstream too. Filed upstream as [NCATSTranslator/Babel#1077](NCATSTranslator/Babel#1077), which links back to the Domain context section as the shortest statement of what is missing. ## What it deliberately does not do - **Document `search-xrefs`** — that is #21, not on `main`. - **Rewrite the version-skew wording** — #30 will change that behaviour; editing the prose now means editing it twice. - **Split Architecture or Testing into `docs/`** — those are context an agent needs often, and spreading them costs more than the length saves. ## Verification 272 unit tests pass and ruff is clean (no source changes, so both should be unchanged). Every relative link and intra-file anchor in `AGENTS.md`, `docs/Downloading.md` and `CLAUDE.md` resolves, no `.py` file still references `CLAUDE.md`, and the symbols the reworked text names were re-checked against the code. Nothing is blocking this merge. **One thing to judge:** `AGENTS.md` is 370 lines, down from 396, with a further 84 in `docs/`. Total documentation went *up*, because the Domain context section came out at 61 lines rather than the ~25 originally scoped. The extra went on the concord/clique boundary above. If that reads as too much for an agent file, the obvious trim is `## Testing` (73 lines, now the largest section).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Running the full test suite against a Babel release that actually publishes the DuckDB Parquet files produced 4 failures and 4 errors, and not one of them was a defect in the code under test. Chasing those turned up a fifth problem that had been corrupting the runs themselves: any
pytestinvocation deleted the integration cache, including the-m "not integration"loop you run constantly while editing.This PR makes the suite report absent dependencies honestly and stop sabotaging itself, so that a red run means a real problem again. It is tests and documentation only — no change to
src/.Closes #19.
What's here
A file being published is settled per file, not per session. A release can publish
Concord.parquetwithoutIdentifiers.parquet— the current one does exactly that, serving a 4.6 GB Concord with no Identifiers beside it — so "does this release have the Parquet files?" is not one question. Every DuckDB file now goes through_download_or_skip(), which skips on theMissingBabelFileErrorthe downloader already raises for a 404 on aduckdb/path.shared_downloaderis left answering only what is genuinely session-wide — is the server reachable — and probes the release root rather than a named file, so it cannot be misread as a publication check that forgot to check anything.A unit-test run no longer deletes the integration cache.
pytest_sessionfinishremoveddata/testunconditionally, and that path is fixed rather than per-run, so any session did it. Apytest -m "not integration"in one terminal silently destroyed a multi-gigabyte download in progress in another, and the integration run simply started over — which reads as a downloader defect rather than an unrelatedpytesttwo windows away. The cleanup now runs only when the session actually selected integration tests. The unit suite never creates or reads that directory, so it has no business removing it.An unreachable NodeNorm skips instead of failing. The
nodenormfixture only constructed a client, so a connection error surfaced inside each test body and failed it. CI runsintegration-teston every push tomainand on a weekly cron, so a RENCI outage turned the build red for reasons unrelated to any code change — the exact failure the Babel-side skip was written to avoid. The fixture now probesstatusonce and skips on aRequestException.NCIT:C55060is removed from the test CURIEs. It has no rows at all inConcord.parquetas of Babel2026jul22, so the four tests parametrized over it assertedlen(results) > 0against an empty list. NodeNorm still resolves it — as "Hypertension, CTCAE", abiolink:PhenotypicFeaturewith a clique of 2, from Babel2025sep1— so this is not a typo or a retired identifier, and something changed between those two releases. That question is #29, not this PR's: whether Babel was right to drop the cross-references is a question about Babel's data, and if a good one went for a bad reason it belongs upstream.valid_curies.txtkeeps a two-line pointer so the short list explains itself.Identifiers.parquetis checked for being a Parquet file, not for being large. The assertion wassize > 2 GB, a figure picked against a release long since superseded. It would fail a legitimately smaller future release while passing a 2.5 GB truncated download. It now checks thePAR1marker at both ends, which tests what actually goes wrong: a download cut short, or an error page saved under a.parquetname.What it produces
Against a release that publishes the Parquet files, the full suite is 299 passed, 3 skipped, 0 failures, 0 errors (16m31s, dominated by the 4.6 GB download). It was 296 passed / 4 failed / 4 errors / 28 skipped before. The 25 tests that turned from skips into passes are the Parquet-dependent ones that had never actually run — they now exercise real data, including the DuckDB query paths and the recursive expansion. Against a release without the files, the suite still skips cleanly, as documented.
Nine new unit tests cover the three guards, because this whole class of bug is otherwise invisible: these code paths only execute during an integration run, and CI's integration job skips, so nothing would notice a guard being dropped. It is the same blind spot that hid ten stale
get_curie_xref.cache_clear()calls until a review found them by reading rather than running. Each guard was confirmed non-vacuous by removing it and watching the test fail.Four things that cost real time are now in
CLAUDE.md: that a release can publish one DuckDB file without the other; thatpytest_sessionfinishdeletesdata/testso a full run re-pays the download; that two concurrent integration runs still clash, with how to detect, avoid and recover; and thataddoptscarries-n auto, so a serial run needs-n0rather than-p no:xdist.What it deliberately does not do
NCIT:C55060. Tracked in NCIT:C55060 lost all its cross-references between Babel 2025sep1 and 2026jul22 #29, which covers both outcomes: picking a replacement CURIE if Babel was right to drop it, or taking it upstream if a genuinely good cross-reference went for a bad reason.data/testis a shared fixed path. Documented instead, with the symptom spelled out, because it presents as a downloader bug.Concord.parquetalone is a 4.6 GB download per run.Nothing is blocking this merge
Everything outstanding is tracked in #18, #26, #28 and #29, and none of it makes what ships here wrong. #29 is the one worth reading before merging: it records that the test CURIE list is now two entries, both
MONDO:, so the integration tests exercise no other namespace until a third is restored. That is reduced coverage, not a defect in this change.How this landed — two things reached their final shape by correction rather than design. Kept for anyone tracing why a line looks the way it does; the durable conclusions are in the code comments and CLAUDE.md above.
The per-file skip arrived in two steps. The
Identifiers.parquetskip was first added inline in thedownloaded_identifiersfixture, duplicating theMissingBabelFileErrorhandling thatshared_downloaderwas doing with a HEAD probe. That left two mechanisms for one rule, which prompted collapsing both onto_download_or_skip()and reducingshared_downloaderto a reachability check — and then re-pointing that check at the release root, since a probe namingConcord.parquetstill looked like the publication check it had stopped being.The cache deletion was misdiagnosed twice before the cause turned up. The symptom was a full run whose
data/testshrank instead of growing and that never finished. It was blamed first on the new "a resume requires a validator" rule in the downloader — wrong, the server sends both an ETag and a Last-Modified, so that rule never fired — and then on two concurrent full suites, which was real but not the cause. The actual culprit was the unit suite: everypytest -m "not integration"run during the investigation deleted the download it was investigating. That is whypytest_sessionfinishnow carries an explicit "do not remove this guard" comment naming the misdiagnosis, and why CLAUDE.md describes the symptom rather than just the rule — the failure mode is much easier to recognise than to reason out.