Overview
.script_sizes.json is the truncation guard described in AGENTS.md ("Bulk-edit safety"): it records the byte size of every scripts/**/*.py, and scripts/check_sizes.sh flags any script that shrank >50% since the snapshot. It exists because of a real incident where a header-insert pass replaced ~80% of 17 scripts with the header alone.
The guard's logic is sound; its baseline mechanism rots. Nothing enforces the "run --update in the same diff" contract, and because the guard stays green while drifting, nobody notices. This issue replaces the static snapshot with git itself — which is never stale — in both autolens_workspace and autogalaxy_workspace (byte-identical check_sizes.sh, same rot).
Measured drift
At autolens_workspace@d45cdca5 (2026-08-20), and — for comparison — the numbers in the original prompt at 726d060d (2026-07-30):
| Condition |
Jul 30 |
Aug 20 |
| Entries in snapshot |
373 |
418 |
| Recorded size != actual |
116 |
212 |
| Keys naming nonexistent files |
3 |
0 |
| Scripts on disk with no baseline |
12 |
39 |
| Entries that would trip the guard |
0 |
0 |
autogalaxy_workspace@main: 174 entries, 179 scripts on disk, 81 stale, 0 dead keys, 5 unsnapshotted.
Two corrections to the original framing
- The 212 stale sizes are almost entirely harmless. A stale baseline only weakens the guard when the file has grown — a grown file's old, smaller baseline raises the bar a truncation must fall under. Measured across the repo, the worst case degrades detection from "catches truncation below 50% of current" to "below 34%" (
scripts/guides/point_source_pairing.py, 17021b -> 24849b). No snapshotted script drops below 25%. "212 wrong baselines" sounds alarming but is not where the harm is.
- The 39 unsnapshotted scripts are the entire real hole — zero protection, and the count grew 12 -> 39 in three weeks. The 3 dead keys self-healed via an incidental
--update in a docs commit (1bd0d3f3, 2026-08-03), which is also why the entry count rose while staleness got worse.
This independently vindicates the original prompt's instinct to gate on structural drift rather than size equality: sizes are supposed to drift. The chosen fix goes further and removes the baseline entirely.
Plan
- Replace the static snapshot with a git-diff comparison: check each changed
scripts/**/*.py against its size at HEAD (local, pre-commit) or the PR merge-base (CI).
- Delete
.script_sizes.json from both workspaces.
- Delete the "
--update in the same diff" instruction from AGENTS.md — the contract nobody follows, and the direct cause of the rot.
- Add an advisory CI workflow to both repos so a truncation is caught on the PR that introduces it.
- Net effect: new scripts are protected the day they land; there is no "unsnapshotted" state to fall into, and no file to refresh.
Detailed implementation plan
Affected Repositories
autolens_workspace (primary)
autogalaxy_workspace — identical check_sizes.sh (verified byte-identical via diff), same drift
Branch Survey
| Repository |
Current Branch |
Dirty? |
| ./autolens_workspace |
main |
clean |
| ./autogalaxy_workspace |
main |
clean |
worktree_check_conflict script-sizes-snapshot-drift autolens_workspace autogalaxy_workspace -> exit 0 (no claims).
Note: autolens_workspace has an unregistered branch feature/point-source-campaign-evidence-tail checked out in another worktree — unrelated to these files, not a conflict.
Suggested branch: feature/script-size-guard-git-based
Implementation Steps
-
Rewrite scripts/check_sizes.sh (keep byte-identical across both repos).
- Usage: bare = working tree vs
HEAD; --base <rev> = explicit base. --update is removed.
- Per changed
.py: prev = git cat-file -s <base>:<path>; cur = os.path.getsize(path).
- Skip when: path absent in base (new file — nothing to truncate);
prev <= 200 (unchanged threshold); path deleted on disk (a deletion is explicit, not truncation).
- Flag when
cur < prev * 0.5, printing pct / path / prev -> cur (same output shape as today).
- Fail closed: an unresolvable
<base> must print a clear error and exit 2 — never a silent pass. (Found during prototyping: HEAD~300 on autogalaxy_workspace is beyond its 266-deep first-parent chain and raised a raw traceback.)
- Retain
ALLOW_SHRINK=1 -> warn, exit 0.
-
Delete .script_sizes.json in both repos (26KB of churn-y JSON in autolens).
-
Add .github/workflows/script_size_guard.yml to both repos.
pull_request: base = git merge-base HEAD origin/${{ github.base_ref }}.
push: [main]: base = github.event.before, falling back to HEAD^ when it is all-zeros/unresolvable.
actions/checkout with fetch-depth: 0 — merge-base needs history.
concurrency block mirroring navigator_check.yml / smoke_tests.yml: cancel-in-progress on PR refs only (a cancelled main run reads as red CI to Heart).
- Deliberately NOT added to
repos.yaml -> required_workflows. That key is group-wide: workspaces requires exactly ["Smoke Tests", "Navigator Check"], and the other five workspace repos have no size guard — adding it would make them all report a missing required workflow. The guard stays advisory. (See PyAutoHeart/heart/checks/ci_status.py, DEFAULT_REQUIRED_WORKFLOWS.)
-
Update AGENTS.md "Bulk-edit safety" in both repos — replace the snapshot paragraph with the new one-command contract. Keep the incident rationale and the "never produce a whole-file write unless you have read the entire current contents" rule; drop the --update refresh instruction.
Verification
- Six controls, all green in a throwaway repo during planning — re-run in-repo after implementing:
- clean tree -> OK, exit 0
- file truncated to header only ->
99% ... 5409b -> 9b, exit 1
- brand-new file with no base blob -> OK, exit 0
ALLOW_SHRINK=1 on a truncation -> warns, exit 0
- legitimate -10% edit -> OK, exit 0
- truncation committed two commits back on a branch -> still caught via merge-base, exit 1
- False-positive replay on real history (already measured, zero false positives):
autolens_workspace HEAD~25 / ~100 / 300 = 114 / 366 / 402 changed scripts, all within tolerance; autogalaxy_workspace HEAD25 / ~100 = 38 / 150, all within tolerance.
- CI positive control (required): push a deliberate truncation commit to the PR branch, confirm the new workflow reddens, then revert. A guard never observed failing is not a proven guard.
- Confirm the 39 (autolens) + 5 (autogalaxy) previously unprotected scripts are covered — by construction every changed script is checked, so the count is zero.
Trade-off (stated deliberately)
The git guard checks only changed scripts, not all 457. That is the correct scope for a truncation guard — an untouched file cannot be truncated — and it is exactly why it needs no baseline and cannot rot. What it gives up is the snapshot's nominal whole-repo audit, which today covers 418 of 457 scripts with 212 wrong numbers and 39 missing.
Control 6 shows the new guard is strictly stronger than the snapshot on the real threat model: it catches a truncation made earlier on a branch regardless of when any baseline was last refreshed.
Key Files
scripts/check_sizes.sh — rewritten; snapshot logic replaced by git blob sizes (both repos)
.script_sizes.json — deleted (both repos)
.github/workflows/script_size_guard.yml — new, advisory (both repos)
AGENTS.md — "Bulk-edit safety" section updated (both repos)
PyAutoHeart/heart/checks/ci_status.py — read-only reference for the required-workflows constraint; not modified
Do NOT bundle a snapshot refresh into a feature PR
Retained from the original prompt, and the reason this was filed separately: running --update inside a scoped PR sweeps hundreds of unrelated entries into that diff and silently blesses other changes' shrinkage. Discovered while shipping the DSPL rename (#394). This issue removes the --update path entirely, so the trap goes away rather than being documented.
Original Prompt
Click to expand starting prompt
Refresh the stale .script_sizes.json snapshot in @autolens_workspace
Difficulty: small
Autonomy: safe
Priority: low
The problem
.script_sizes.json is the truncation guard described in
autolens_workspace/AGENTS.md: it records the byte size of every
scripts/**/*.py, and scripts/check_sizes.sh flags any script that shrank by
50% since the snapshot. It exists because of a real incident where a
header-insert pass replaced ~80% of 17 scripts with the header alone.
The snapshot has drifted badly enough that the guard now protects almost
nothing. Measured on main at 726d060d (2026-07-30):
| Condition |
Count |
| Entries in snapshot |
373 |
| Entries whose recorded size ≠ actual size |
116 |
| Keys naming files that no longer exist |
3 |
| Scripts on disk absent from the snapshot entirely |
12 |
| Entries that would currently trip the >50% shrink guard |
0 |
That last row is the point. check_sizes.sh reports "OK: all scripts within
size tolerance" today, so the drift is invisible — but 116 baselines are wrong
and 12 scripts have no baseline at all, so a genuine truncation in any of them
would be measured against a stale or absent reference.
The 3 dead keys
scripts/imaging/features/potential_correction/likelihood_function.py
scripts/interferometer/features/potential_correction/likelihood_function.py
scripts/interferometer/features/potential_correction/start_here.py
These are leftovers from the features/potential_correction/ →
features/advanced/potential_correction/ move.
The 12 unsnapshotted scripts
Mostly recent multi_galaxy work — e.g. scripts/multi_galaxy/source_science.py,
likelihood_function.py, simulator_sample.py,
features/extra_galaxies/{modeling,simulator}.py.
Why it drifted
AGENTS.md asks contributors to run scripts/check_sizes.sh --update in the
same diff as an intentional shrink. Nothing enforces it, and because the guard
stays green while drifting, nobody notices.
Proposed fix
- Run
scripts/check_sizes.sh --update on clean main as a single dedicated
commit that touches nothing else, so the refresh is reviewable and is not
entangled with a feature diff.
- Before committing, sanity-check that no entry shrank drastically —
a >50% drop in the refresh itself would mean a real truncation is being
blessed rather than recorded. Diff the before/after and eyeball the large
negative deltas.
- Consider a CI guard so this cannot silently rot again: fail if any
scripts/**/*.py is missing from the snapshot, or if any key names a
nonexistent file. That catches the two structural drifts (dead keys,
unsnapshotted scripts) cheaply, without the false-positive risk of gating on
exact size equality — sizes legitimately change on every prose edit.
Step 3 is the part worth debating; steps 1–2 are mechanical.
Do NOT bundle this into a feature PR
This is exactly why it is filed separately. Running --update inside a scoped
PR sweeps all 116 unrelated entries into that diff and silently blesses other
changes' shrinkage. Discovered while shipping the DSPL rename
(autolens_workspace#394), where the 18 affected entries were instead updated by
hand for this reason.
Verification
check_sizes.sh green (it already is — that is not sufficient evidence)
- Recompute the four counts above: stale = 0, dead keys = 0, unsnapshotted = 0
- If step 3 lands, prove the new CI guard is not vacuous with a positive control
(delete an entry, confirm CI reddens)
Overview
.script_sizes.jsonis the truncation guard described inAGENTS.md("Bulk-edit safety"): it records the byte size of everyscripts/**/*.py, andscripts/check_sizes.shflags any script that shrank >50% since the snapshot. It exists because of a real incident where a header-insert pass replaced ~80% of 17 scripts with the header alone.The guard's logic is sound; its baseline mechanism rots. Nothing enforces the "run
--updatein the same diff" contract, and because the guard stays green while drifting, nobody notices. This issue replaces the static snapshot with git itself — which is never stale — in bothautolens_workspaceandautogalaxy_workspace(byte-identicalcheck_sizes.sh, same rot).Measured drift
At
autolens_workspace@d45cdca5(2026-08-20), and — for comparison — the numbers in the original prompt at726d060d(2026-07-30):autogalaxy_workspace@main: 174 entries, 179 scripts on disk, 81 stale, 0 dead keys, 5 unsnapshotted.Two corrections to the original framing
scripts/guides/point_source_pairing.py, 17021b -> 24849b). No snapshotted script drops below 25%. "212 wrong baselines" sounds alarming but is not where the harm is.--updatein a docs commit (1bd0d3f3, 2026-08-03), which is also why the entry count rose while staleness got worse.This independently vindicates the original prompt's instinct to gate on structural drift rather than size equality: sizes are supposed to drift. The chosen fix goes further and removes the baseline entirely.
Plan
scripts/**/*.pyagainst its size atHEAD(local, pre-commit) or the PR merge-base (CI)..script_sizes.jsonfrom both workspaces.--updatein the same diff" instruction fromAGENTS.md— the contract nobody follows, and the direct cause of the rot.Detailed implementation plan
Affected Repositories
autolens_workspace(primary)autogalaxy_workspace— identicalcheck_sizes.sh(verified byte-identical viadiff), same driftBranch Survey
worktree_check_conflict script-sizes-snapshot-drift autolens_workspace autogalaxy_workspace-> exit 0 (no claims).Note:
autolens_workspacehas an unregistered branchfeature/point-source-campaign-evidence-tailchecked out in another worktree — unrelated to these files, not a conflict.Suggested branch:
feature/script-size-guard-git-basedImplementation Steps
Rewrite
scripts/check_sizes.sh(keep byte-identical across both repos).HEAD;--base <rev>= explicit base.--updateis removed..py:prev = git cat-file -s <base>:<path>;cur = os.path.getsize(path).prev <= 200(unchanged threshold); path deleted on disk (a deletion is explicit, not truncation).cur < prev * 0.5, printingpct / path / prev -> cur(same output shape as today).<base>must print a clear error and exit 2 — never a silent pass. (Found during prototyping:HEAD~300onautogalaxy_workspaceis beyond its 266-deep first-parent chain and raised a raw traceback.)ALLOW_SHRINK=1-> warn, exit 0.Delete
.script_sizes.jsonin both repos (26KB of churn-y JSON in autolens).Add
.github/workflows/script_size_guard.ymlto both repos.pull_request: base =git merge-base HEAD origin/${{ github.base_ref }}.push: [main]: base =github.event.before, falling back toHEAD^when it is all-zeros/unresolvable.actions/checkoutwithfetch-depth: 0— merge-base needs history.concurrencyblock mirroringnavigator_check.yml/smoke_tests.yml: cancel-in-progress on PR refs only (a cancelled main run reads as red CI to Heart).repos.yaml -> required_workflows. That key is group-wide:workspacesrequires exactly["Smoke Tests", "Navigator Check"], and the other five workspace repos have no size guard — adding it would make them all report a missing required workflow. The guard stays advisory. (SeePyAutoHeart/heart/checks/ci_status.py,DEFAULT_REQUIRED_WORKFLOWS.)Update
AGENTS.md"Bulk-edit safety" in both repos — replace the snapshot paragraph with the new one-command contract. Keep the incident rationale and the "never produce a whole-file write unless you have read the entire current contents" rule; drop the--updaterefresh instruction.Verification
99% ... 5409b -> 9b, exit 1ALLOW_SHRINK=1on a truncation -> warns, exit 0autolens_workspaceHEAD~25 / ~100 /300 = 114 / 366 / 402 changed scripts, all within tolerance;25 / ~100 = 38 / 150, all within tolerance.autogalaxy_workspaceHEADTrade-off (stated deliberately)
The git guard checks only changed scripts, not all 457. That is the correct scope for a truncation guard — an untouched file cannot be truncated — and it is exactly why it needs no baseline and cannot rot. What it gives up is the snapshot's nominal whole-repo audit, which today covers 418 of 457 scripts with 212 wrong numbers and 39 missing.
Control 6 shows the new guard is strictly stronger than the snapshot on the real threat model: it catches a truncation made earlier on a branch regardless of when any baseline was last refreshed.
Key Files
scripts/check_sizes.sh— rewritten; snapshot logic replaced by git blob sizes (both repos).script_sizes.json— deleted (both repos).github/workflows/script_size_guard.yml— new, advisory (both repos)AGENTS.md— "Bulk-edit safety" section updated (both repos)PyAutoHeart/heart/checks/ci_status.py— read-only reference for the required-workflows constraint; not modifiedDo NOT bundle a snapshot refresh into a feature PR
Retained from the original prompt, and the reason this was filed separately: running
--updateinside a scoped PR sweeps hundreds of unrelated entries into that diff and silently blesses other changes' shrinkage. Discovered while shipping the DSPL rename (#394). This issue removes the--updatepath entirely, so the trap goes away rather than being documented.Original Prompt
Click to expand starting prompt
Refresh the stale
.script_sizes.jsonsnapshot in @autolens_workspaceDifficulty: small
Autonomy: safe
Priority: low
The problem
.script_sizes.jsonis the truncation guard described inautolens_workspace/AGENTS.md: it records the byte size of everyscripts/**/*.py, andscripts/check_sizes.shflags any script that shrank byThe snapshot has drifted badly enough that the guard now protects almost
nothing. Measured on
mainat726d060d(2026-07-30):That last row is the point.
check_sizes.shreports "OK: all scripts withinsize tolerance" today, so the drift is invisible — but 116 baselines are wrong
and 12 scripts have no baseline at all, so a genuine truncation in any of them
would be measured against a stale or absent reference.
The 3 dead keys
These are leftovers from the
features/potential_correction/→features/advanced/potential_correction/move.The 12 unsnapshotted scripts
Mostly recent
multi_galaxywork — e.g.scripts/multi_galaxy/source_science.py,likelihood_function.py,simulator_sample.py,features/extra_galaxies/{modeling,simulator}.py.Why it drifted
AGENTS.mdasks contributors to runscripts/check_sizes.sh --updatein thesame diff as an intentional shrink. Nothing enforces it, and because the guard
stays green while drifting, nobody notices.
Proposed fix
scripts/check_sizes.sh --updateon cleanmainas a single dedicatedcommit that touches nothing else, so the refresh is reviewable and is not
entangled with a feature diff.
a >50% drop in the refresh itself would mean a real truncation is being
blessed rather than recorded. Diff the before/after and eyeball the large
negative deltas.
scripts/**/*.pyis missing from the snapshot, or if any key names anonexistent file. That catches the two structural drifts (dead keys,
unsnapshotted scripts) cheaply, without the false-positive risk of gating on
exact size equality — sizes legitimately change on every prose edit.
Step 3 is the part worth debating; steps 1–2 are mechanical.
Do NOT bundle this into a feature PR
This is exactly why it is filed separately. Running
--updateinside a scopedPR sweeps all 116 unrelated entries into that diff and silently blesses other
changes' shrinkage. Discovered while shipping the DSPL rename
(autolens_workspace#394), where the 18 affected entries were instead updated by
hand for this reason.
Verification
check_sizes.shgreen (it already is — that is not sufficient evidence)(delete an entry, confirm CI reddens)