Overview
Running the jax_grad scripts locally under the resolved smoke profile produces
deterministic assertion failures in scripts that pass in CI on the same commit.
imaging/jax_grad/lp.py is the decisive control: it passes in CI on two independent
runs and fails locally, so the three local failures found during PyAutoHands#226 were
environment artefacts, not source defects.
This is an active trap for anyone validating these scripts locally — during #226 it
looked exactly like two fresh correctness regressions on current main. Whatever the
cause, either the scripts or the documented local-run recipe should make this
reproducible, so that a local FAIL means something.
Split out of PyAutoHands#226; see PyAutoMind/complete/2026/08/jax-grad-smoke-timeout-budget.md.
Plan
- Capture both environments as data before anything drifts.
pip freeze +
np.show_config() from the local venv, and the same from a CI job. The prompt's
table is currently the only record of the local side.
- Run the decisive A/B against the control. Build the CI install set, confirm
lp.py PASSES, then change only numpy 2.4.6 -> 2.2.6 and confirm it FAILS. Run
it in both directions — the upgrade recovering PASS is what rules out
install-ordering effects.
- If numpy is not the discriminator, diff the two freezes and bisect the
remaining delta against lp.py, cheapest-first.
- Root-cause the mechanism, not the correlation.
lp.py's failure is an
active-set change in the positive-only NNLS solve, not a last-digit tolerance
breach — that points at the BLAS/LAPACK backend the numpy wheel ships, not at
numpy's Python layer.
- Then decide the verdict — either the assertions are under-specified across the
supported dependency range, or the local environment is out of spec. These call for
different fixes, and only measurement chooses between them.
- Make a local FAIL mean something: whichever verdict, land a documented,
reproducible local-run recipe in AGENTS.md and cross-reference it from the
existing profile_smoke.yaml warning.
What the code already tells us
scripts/misc/util.py:185 — assert_eager_jit_consistent(..., rtol=1e-10) is
documented as a pure_callback constant-folding detector, not a numerics
check. Constant-folding produces order-unity disagreement; the observed 8.9e-6 is
~5 orders of magnitude too small to be the thing this assert exists to catch.
scripts/imaging/jax_grad/lp.py:213 — the failing guard is
any(|ad[source]| > 1e-3), i.e. the NNLS solve zeroed the source. The linear
solve is landing on a different active set — the same signature as the
pixelization.py 8.9e-6 gap and the regularization.py marginal FD breach.
.github/scripts/smoke_install.sh pins nothing but jax<0.7/jaxlib<0.7 — numpy
is whatever the resolver picks that day. "The CI set" is not a fixed target, and
nothing in the repo tells a local runner how to reproduce it.
AGENTS.md documents run_smoke.py and run_all_scripts.sh but has no
local-environment recipe at all. That gap is the trap.
config/build/profile_smoke.yaml:74 already carries a "Do NOT re-derive these from
local runs" warning naming this exact divergence — the right place to record the
resolved cause.
Detailed implementation plan
Work Classification
Workspace (single repo) — routes to /start_workspace.
Affected Repositories
autolens_workspace_test (primary)
Branch Survey
| Repository |
Current Branch |
Dirty? |
PyAutoLabs/autolens_workspace_test |
main @ b18fd38 |
clean |
worktree_check_conflict jax-grad-local-vs-ci-assertions autolens_workspace_test
-> exit 0, no conflict.
Warning (not a block): feature/jax-grad-param9-mismatch and
claude/rectangular-mesh-gradients-mh1j0z exist on the remote as unregistered
prior jax_grad work.
Suggested branch: feature/jax-grad-local-vs-ci-assertions
Worktree root: ~/Code/PyAutoLabs-wt/jax-grad-local-vs-ci-assertions/
(created later by /start_workspace)
Implementation Steps
-
Capture ground truth. From the local machine: pip freeze > local.txt and
python -c "import numpy; numpy.show_config()". From CI: the same two, pulled
from a fresh main smoke run's job log, or reproduced by running
.github/scripts/smoke_install.sh against fresh chain checkouts. Record jax,
jaxlib, numpy, scipy and tfp-nightly explicitly.
-
The A/B. In an isolated venv holding the CI set, run
scripts/imaging/jax_grad/lp.py via env_config.build_env_for_script with the
workspace root as cwd (the exact env the report used) -> expect PASS.
pip install numpy==2.2.6 alone -> rerun -> expect the
All source-parameter gradients are ~zero failure. Then pip install numpy==2.4.6
-> rerun -> expect PASS to return. lp.py is the control because it is the only
script known to pass in CI and fail locally, and at ~41s it is the cheapest.
-
Widen if needed. diff local.txt ci.txt; bisect the remaining delta
package-by-package against lp.py.
-
Mechanism. Compare np.show_config() across the two numpy versions (OpenBLAS
build and threading). Check whether the inversion's non-negative solve (the fnnls
active set) selects a different set under each, by printing the active-set size at
the lp.py evaluation point in both. This is what distinguishes "float noise"
from "a different solution" — the evidence says it is the latter.
-
The fix, chosen by the measurement:
- If the supported dependency range genuinely admits this spread: the
rtol=1e-10
on assert_eager_jit_consistent is the outlier and the only tolerance worth
changing — raised to a value that still detects constant-folding (which is
order-unity), with the measured spread written in as its justification. Leave
assert_gradients_match (rtol=1e-3/atol=1e-4) alone unless
regularization.py's breach survives step 4.
- If the local env is out of spec: no tolerance change at all. Add a
"Local runs of jax_grad/" subsection to AGENTS.md with the exact install
(mirroring smoke_install.sh plus the resolved pins), the build_env_for_script
invocation, and the cwd requirement.
- Either way: cross-reference the verdict from the
config/build/profile_smoke.yaml "Do NOT re-derive these from local runs" note,
so that warning points at a resolved cause instead of an open mystery.
-
Validate. Run all 7 live jax_grad scripts under the fixed recipe and confirm
they agree with CI. Ship as a PR; the per-PR smoke gate is the CI-side confirmation.
Explicitly out of bounds
Any of these masks the trap rather than removing it, and AGENTS.md already states
the rule (never edit a script to mask a real regression):
- moving
lp.py's evaluation point;
- adding
skip_indices to get green;
- widening a tolerance without the measured basis.
Key Files
scripts/misc/util.py — assert_eager_jit_consistent (:185),
assert_gradients_match (:157), fd_gradient (:55). Blast radius: all 8
jax_grad scripts plus scripts/weak/jax_grad.py.
scripts/imaging/jax_grad/lp.py — the control; failing guard at :213.
scripts/imaging/jax_grad/pixelization.py, scripts/imaging/jax_grad/regularization.py
— the two other local failures.
.github/scripts/smoke_install.sh — the CI install set (unpinned numpy).
config/build/profile_smoke.yaml:74 — the existing warning to cross-reference.
AGENTS.md — "Running Scripts" / "Testing"; where the local recipe lands.
Trade-off
A util.py tolerance change has repo-wide blast radius and needs the strongest
evidence; an AGENTS.md recipe has zero blast radius but only helps people who read
it. Default to documentation unless step 4 proves a real supported-range problem.
Open dependency
The local venv that produced the failures cannot be inspected from a cloud session.
If it still exists, capture its pip freeze + np.show_config() before it drifts —
otherwise step 2 has no ground truth to reproduce.
Sizing note
Brain sized this medium (declared) vs large (derived); the Bug Agent classifies it
severity=critical, scope=single-repo, type=wrong-result, confidence=low, strategy
investigate-first. Reproduce and confirm root cause before patching anything.
Original Prompt
Click to expand starting prompt
jax_grad scripts fail assertions locally that PASS in CI
Type: bug
Target: autolens_workspace_test
Repos:
- autolens_workspace_test
Difficulty: medium
Autonomy: supervised
Priority: medium
Status: formalised
Running the jax_grad scripts locally under the resolved smoke profile produces
deterministic assertion failures in scripts that pass in CI on the same commit.
Found while measuring script durations for PyAutoHands#226.
Evidence
Run via env_config.build_env_for_script with the workspace root as CWD (i.e. the
exact env the runner builds — PYAUTO_SMALL_DATASETS unset, PYAUTO_DISABLE_JAX
unset, PYAUTO_TEST_MODE=2, verified by printing the resolved env):
| script |
local |
CI (run 30858578587 / 30790463134) |
imaging/jax_grad/lp.py |
FAIL 41.3s |
PASS 39.6s / 40.0s |
imaging/jax_grad/knn.py |
PASS 141.6s |
PASS 200.0s / 175.8s |
imaging/jax_grad/pixelization.py |
FAIL 57.5s |
PASS 244.8s (06:31Z) |
imaging/jax_grad/regularization.py |
FAIL 131.5s |
(import gap, then TIMEOUT) |
point_source/jax_grad/gradient.py |
PASS 665.9s |
TIMEOUT (300s cap) |
lp.py is the decisive case: it passes in CI on both runs and fails locally.
Failures are deterministic and bit-identical across repeated runs, e.g.
pixelization.py:
AssertionError: Eager (-8354.484097835004) and jitted (-8354.55843260181) evaluations
disagree — possible pure_callback constant-folding; do not trust jitted gradients.
(relative difference ~8.9e-6 against assert_eager_jit_consistent's rtol=1e-10).
lp.py fails with All source-parameter gradients are ~zero — NNLS zeroed the source;
regularization.py with an AD-vs-FD mismatch marginally over tolerance
(abs_err=[0.045, 0.042, 0.057] vs tolerance=[0.031, 0.008, 0.003]).
What is ruled out
- Not the small-datasets cap.
full_datasets correctly unsets
PYAUTO_SMALL_DATASETS; verified by resolving the env directly rather than
inferring from mask sizes.
- Not a JAX version difference. Local jax/jaxlib are 0.10.2 — identical to CI.
- Not flake. Repeated runs give bit-identical values.
Prime remaining suspect: numpy 2.2.6 local vs 2.4.6 in CI, or another local venv
package differing from the CI install set. Not yet confirmed.
Why it matters
This is an active trap for anyone validating these scripts locally. During #226 it
looked exactly like two fresh correctness regressions on current main
(pure_callback constant-folding, and an FD tolerance breach). Only running a
control — lp.py, known-passing in CI — revealed that the local environment
itself produces the failures, so none of the three local failures were evidence of
source defects.
Whatever the cause, either the scripts or the documented local-run recipe should make
this reproducible, so a local FAIL means something.
Suggested scope
- Bisect the local-vs-CI package delta (start with numpy 2.2.6 -> 2.4.6) against
lp.py, the cleanest discriminator.
- If numpy is the cause, decide whether the tolerances are under-specified for the
supported numpy range, or the local env should be pinned to the CI set.
- Record the outcome in the workspace's local-run instructions.
Overview
Running the
jax_gradscripts locally under the resolved smoke profile producesdeterministic assertion failures in scripts that pass in CI on the same commit.
imaging/jax_grad/lp.pyis the decisive control: it passes in CI on two independentruns and fails locally, so the three local failures found during PyAutoHands#226 were
environment artefacts, not source defects.
This is an active trap for anyone validating these scripts locally — during #226 it
looked exactly like two fresh correctness regressions on current main. Whatever the
cause, either the scripts or the documented local-run recipe should make this
reproducible, so that a local FAIL means something.
Split out of PyAutoHands#226; see
PyAutoMind/complete/2026/08/jax-grad-smoke-timeout-budget.md.Plan
pip freeze+np.show_config()from the local venv, and the same from a CI job. The prompt'stable is currently the only record of the local side.
lp.pyPASSES, then change only numpy 2.4.6 -> 2.2.6 and confirm it FAILS. Runit in both directions — the upgrade recovering PASS is what rules out
install-ordering effects.
remaining delta against
lp.py, cheapest-first.lp.py's failure is anactive-set change in the positive-only NNLS solve, not a last-digit tolerance
breach — that points at the BLAS/LAPACK backend the numpy wheel ships, not at
numpy's Python layer.
supported dependency range, or the local environment is out of spec. These call for
different fixes, and only measurement chooses between them.
reproducible local-run recipe in
AGENTS.mdand cross-reference it from theexisting
profile_smoke.yamlwarning.What the code already tells us
scripts/misc/util.py:185—assert_eager_jit_consistent(..., rtol=1e-10)isdocumented as a
pure_callbackconstant-folding detector, not a numericscheck. Constant-folding produces order-unity disagreement; the observed 8.9e-6 is
~5 orders of magnitude too small to be the thing this assert exists to catch.
scripts/imaging/jax_grad/lp.py:213— the failing guard isany(|ad[source]| > 1e-3), i.e. the NNLS solve zeroed the source. The linearsolve is landing on a different active set — the same signature as the
pixelization.py8.9e-6 gap and theregularization.pymarginal FD breach..github/scripts/smoke_install.shpins nothing butjax<0.7/jaxlib<0.7— numpyis whatever the resolver picks that day. "The CI set" is not a fixed target, and
nothing in the repo tells a local runner how to reproduce it.
AGENTS.mddocumentsrun_smoke.pyandrun_all_scripts.shbut has nolocal-environment recipe at all. That gap is the trap.
config/build/profile_smoke.yaml:74already carries a "Do NOT re-derive these fromlocal runs" warning naming this exact divergence — the right place to record the
resolved cause.
Detailed implementation plan
Work Classification
Workspace (single repo) — routes to
/start_workspace.Affected Repositories
autolens_workspace_test(primary)Branch Survey
PyAutoLabs/autolens_workspace_testmain@b18fd38worktree_check_conflict jax-grad-local-vs-ci-assertions autolens_workspace_test-> exit 0, no conflict.
Warning (not a block):
feature/jax-grad-param9-mismatchandclaude/rectangular-mesh-gradients-mh1j0zexist on the remote as unregisteredprior
jax_gradwork.Suggested branch:
feature/jax-grad-local-vs-ci-assertionsWorktree root:
~/Code/PyAutoLabs-wt/jax-grad-local-vs-ci-assertions/(created later by
/start_workspace)Implementation Steps
Capture ground truth. From the local machine:
pip freeze > local.txtandpython -c "import numpy; numpy.show_config()". From CI: the same two, pulledfrom a fresh
mainsmoke run's job log, or reproduced by running.github/scripts/smoke_install.shagainst fresh chain checkouts. Record jax,jaxlib, numpy, scipy and tfp-nightly explicitly.
The A/B. In an isolated venv holding the CI set, run
scripts/imaging/jax_grad/lp.pyviaenv_config.build_env_for_scriptwith theworkspace root as cwd (the exact env the report used) -> expect PASS.
pip install numpy==2.2.6alone -> rerun -> expect theAll source-parameter gradients are ~zerofailure. Thenpip install numpy==2.4.6-> rerun -> expect PASS to return.
lp.pyis the control because it is the onlyscript known to pass in CI and fail locally, and at ~41s it is the cheapest.
Widen if needed.
diff local.txt ci.txt; bisect the remaining deltapackage-by-package against
lp.py.Mechanism. Compare
np.show_config()across the two numpy versions (OpenBLASbuild and threading). Check whether the inversion's non-negative solve (the fnnls
active set) selects a different set under each, by printing the active-set size at
the
lp.pyevaluation point in both. This is what distinguishes "float noise"from "a different solution" — the evidence says it is the latter.
The fix, chosen by the measurement:
rtol=1e-10on
assert_eager_jit_consistentis the outlier and the only tolerance worthchanging — raised to a value that still detects constant-folding (which is
order-unity), with the measured spread written in as its justification. Leave
assert_gradients_match(rtol=1e-3/atol=1e-4) alone unlessregularization.py's breach survives step 4."Local runs of
jax_grad/" subsection toAGENTS.mdwith the exact install(mirroring
smoke_install.shplus the resolved pins), thebuild_env_for_scriptinvocation, and the cwd requirement.
config/build/profile_smoke.yaml"Do NOT re-derive these from local runs" note,so that warning points at a resolved cause instead of an open mystery.
Validate. Run all 7 live
jax_gradscripts under the fixed recipe and confirmthey agree with CI. Ship as a PR; the per-PR smoke gate is the CI-side confirmation.
Explicitly out of bounds
Any of these masks the trap rather than removing it, and
AGENTS.mdalready statesthe rule (never edit a script to mask a real regression):
lp.py's evaluation point;skip_indicesto get green;Key Files
scripts/misc/util.py—assert_eager_jit_consistent(:185),assert_gradients_match(:157),fd_gradient(:55). Blast radius: all 8jax_gradscripts plusscripts/weak/jax_grad.py.scripts/imaging/jax_grad/lp.py— the control; failing guard at :213.scripts/imaging/jax_grad/pixelization.py,scripts/imaging/jax_grad/regularization.py— the two other local failures.
.github/scripts/smoke_install.sh— the CI install set (unpinned numpy).config/build/profile_smoke.yaml:74— the existing warning to cross-reference.AGENTS.md— "Running Scripts" / "Testing"; where the local recipe lands.Trade-off
A
util.pytolerance change has repo-wide blast radius and needs the strongestevidence; an
AGENTS.mdrecipe has zero blast radius but only helps people who readit. Default to documentation unless step 4 proves a real supported-range problem.
Open dependency
The local venv that produced the failures cannot be inspected from a cloud session.
If it still exists, capture its
pip freeze+np.show_config()before it drifts —otherwise step 2 has no ground truth to reproduce.
Sizing note
Brain sized this
medium(declared) vslarge(derived); the Bug Agent classifies itseverity=critical, scope=single-repo, type=wrong-result, confidence=low, strategy
investigate-first. Reproduce and confirm root cause before patching anything.
Original Prompt
Click to expand starting prompt
jax_grad scripts fail assertions locally that PASS in CI
Type: bug
Target: autolens_workspace_test
Repos:
Difficulty: medium
Autonomy: supervised
Priority: medium
Status: formalised
Running the
jax_gradscripts locally under the resolved smoke profile producesdeterministic assertion failures in scripts that pass in CI on the same commit.
Found while measuring script durations for PyAutoHands#226.
Evidence
Run via
env_config.build_env_for_scriptwith the workspace root as CWD (i.e. theexact env the runner builds —
PYAUTO_SMALL_DATASETSunset,PYAUTO_DISABLE_JAXunset,
PYAUTO_TEST_MODE=2, verified by printing the resolved env):imaging/jax_grad/lp.pyimaging/jax_grad/knn.pyimaging/jax_grad/pixelization.pyimaging/jax_grad/regularization.pypoint_source/jax_grad/gradient.pylp.pyis the decisive case: it passes in CI on both runs and fails locally.Failures are deterministic and bit-identical across repeated runs, e.g.
pixelization.py:(relative difference ~8.9e-6 against
assert_eager_jit_consistent'srtol=1e-10).lp.pyfails withAll source-parameter gradients are ~zero — NNLS zeroed the source;regularization.pywith an AD-vs-FD mismatch marginally over tolerance(
abs_err=[0.045, 0.042, 0.057]vstolerance=[0.031, 0.008, 0.003]).What is ruled out
full_datasetscorrectly unsetsPYAUTO_SMALL_DATASETS; verified by resolving the env directly rather thaninferring from mask sizes.
Prime remaining suspect: numpy 2.2.6 local vs 2.4.6 in CI, or another local venv
package differing from the CI install set. Not yet confirmed.
Why it matters
This is an active trap for anyone validating these scripts locally. During #226 it
looked exactly like two fresh correctness regressions on current main
(
pure_callbackconstant-folding, and an FD tolerance breach). Only running acontrol —
lp.py, known-passing in CI — revealed that the local environmentitself produces the failures, so none of the three local failures were evidence of
source defects.
Whatever the cause, either the scripts or the documented local-run recipe should make
this reproducible, so a local FAIL means something.
Suggested scope
lp.py, the cleanest discriminator.supported numpy range, or the local env should be pinned to the CI set.