|
| 1 | +# pre_build stages untracked files, publishing uncommitted human work |
| 2 | + |
| 3 | +Type: bug |
| 4 | +Target: PyAutoHands |
| 5 | +Repos: |
| 6 | +- PyAutoHands |
| 7 | +Difficulty: small |
| 8 | +Autonomy: supervised |
| 9 | +Priority: high |
| 10 | +Status: formalised |
| 11 | + |
| 12 | +`pre_build.sh`'s `run_workspace` runs, for each of 13 workspace repos: |
| 13 | + |
| 14 | +```bash |
| 15 | +for d in scripts slam_pipeline; do black "$d/"; done |
| 16 | +... |
| 17 | +for d in notebooks scripts; do git add "$d/"; done |
| 18 | +``` |
| 19 | + |
| 20 | +Both operations reach **untracked** files. `git add <dir>/` stages them, so any |
| 21 | +uncommitted human work under `scripts/` or `notebooks/` is reformatted by black |
| 22 | +and pushed inside the `"pre build"` commit — to a public repo, with no prompt |
| 23 | +and exit status 0. |
| 24 | + |
| 25 | +This is the same leak class as PyAutoBuild#126, which was fixed for `dataset/` |
| 26 | +and `config/` by deleting their staging lines (#156). The `scripts/` path kept |
| 27 | +the hole; the comment above the staging block asserts "Releases require clean |
| 28 | +mains (Heart gates on it)" but nothing in the script enforces it for the 13 |
| 29 | +repos it actually commits to — the clean-main gate at the top covers PyAutoHands |
| 30 | +alone. |
| 31 | + |
| 32 | +## How it surfaced |
| 33 | + |
| 34 | +A near-miss during the 2026-08-07 release drive (see `active.md` |
| 35 | +→ `release-drive-2026-08-07`, `pre_build-trap`): an uncommitted WIP script in |
| 36 | +`autolens_assistant/scripts/` would have been reformatted and published. It was |
| 37 | +caught only because the operator noticed and moved the file out of the repo by |
| 38 | +hand, restoring it afterwards and verifying it byte-identical by md5. The record |
| 39 | +notes it is "worth a real fix so it is not left to operator vigilance." |
| 40 | + |
| 41 | +Reproduced against the pre-fix script on throwaway fixture repos: the private |
| 42 | +file was committed as `"pre build"` and pushed to the remote, exit 0, silently. |
| 43 | + |
| 44 | +## The fix |
| 45 | + |
| 46 | +1. **Fail-fast preflight over every repo, before the first is touched.** |
| 47 | + `run_workspace` commits *and pushes* each repo before moving to the next, so |
| 48 | + a per-repo check aborting midway would leave earlier repos already |
| 49 | + published. This also answers the open atomicity question in |
| 50 | + `docs/pre_build_failure_audit.md` §6 ("worth a fail-fast pre-pass?"). |
| 51 | + Uses `git ls-files --others --exclude-standard`, which honours `.gitignore` |
| 52 | + and tolerates pathspecs matching nothing. |
| 53 | +2. **Narrow the staging** to `git add -u` (tracked edits and deletions) plus |
| 54 | + newly created files added by explicit path, so the directory-wide form |
| 55 | + cannot return. New notebooks from `generate.py` must still be staged — that |
| 56 | + is why plain `git add -u` alone is insufficient. |
| 57 | +3. The repo list moves from `run_workspace "..."` call lines into a |
| 58 | + `WORKSPACE_SPECS` array, because two passes now read it and a second |
| 59 | + hand-maintained list would drift. |
| 60 | + |
| 61 | +No `--allow-dirty` override: an override is exactly the operator vigilance the |
| 62 | +change replaces. |
| 63 | + |
| 64 | +## Verification |
| 65 | + |
| 66 | +Text assertions cannot prove a gate fires, so `tests/test_pre_build_staging.py` |
| 67 | +runs the real script against a throwaway `PYAUTOBASE` of fixture git repos with |
| 68 | +`black`/`python`/`gh` stubbed and real bare remotes — covering the WIP abort, |
| 69 | +multi-repo reporting, gitignored files not blocking a release, new generated |
| 70 | +notebooks still being staged, tracked deletions, and the missing-checkout abort. |
| 71 | + |
| 72 | +## Related |
| 73 | + |
| 74 | +`draft/bug/pyautobuild/root_level_git_add_stages_nothing_on_unmatched_glob.md` |
| 75 | +is **obsolete** — the root-level glob `git add` line it describes no longer |
| 76 | +exists; #156 deleted it as a measured no-op in all 13 repos. Retired to |
| 77 | +`complete/archive/shelved/` alongside this task. |
| 78 | + |
| 79 | +<!-- filed 2026-08-08 from the release-drive-2026-08-07 pre_build-trap record --> |
0 commit comments