Repository navigation
Run the CI test job in parallel with pytest-xdist - #253
Conversation
Lock pytest-xdist 3.8.0 and its dependency execnet 2.1.2. No other locked package moves; pexpect's ptyprocess marker keeps the form main locked, which uv lock --check accepts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6J8V2hdzByTC5Q7Fnu
test_any_refused_input_stops_the_impact_script_before_it_scores and test_any_refused_input_stops_the_direct_script_before_it_writes built their parameters by iterating over sets of path strings, whose order follows the per-process string hash seed. pytest-xdist requires every worker to collect the same tests in the same order, so the workers refused to start. Iterate over the sorted paths instead; the collected node ids are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6J8V2hdzByTC5Q7Fnu
Run pytest with -n auto --dist loadfile, one worker per runner core, and restate the timeout comment from the new measurements: the three latest serial test jobs on main took 17.3, 15.3 and 19.9 minutes; on a 4-core VM the suite took 34.4 minutes serially and 9.2 to 9.7 minutes in parallel. Keep timeout-minutes at 30. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6J8V2hdzByTC5Q7Fnu
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Ready for hub review Head: Items and how I resolved them
Tests and checks run
Judgment calls
Generated by Claude Code |
|
Ready for hub review Independent review by a Claude Opus 5.5 cloud session for the PolicyBench owner session. Verdict: APPROVE at The PR's head on GitHub is still None of the findings below blocks the merge. Findings 1–3 propose comment or description text. Findings1.
|
| Commit | Run | Test job |
|---|---|---|
| c9f7269 (Count only real request timeouts in the supervisor's concurrency backoff #183) | 41486939223 | 29:09, success |
| b0c8f2e (Probe card-less models under the card the onboarding gauntlet derives #189) | 41486949933 | 29:32, success |
| f0f7fae (Pin the reference adversary's leaderboard impact to the pass's inputs #205) | 41494610573 | 29:07, success |
| 7bbd92b (Record PolicyEngine provenance without importing policyengine #186) | 41494665359 | 23:14, success |
| ec5413c (Merge batch repair rounds cell by cell, like sync #180) | 41495271792 | cancelled at 30:01: the check run's annotation reads "The job has exceeded the maximum execution time of 30m0s", and the log shows the suite at [100%] at 18:22:45, about seven seconds before the job ended at 18:22:53 |
So the serial job on main already hits this limit. That makes this PR more urgent than the description's "28 seconds short" suggests.
"34.4 minutes serially" doesn't match the description's Times table, which gives 34:10 (34.2 min) before the fix and 32:05 (32.1 min) after. UNVERIFIED either way, since it is a VM figure.
The comment leaves out the evidence that justifies keeping 30 minutes: the three CI attempts of the parallel job.
Suggested replacement for lines 33-37:
# Serially, the test job on main took 29.5 minutes at b0c8f2e and hit
# this limit at ec5413c (2026-11-02). With -n auto --dist loadfile it
# took 12.7, 12.5 and 16.2 minutes (run 41490222217, attempts 1-3). Fail
# a hung job instead of holding a shared runner for GitHub's 6-hour
# default.3. Description: three small corrections (non-blocking)
-
Run 3. "Run 3 took about 3.5 minutes longer ... and its logs don't show why." That's accurate in that no line names a cause. But the logs do show the slowdown was spread evenly, not concentrated in one test. Elapsed seconds from the first progress line to each percentage mark:
Attempt 10% 25% 50% 75% 90% 95% 100% 1 17 87 180 476 594 663 700 2 18 87 176 464 564 634 687 3 23 108 214 614 764 859 917 Attempt 3 runs 20–30% behind at every mark, while its checkout,
pip install uvanduv syncsteps took about as long as attempts 1 and 2 (20 s, 2 s, 3 s against 17–18 s, 2–5 s, 5–6 s). That points to a slower runner, not a test. Suggested sentence: "Run 3 was 20–30% slower at every progress mark, from 10% to 100%, with no single test holding it up, which points to a slower runner." -
The 7.4-minute floor. The bound is correct: one worker runs all of
test_freeze_terra.py(LoadFileSchedulingsubclassesLoadScopeScheduling;xdist/scheduler/loadfile.py:10). But the job can't actually reach it. With--loadscope-reorder, on by default (xdist/plugin.py:131-134), xdist queues files by test count, not by duration (xdist/scheduler/loadscope.py:368-373). On today's merge,test_freeze_terra.pyhas 265 tests and is 7th by count. With 4 workers it goes out in the initial batch as some worker's second file, so it starts only after that worker finishes its first file. The floor is then "first file on that worker + 7.4 minutes". Suggested wording: "...so more cores can't bring the job under 7.4 minutes there, and in practice it starts after another file on its worker finishes." -
Base counts. "On
mainat 875fe1f and on this branch ... collects 6,873 of 6,909 tests (36 deselected)" is correct (below). Today'smainmerged with the head collects 7,097 of 7,135 (38 deselected), so the owner's rerun after syncing will report larger totals than the description.
4. Non-blocking notes for the owner
CLAUDE.md:6and:30, andREADME.md:60, tell contributors to runpytest/pytest -m "not slow". These still work. On a 4-core machine,pytest -m "not slow" -n 4 --dist loadfiletook 11:11 for me against 43:26 serially. A one-line note inCLAUDE.mdwould save local runs time. Not needed for this PR.- When the parallel suite runs from a shallow clone, it fails 22 more tests that need git history:
test_build_references_upgrade.py,_1012.py,_1022.py("cannot read ... at f108a95 (fetch full history)") and twotest_notes.pybbce tests. My first parallel run hit this because my session's clone was shallow. The cause is the clone, not parallelism: CI'stestjob setsfetch-depth: 0(ci.yml:44), and aftergit fetch --unshallowthese tests passed in both modes.
Checked and sound
Item 1: the parallel suite on today's main. Scratch merge git checkout -B scratch-merge origin/main && git merge --no-ff --no-edit 4491519e... (origin/main = 4e5a8459, #251), uv sync --locked --extra dev --python 3.12: pytest-xdist 3.8.0, filelock 4.2.6, Python 3.12.3. Full history (after git fetch --unshallow).
- Parallel:
uv run pytest -m "not slow" -n 4 --dist loadfile --tb=short -q -p no:cacheprovider --junitxml=par2.xml --durations=0 -rfE→27 failed, 6981 passed, 89 skipped, 3 warnings in 671.85s (0:11:11). - Serial:
uv run pytest -m "not slow" --tb=short -q -p no:cacheprovider --junitxml=ser.xml --durations=0 -rfE→27 failed, 6981 passed, 89 skipped, 38 deselected, 3 warnings in 2606.91s (0:43:26). - I ran them one after the other. A first serial attempt was stopped at 81% by my session's 30-minute background limit, so I reran it in full. A first parallel run on a shallow clone is the one described in finding 4.
- Comparison of the two junit files by (classname, name): both files hold the same 7,097 test cases (none only in one), and no test's outcome differs (6,981 passed, 89 skipped, 27 failed in each). This confirms the description's "No other test failed only in parallel" on today's
main. - My session's proxy refuses
huggingface.co(curl https://huggingface.co/api/models/gpt2→CONNECT tunnel failed, response 403). The 27 failures in each run are the same 27 tests, all intests/test_scenarios.py:test_adult_dependents_are_allowed_when_structure_is_clean,test_aggregate_net_worth_input_is_not_preserved,test_children_and_adults_split_by_age,test_conditional_inputs_are_not_preserved,test_formula_county_fields_are_not_prompted_or_sent_to_policyengine,test_generate_scenarios_count,test_generate_scenarios_deterministic,test_generate_scenarios_different_seeds,test_generate_scenarios_uses_loader,test_geographic_leaf_inputs_are_preserved,test_invalid_households_are_filtered,test_large_households_are_allowed_when_structure_is_clean,test_load_scenarios_from_manifest_round_trips,test_missing_bool_inputs_are_not_preserved,test_overtime_premium_input_is_sent_to_policyengine,test_pe_household_includes_cross_entity_inputs,test_promptable_inputs_are_pure_leaf_variables,test_richer_cps_inputs_are_preserved,test_scenario_manifest_exports_summary_fields,test_scenario_structure,test_scenarios_from_cps_frame_can_exclude_households,test_stated_usual_hours_reach_the_snap_work_tests(3 cases),test_total_income_ignores_deductions_and_hours,test_unstated_or_zero_hours_keep_the_engine_default(2 cases). In both junit files, every one of the 27 failure texts nameshuggingface.co/hf_hub_downloadwith the proxy refusal, anddiffof the two sortedFAILEDlists prints nothing. The author saw the same 27.
Item 2: collection order.
PYTHONHASHSEED={0,1,2,3,random} uv run pytest -m "not slow" --collect-only -q -p no:cacheprovideron the scratch merge: all five give 7,097 ids with md5 prefix2d7223b27d08.- Control: with
sorted(...)removed from the two parametrizations in a scratch copy,--collect-onlyontests/test_reference_audit_pins.pyunder seeds 1, 2 and 3 gave three different md5s (d8c833a1ddb3,2b7f223738f7,3bc27c3c8f32), so the check detects the defect the PR fixes. I restored the file withgit checkout. - I searched
tests/, including the files Pin the reference adversary's leaderboard impact to the pass's inputs #205, Record PolicyEngine provenance without importing policyengine #186 and Merge batch repair rounds cell by cell, like sync #180 added or changed, for parametrizations andparams=fixtures over sets, frozensets, set comprehensions,.keys()/.items()/.values()and globs. The twoparams=fixtures already sort (tests/test_reference_upgrade.py:180params=sorted(TREES),tests/test_paper_results.py:546params=sorted(RESULTS)).tests/test_notes.py:293sorts its glob. The remaining.items()/.values()parametrizations (test_eval_no_tools.py:3320,test_finish_terra.py:3008,test_request_timeout_signature.py:120,127,test_cache.py:1145) iterate dicts, which keep insertion order. Intest_reference_audit_pins.py,_refusalssorts its set (sorted(REWRITTEN & set(module.INPUTS)),sorted(path for path in module.INPUTS ...), lines 472 and 475), and the other parametrizations iterate theIMPACT/DIRECT/SPECSdicts. Nopytest_generate_testsorpytest_collection_modifyitemshook exists intests/. - pytest-xdist 3.8.0 doesn't set a hash seed.
grep -rni hashseedoverxdist/andexecnet/finds nothing. Workers start throughexecmodel.subprocess.Popen(args, stdout=PIPE, stdin=PIPE)(execnet/gateway_io.py:34), with noenv=, so each worker inherits the controller's environment. Unless CI setsPYTHONHASHSEED(it doesn't), each worker randomizes str hashes on its own. xdist compares the workers' collections as ordered lists (xdist/report.py:18,if from_collection == to_collection, called fromloadscope.py:409-421). So any future parametrization over a set of strings will stop the run with "Different tests were collected". The five-seed check above is the guard. A test or aconftest.pycheck could make it permanent, but this PR doesn't need one.
Item 3: state shared across workers.
- Empirical check: I touched a marker before parallel run 2 and ran
find . -newer marker(excluding.venv,.git,__pycache__) andgit status --porcelainafter. No tracked file changed (git statusempty). The only new paths were.hypothesis/and an emptyresults/local/(both git-ignored;results/local/is the CLI's default output directory,policybench/cli.py:157and others) plus__pycache__directories. Run 1 gave the same result. Hypothesis writes its example database to./.hypothesis/, shared by the workers. Hypothesis's directory database is meant for concurrent use, and no test failed from it. I didn't read Hypothesis's source to confirm its write protocol. os.chdir: none in tests outside a string fixture (test_fix_module_closure.py:71). Six files usemonkeypatch.chdir, which is per process.- Environment variables set without
monkeypatch:test_model_response_window.py:154-164setsTZand callstime.tzset()with atry/finallyrestore. That is per process, so the same in serial and parallel.test_eval_no_tools.py:3060setsLITELLM_LOCAL_MODEL_COST_MAPinside a code string run in a child interpreter. - Fixed
/tmppaths: none. Temporary paths come fromtmp_path,tempfile.TemporaryDirectory()ortempfile.mkdtemp(...), all unique per call (test_consensus.py:744mkdtemp(prefix="pb-pass-payload-")is never removed, but it can't collide). No test binds a port. - Git: tests run
git show,log,merge-base,rev-parseandls-filesagainst the shared checkout (read-only, no index lock).git add/commitrun only in scratch repositories (test_finish_*.py,test_freeze_terra.py:1089-1090, with-C <root>under the test's temporary directory). - Monkeypatched repository paths: e.g.
test_freeze_gpt61sol.py:505unlinksdriver.JUDGE_PROVENANCE, which line 469 monkeypatches to a temporary record. - Hugging Face: only
policybench/policyengine_runtime.py:474-507(materialize_certified_us_dataset) callshf_hub_download. It is reached throughmake_us_microsimulation(policyengine_runtime.py:510-515),policybench/scenarios.py:477, 931andpolicybench/population_weights.py:184. In both my runs every test that reached the download failed with the proxy's 403, and all 27 are intests/test_scenarios.py. Under--dist loadfilethat file runs on one worker, so two workers don't download at once in this suite. If a second file ever does, huggingface-hub 1.4.1 guards each blob withWeakFileLock(lock_path)(file_download.py:1214-1247), which wrapsfilelock.FileLock. I ran fourspawnprocesses contending oneWeakFileLockunder filelock 4.2.6: each held it in turn ("max holders at once: 1", exit codes all 0).
Item 4: lock and pyproject.toml.
pyproject.toml:29:"pytest-xdist>=3.6",insidedev = [only.grep -n xdist pyproject.tomlfinds just that line.git diff 875fe1f 4491519e -- uv.lock: no removed lines. It adds theexecnet2.1.2 andpytest-xdist3.8.0 package entries and one line each to thedevlist andrequires-dist. The header lines (version = 1,revision = 3,requires-python,resolution-markers) are unchanged.uv lock --check: passes at the head and on the scratch merge (Resolved 139 packages).- CI's test job runs
uv sync --locked --extra dev(ci.yml:49), so xdist is installed where-nruns.
Item 5: workflow. The lint and app jobs are unchanged. .github/workflows/paper.yml doesn't run pytest. No Makefile; CLAUDE.md and README.md mention plain pytest (finding 4). Worker count: finding 1.
Item 6: the test change.
--collect-only -qat the merge base 875fe1f and at the head (each in its own worktree withPYTHONPATHset to it): both6873/6909 tests collected (36 deselected). The sorted id lists are identical (diffprints nothing), and 52 diff lines differ in unsorted order (26 reordered ids).- Mutation, impact script: with
if digest != pinned:inreference_audit/2026-10-05-louisiana/scripts/leaderboard_impact.py:175replaced byif False:,test_any_refused_input_stops_the_impact_script_before_it_scores[reference_audit/2026-10-05-louisiana-paper/snapshot/20260501/runs/us_full_run_20260612_policyengine_4_16_1_populace/data.json.gz]failed withAssertionError: stage ran after the inputs were refused. Restored, it passes. Killed. - Mutation, direct script: with the same change at
reference_audit/2026-10-05-medicare-part-b/scripts/propose_exclusions.py:142,test_any_refused_input_stops_the_direct_script_before_it_writes[reference_audit/2026-10-05-medicare-part-b/scripts/propose_exclusions.py-app/src/modelMeta.ts]failed withFailed: DID NOT RAISE SystemExit. Restored. Killed.
Item 7: merging.
git merge-tree --write-tree origin/main 4491519e→743458ef..., exit 0.git merge-tree --write-tree <#252 head dc43c517> 4491519e→56b3415e..., exit 0. With081727b9(the brief's Move litellm to 1.95.0 (GHSA-g5ff-637f-6q2m) and accept it in the tool pilot's Gemini patch #252 SHA) →6983955f..., exit 0.- Three-way scratch merge (origin/main, then Move litellm to 1.95.0 (GHSA-g5ff-637f-6q2m) and accept it in the tool pilot's Gemini patch #252 at
dc43c517, then this head):uv lock --checkpasses (Resolved 139 packages). The lock moves litellm 1.88.6 → 1.95.0 and adds execnet 2.1.2 and pytest-xdist 3.8.0, with no other name or version line changing againstorigin/main. I didn't run the suite on the three-way merge (below).
Item 8: the description.
-
CI times, from
actions/runs/<run>/attempts/<n>/jobsand the job logs:Claim started → completed pytest summary line 875fe1f 17.3 min, pytest 16:43 12:05:37 → 12:22:55 = 17:18 6784 passed, 89 skipped, 36 deselected, 3 warnings in 1003.77s (0:16:43)bd11d55 15.3 min 23:37:54 → 23:53:14 = 15:20 — e0217ed 19.9 min 17:38:47 → 17:58:40 = 19:53 — b0c8f2e 29.5 min, pytest 28:47 17:00:32 → 17:30:04 = 29:32 6900 passed, 90 skipped, 36 deselected, 3 warnings in 1727.79s (0:28:47)attempt 1: 12.7 min, 12:00 17:20:28 → 17:33:11 = 12:43 6900 passed, 90 skipped, 3 warnings in 720.16s (0:12:00)attempt 2: 12.5 min, 11:45 17:34:12 → 17:46:42 = 12:30 6900 passed, 90 skipped, 3 warnings in 705.36s (0:11:45)attempt 3: 16.2 min, 15:39 17:47:06 → 18:03:20 = 16:14 6900 passed, 90 skipped, 3 warnings in 939.75s (0:15:39)All match. "29 minutes 32 seconds, 28 seconds short" matches. The check-runs for the head show
test,lint,appandpaperas success. -
Growth ed4f89a → 875fe1f:
git ls-treegives 53 → 91tests/test_*.pyfiles.git grep -c '^\s*def test_'overtests/*.pygives 1,090 → 3,608. Line counts oftests/**/*.pygive 35,168 → 116,719. All match. -
The 909 s sum: 443+123+83+81+71+56+52 = 909, correct. The seven files were first added on 2026-10-01 (
test_freeze_gpt61sol.py), 2026-10-10 (the haiku55 and musewonder freeze and finish files) and 2026-10-31 (the terra files), so "added between 1 and 31 Oct" holds. Per-file VM seconds: UNVERIFIED (my machine differs). My parallel run 2 shows the same pattern:--durations=0summed to 2,451 s, withtest_freeze_terra.pyfirst (560 s), thentest_notes.py178,test_freeze_gpt61sol.py162,test_run_audit_claude.py130,test_reference_audit_pins.py116,test_freeze_haiku55.py111 andtest_freeze_musewonder.py107. The seven files summed to 1,170 s. -
"The 40 slowest tests account for about 557 s of the 34-minute serial run": UNVERIFIED for the VM. My serial run shows the same share: the 40 slowest phases sum to 684 s of 2,509 s of
--durations=0time (27%), against the description's 557 s of about 2,040 s (27%). -
VM wall times (34:10, 32:05, 9:28/9:39/9:09, 10:08) and "6,757 passed, 89 skipped and 27 failed": UNVERIFIED (VM). My runs on today's larger merge took 11:11 in parallel and 43:26 serially, and failed the same 27 Hugging Face tests.
-
The 7.4-minute floor: 443 s = 7.4 min, and the reasoning holds as a lower bound (finding 3).
-
"This PR puts no test in an
xdist_group, and it skips, deselects, re-marks or weakens none": the diff touches only the two parametrize lines in tests (above).grep -rn xdist_group testsfinds nothing.
What I couldn't check
- How many workers
-n autostarts on GitHub's runner (finding 1). One CI line printingpsutil.cpu_count(logical=False)would settle it. - The 27 Hugging Face tests in
tests/test_scenarios.py: the proxy blockshuggingface.co, so I never saw them pass. CI ran them (6,900 passed in each parallel attempt). I couldn't test concurrent downloads against the real hub, only the lock. - The VM timings in the description (marked UNVERIFIED above).
- A parallel suite run on the three-way merge of
origin/main, Move litellm to 1.95.0 (GHSA-g5ff-637f-6q2m) and accept it in the tool pilot's Gemini patch #252 and this head. I checked that it merges cleanly and that the lock resolves; the owner's CI rerun after syncing will cover the rest. - Hypothesis's directory-database write protocol under concurrent writers (no failures observed; source not read).
Generated by Claude Code
With psutil installed, as the lock installs it, -n auto starts one worker per physical core, which can be fewer than the runner's CPUs. -n logical starts one per logical CPU, and without -q pytest prints its "created: N/N workers" line. The timeout comment now cites main's serial job, which hit the 30-minute limit at ec5413c, and the parallel job's times. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l runs The comment on the test job's timeout now gives the parallel time with -n logical (4 workers, 12.3 minutes in run 41526298328) next to the three -n auto attempts. CLAUDE.md gives the four-worker pytest command and says to run the suite from a full clone, since some tests read git history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The `timeout-minutes` comment in `ci.yml` now compares each parallel run with a serial run on the same base. The `--dist loadfile` comment names the 4-vCPU VM and gives both modes' times. `CLAUDE.md` says the `-n 4` command runs the same tests as CI. Its list of what the tests mock drops a PolicyEngine API that no test calls. It also names the two calls that reach the network: LiteLLM's cost-map fetch and the certified US dataset download from Hugging Face. The workflow itself doesn't change: `ci.yml` loads to the same data as on `main`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On
main, the CItestjob runs pytest in one process. On 2 Nov it took 29 minutes 32 seconds at b0c8f2e, and at ec5413c it ran into the job's 30-minute limit, so GitHub cancelled it. This PR splits the tests across pytest-xdist workers, one per logical CPU (-n logical --dist loadfile), and xdist starts four on GitHub's runner. At f4c3c38,main's serial job took 22.3 minutes, and this PR's job on the merge with that commit took 12.3 minutes at 654c525 and 15.7 minutes at efcecfc. All three report 7,064 passed and 90 skipped.pyproject.toml,uv.lock: addpytest-xdist>=3.6to thedevextra. The lock adds pytest-xdist 3.8.0 and execnet 2.1.2 and changes nothing else:git diff origin/main -- uv.lockshows no removed line, so no policyengine, policyengine-us, policyengine-uk, policyengine-core or litellm entry moves.tests/test_reference_audit_pins.py: parametrize two tests over sorted paths (see "Tests changed")..github/workflows/ci.yml: thetestjob runsuv run pytest -m "not slow" -n logical --dist loadfile --tb=short.uv syncinstalls psutil, so pytest-xdist 3.8.0 would count physical cores for-n auto;-n logicalcounts logical CPUs. Without-q, xdist prints the worker count (created: 4/4 workers). The comment ontimeout-minutes: 30gives the new measurements.CLAUDE.md: give the four-worker command for local runs, and say to run the suite from a full clone, since some tests read git history.Times
main(875fe1f, bd11d55, e0217ed)-n 4 --dist loadfile-n 4 --dist loadmainat b0c8f2e (run 41486949933)-n auto --dist loadfilemainat ec5413c (run 41495271792)mainat f4c3c38 (run 41524591677)-n logical --dist loadfile, 4 workers-n logical --dist loadfile, 4 workersAttempt 3 of run 41490222217 took about 3.5 minutes longer than attempts 1 and 2 on the same commit, and its elapsed time exceeded theirs by 18 to 42% at every progress mark from 10% to 100%. The job at efcecfc took 3.4 minutes longer than the job at 654c525 on the same tests, and its elapsed time exceeded that job's by 17 to 39% at those marks. In both comparisons the delay built up across the whole run, so we attribute it to a slower runner. At 16.2 minutes, attempt 3 finished 13.8 minutes inside the limit, so we kept
timeout-minutes: 30.We chose
loadfileoverload. It ran faster on the VM (9:09 to 9:39, against 10:08), and it keeps each test file in one worker, so a module-scoped fixture runs once per file, as in a serial run.Invariant: same tests, same outcomes
The parallel job collects and runs the same tests as the serial job, and each test gets the same outcome.
mainat 875fe1f and at 4491519 (this branch before it mergedmain),pytest -m "not slow" --collect-only -qcollects 6,873 of 6,909 tests (36 deselected), and the sorted node ids match (diffprints nothing).PYTHONHASHSEED1, 2 and 3 (one md5 of the id list), as xdist requires of every worker.--junitxmlfor the serial run with sorted parameters against parallel runs 2 and 3 (loadfile) and theloadrun. Each file holds 6,873 test cases; no node id appears in one and not the other, and no test's outcome differs.maingained Count only real request timeouts in the supervisor's concurrency backoff #183 and Probe card-less models under the card the onboarding gauntlet derives #189 after this branch started, so CI at 4491519 tested the merge with b0c8f2e. The serial test job on b0c8f2e and each of the three parallel jobs here report the same counts: 6,900 passed, 90 skipped and no failures, including the 27 tests that fail on the VM.mainat 4e5a845 and ran the suite both ways on a 4-vCPU machine. The serial and parallel junit files held the same 7,097 tests with the same outcomes: 6,981 passed, 89 skipped, and the same 27 tests failed behind the reviewer's proxy.main's serial job and this PR's two parallel jobs on the merge with it all report 7,064 passed and 90 skipped.The 27 VM failures all come from tests in
tests/test_scenarios.pythat reachscenarios_from_cps_frame→get_promptable_input_specs→make_us_microsimulation→materialize_certified_us_dataset. That last function callshf_hub_downloadfor the certified US dataset, and the VM's proxy refuseshuggingface.co(403), so these tests fail on the VM serially and in parallel alike. CI reaches Hugging Face: at 875fe1f, where this branch started,main's test job passed 6,784 (6,757 + 27) with 89 skipped, and the parallel jobs on this PR passed all 27.Tests changed
tests/test_reference_audit_pins.py::test_any_refused_input_stops_the_impact_script_before_it_scoressetof path strings (IMPACT[audit].inputs), whose order follows the per-process string hash seed. Each xdist worker collected the tests in a different order, and xdist refused to start ("Different tests were collected between gw1 and gw2").sorted(IMPACT[audit].inputs). Sorting changes only the order of the node ids.tests/test_reference_audit_pins.py::test_any_refused_input_stops_the_direct_script_before_it_writesDIRECT[script].inputs, also aset.sorted(DIRECT[script].inputs).No other test failed only in parallel.
Where the test time goes
mainon 8 Oct (ed4f89a) to 875fe1f on 31 Oct, where this branch started, test files grew from 53 to 91,def test_functions from 1,090 to 3,608, and test code from 35k to 117k lines.--durations=0).test_freeze_terra.pytook 443 s of that, followed bytest_notes.py(149 s),test_freeze_gpt61sol.py(123 s),test_run_audit_claude.py(113 s),test_reference_audit_pins.py(93 s),test_freeze_haiku55.py(83 s),test_freeze_musewonder.py(81 s),test_tool_pilot_argon.py(78 s),test_finish_terra.py(71 s),test_finish_haiku55.py(56 s) andtest_finish_musewonder.py(52 s).loadfile, one worker runs all oftest_freeze_terra.py, about 7.4 minutes of work on the VM, so more cores can't bring the job under 7.4 minutes there. xdist queues files by test count rather than duration (--loadscope-reorder, on by default) and hands a worker its next file once two or fewer of its tests remain. On review r1's merge withmainat 4e5a845,test_freeze_terra.pyranked seventh by test count, so with four workers it starts only after its worker finishes another file. Splitting it would lower the bound.40 slowest tests, serial (VM, unsorted parameters)
tests/test_freeze_terra.py::test_the_finalize_accepts_exactly_allowed_subsets_with_valid_checkstests/test_notes.py::test_no_board_model_declined_a_household_over_policyenginetests/test_reference_audit_pins.py::test_the_impact_script_scores_only_the_staged_inputs[reference_audit/2026-10-05-medicare-part-b]tests/test_engine_situation.py::test_every_situation_input_is_a_variable_of_the_pinned_engine[paper/snapshot/20260501/runs/us_full_run_20260612_policyengine_4_16_1_populace/scenarios.csv]tests/test_tool_pilot_argon.py::test_validator_accepts_only_known_names_on_their_entitiestests/test_notes.py::test_release_20260929_notetests/test_tax_table_audit.py::test_the_independent_scorer_reproduces_the_release_under_each_conventiontests/test_snapshot_artifacts.py::test_snapshot_deviation_audit_annotations_are_complete_and_finaltests/test_call_provenance.py::test_household_classification_and_published_numberstests/test_freeze_terra.py::test_rebound_adjudication_wording_reaches_applied_sentence_gatetests/test_tax_table_audit.py::test_the_independent_scorer_agrees_with_the_repo_scorertests/test_supervisor.py::test_supervised_run_keeps_policyengine_out_of_workerstests/test_notes.py::test_america_gov_note_factstests/test_tool_pilot_argon.py::test_score_counts_every_household_and_unparsed_outputs_as_missestests/test_snapshot_artifacts.py::test_snapshot_audit_annotations_have_no_orphan_rowstests/test_tool_pilot_argon.py::test_arm_labels_follow_the_modeltests/test_notes.py::test_release_20261010_notetests/test_notes.py::test_release_20261013_notetests/test_freeze_terra.py::test_the_finalize_accepts_publication_render_and_repin_sequencetests/test_freeze_terra.py::test_rebound_nonisolated_verdict_is_refused_at_export_and_freezetests/test_sensitivity_evidence.py::test_doc_all_output_scores_recompute_on_the_frozen_referencestests/test_freeze_terra.py::test_an_incumbent_prediction_change_is_refused_before_mutation[stage]tests/test_cost_latency.py::test_cost_is_present_exactly_when_recorded_usage_supports_ittests/test_reference_audit_pins.py::test_the_impact_script_scores_only_the_staged_inputs[reference_audit/2026-10-05-louisiana]tests/test_notes.py::test_release_20261006_notetests/test_finish_musewonder.py::test_install_references_is_idempotent_for_any_buildtests/test_snapshot_artifacts.py::test_manuscript_bootstrap_point_estimates_reproduce_model_statstests/test_finish_haiku55.py::test_install_references_is_idempotent_for_any_buildtests/test_cost_latency.py::test_a_replay_can_only_lower_the_mediantests/test_call_provenance.py::test_rows_record_each_responses_provenance_and_agree_with_the_ledgertests/test_uk_audit.py::test_a_later_stratum_does_not_change_earlier_drawstests/test_freeze_terra.py::test_rejudged_adjudicated_stage_passes_the_freezetests/test_uk_audit.py::test_sample_is_deterministictests/test_notes.py::test_categorical_asset_error_statementstests/test_snapshot_artifacts.py::test_frozen_impact_summaries_score_the_scored_referencetests/test_freeze_argon.py::test_a_stage_list_is_appended_to_release_20261013bs_amendmentstests/test_reference_audit_pins.py::test_the_direct_script_reproduces_its_files_from_a_junk_checkout[reference_audit/2026-10-05-medicare-part-b/scripts/propose_exclusions.py]tests/test_tool_pilot_argon.py::test_arm1_rescoring_reproduces_the_published_board_rowtests/test_freeze_terra.py::test_an_incumbent_prediction_change_is_refused_before_mutation[base]tests/test_freeze_terra.py::test_repository_input_edits_after_export_are_refused_without_writes[pyproject.toml]40 slowest tests, parallel (VM,
-n 4 --dist loadfile, run 3)tests/test_notes.py::test_no_board_model_declined_a_household_over_policyenginetests/test_freeze_terra.py::test_the_finalize_accepts_exactly_allowed_subsets_with_valid_checkstests/test_reference_audit_pins.py::test_the_impact_script_scores_only_the_staged_inputs[reference_audit/2026-10-05-medicare-part-b]tests/test_engine_situation.py::test_every_situation_input_is_a_variable_of_the_pinned_engine[paper/snapshot/20260501/runs/us_full_run_20260612_policyengine_4_16_1_populace/scenarios.csv]tests/test_scenarios.py::test_generate_scenarios_counttests/test_tool_pilot_argon.py::test_validator_accepts_only_known_names_on_their_entitiestests/test_notes.py::test_release_20260929_notetests/test_tax_table_audit.py::test_the_independent_scorer_reproduces_the_release_under_each_conventiontests/test_call_provenance.py::test_household_classification_and_published_numberstests/test_freeze_terra.py::test_rebound_adjudication_wording_reaches_applied_sentence_gatetests/test_tax_table_audit.py::test_the_independent_scorer_agrees_with_the_repo_scorertests/test_tool_pilot_argon.py::test_score_counts_every_household_and_unparsed_outputs_as_missestests/test_notes.py::test_america_gov_note_factstests/test_tool_pilot_argon.py::test_arm_labels_follow_the_modeltests/test_supervisor.py::test_supervised_run_keeps_policyengine_out_of_workerstests/test_notes.py::test_release_20261010_notetests/test_snapshot_artifacts.py::test_snapshot_deviation_audit_annotations_are_complete_and_finaltests/test_notes.py::test_release_20261013_notetests/test_notes.py::test_release_20261006_notetests/test_finish_musewonder.py::test_install_references_is_idempotent_for_any_buildtests/test_finish_haiku55.py::test_install_references_is_idempotent_for_any_buildtests/test_snapshot_artifacts.py::test_manuscript_bootstrap_point_estimates_reproduce_model_statstests/test_freeze_terra.py::test_rebound_nonisolated_verdict_is_refused_at_export_and_freezetests/test_uk_audit.py::test_sample_is_deterministictests/test_freeze_terra.py::test_the_finalize_accepts_publication_render_and_repin_sequencetests/test_freeze_terra.py::test_rebound_parse_flag_is_refused_at_export_and_freeze[False]tests/test_freeze_terra.py::test_rebound_parse_flag_is_refused_at_export_and_freeze[True]tests/test_snapshot_artifacts.py::test_snapshot_audit_annotations_have_no_orphan_rowstests/test_call_provenance.py::test_rows_record_each_responses_provenance_and_agree_with_the_ledgertests/test_notes.py::test_categorical_asset_error_statementstests/test_freeze_terra.py::test_rejudged_adjudicated_stage_passes_the_freezetests/test_cost_latency.py::test_cost_is_present_exactly_when_recorded_usage_supports_ittests/test_uk_audit.py::test_a_later_stratum_does_not_change_earlier_drawstests/test_cost_latency.py::test_a_replay_can_only_lower_the_mediantests/test_reference_audit_pins.py::test_the_impact_script_scores_only_the_staged_inputs[reference_audit/2026-10-05-louisiana]tests/test_snapshot_artifacts.py::test_frozen_impact_summaries_score_the_scored_referencetests/test_freeze_terra.py::test_a_run_state_treatment_off_the_registry_is_refusedtests/test_freeze_argon.py::test_a_stage_list_is_appended_to_release_20261013bs_amendmentstests/test_tool_pilot_argon.py::test_score_writes_a_strict_json_audit_and_flags_benchmark_searchestests/test_reference_audit_pins.py::test_the_direct_script_reproduces_its_files_from_a_junk_checkout[reference_audit/2026-10-05-medicare-part-b/scripts/propose_exclusions.py]Test plan
uv lock --check,ruff check .,ruff format --check .-n 4 --dist loadfileruns and one--dist loadrun, with per-test outcome comparison-n logical, the timeout comment)axiom: n/a: CI test runner change, no policy logic🤖 Generated with Claude Code