Skip to content

refactor: replace .script_sizes.json with a git-diff truncation guard #490

Description

@Jammy2211

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

  1. 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.
  2. 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

  1. 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.
  2. Delete .script_sizes.json in both repos (26KB of churn-y JSON in autolens).

  3. 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.)
  4. 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:
    1. clean tree -> OK, exit 0
    2. file truncated to header only -> 99% ... 5409b -> 9b, exit 1
    3. brand-new file with no base blob -> OK, exit 0
    4. ALLOW_SHRINK=1 on a truncation -> warns, exit 0
    5. legitimate -10% edit -> OK, exit 0
    6. 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

  1. 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.
  2. 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.
  3. 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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions