pre_build.sh's run_workspace runs, for each of the 13 workspace repos:
for d in scripts slam_pipeline; do black "$d/"; done
...
for d in notebooks scripts; do git add "$d/"; done
Both operations reach untracked files. git add <dir>/ stages them, so any uncommitted work under a workspace's scripts/ or notebooks/ is reformatted by black and pushed inside the "pre build" commit — to a public repo, with no prompt and exit status 0.
This is the same leak class as #126, which was fixed for dataset/ and config/ by deleting their staging lines (#156). The scripts/ path kept the hole. The comment above the staging block asserts "Releases require clean mains (Heart gates on it)", but nothing in the script enforces that for the 13 repos it actually commits to — the clean-main gate at the top covers PyAutoHands alone.
How it surfaced
A near-miss during the 2026-08-07 release drive: an uncommitted WIP script in autolens_assistant/scripts/ would have been reformatted and published. It was caught only because the operator noticed and moved the file out of the repo by hand, restoring it afterwards and verifying it byte-identical by md5.
Reproduced against the unmodified script on throwaway fixture repos with real bare remotes: the private file was committed as "pre build" and pushed to the remote, exit 0, silently.
Fix
-
Fail-fast preflight over every repo, before the first is touched. run_workspace commits and pushes each repo before moving to the next, so a per-repo check aborting midway would leave earlier repos already published. Uses git ls-files --others --exclude-standard, which honours .gitignore and tolerates pathspecs matching nothing. Also aborts on a missing checkout, which previously surfaced as a bare cd error partway through a run.
This answers the open atomicity question in docs/pre_build_failure_audit.md §6 — "worth a fail-fast pre-pass (all repos validated before any push)?", costed there as a follow-up.
-
Narrow the staging to git add -u (tracked edits and deletions) plus files created by the run, added by explicit path — so the directory-wide form cannot return. New notebooks from generate.py must still be staged, which is why git add -u alone is insufficient.
-
The repo list moves from run_workspace "..." call lines into a WORKSPACE_SPECS array, because two passes now read it and a second hand-maintained list would drift.
No --allow-dirty override: an override is precisely the operator vigilance this replaces.
Verification
Text assertions cannot prove a gate fires, so tests/test_pre_build_staging.py runs the real script against a throwaway PYAUTOBASE of fixture git repos with black/python/gh stubbed and real bare remotes — covering the WIP abort, multi-repo reporting, gitignored files not blocking a release, new generated notebooks still being staged, tracked deletions, and the missing-checkout abort.
Related
The filed prompt draft/bug/pyautobuild/root_level_git_add_stages_nothing_on_unmatched_glob.md in PyAutoMind is obsolete — the root-level glob git add line it describes no longer exists, having been deleted outright by #156 as a measured no-op in all 13 repos. Retired to complete/archive/shelved/ rather than left to be worked.
pre_build.sh'srun_workspaceruns, for each of the 13 workspace repos:Both operations reach untracked files.
git add <dir>/stages them, so any uncommitted work under a workspace'sscripts/ornotebooks/is reformatted by black and pushed inside the"pre build"commit — to a public repo, with no prompt and exit status 0.This is the same leak class as #126, which was fixed for
dataset/andconfig/by deleting their staging lines (#156). Thescripts/path kept the hole. The comment above the staging block asserts "Releases require clean mains (Heart gates on it)", but nothing in the script enforces that for the 13 repos it actually commits to — the clean-main gate at the top covers PyAutoHands alone.How it surfaced
A near-miss during the 2026-08-07 release drive: an uncommitted WIP script in
autolens_assistant/scripts/would have been reformatted and published. It was caught only because the operator noticed and moved the file out of the repo by hand, restoring it afterwards and verifying it byte-identical by md5.Reproduced against the unmodified script on throwaway fixture repos with real bare remotes: the private file was committed as
"pre build"and pushed to the remote, exit 0, silently.Fix
Fail-fast preflight over every repo, before the first is touched.
run_workspacecommits and pushes each repo before moving to the next, so a per-repo check aborting midway would leave earlier repos already published. Usesgit ls-files --others --exclude-standard, which honours.gitignoreand tolerates pathspecs matching nothing. Also aborts on a missing checkout, which previously surfaced as a barecderror partway through a run.This answers the open atomicity question in
docs/pre_build_failure_audit.md§6 — "worth a fail-fast pre-pass (all repos validated before any push)?", costed there as a follow-up.Narrow the staging to
git add -u(tracked edits and deletions) plus files created by the run, added by explicit path — so the directory-wide form cannot return. New notebooks fromgenerate.pymust still be staged, which is whygit add -ualone is insufficient.The repo list moves from
run_workspace "..."call lines into aWORKSPACE_SPECSarray, because two passes now read it and a second hand-maintained list would drift.No
--allow-dirtyoverride: an override is precisely the operator vigilance this replaces.Verification
Text assertions cannot prove a gate fires, so
tests/test_pre_build_staging.pyruns the real script against a throwawayPYAUTOBASEof fixture git repos withblack/python/ghstubbed and real bare remotes — covering the WIP abort, multi-repo reporting, gitignored files not blocking a release, new generated notebooks still being staged, tracked deletions, and the missing-checkout abort.Related
The filed prompt
draft/bug/pyautobuild/root_level_git_add_stages_nothing_on_unmatched_glob.mdin PyAutoMind is obsolete — the root-level globgit addline it describes no longer exists, having been deleted outright by #156 as a measured no-op in all 13 repos. Retired tocomplete/archive/shelved/rather than left to be worked.