Repository navigation
Description
Overview
The 2026-08-27 mobile-workflow pass shipped two things half-rolled-out, because the session that shipped them could attach only PyAutoMind and PyAutoBrain. Heart and Hands still carry hand-written remote-session text that is a pass behind (it names No module named pytest as the trigger — a symptom the canonical text records as having stopped appearing when the image moved to 3.12). And repair_uv_tools — the fix for uv tool envs that resolve into the session venv and die with ModuleNotFoundError naming themselves — sits in scripts/session_bootstrap.sh, which a single-repo session never calls; the hook is what that session runs.
Both halves landed in be0121ad and 33c81de6 (2026-08-27 18:14 UTC). Nothing has touched scripts/, policy/ or tests/ since, and there are no open PRs on the four organs.
Plan
- Add the
repos_sync:remotemarker pair toPyAutoHeart/AGENTS.mdandPyAutoHands/AGENTS.md, replacing their hand-written "Remote sessions" sections, so all four organs take the block from the generated source. - Move
repair_uv_toolsout of the bootstrap and intopolicy/session_start_hook.sh, besideretool_uv_toolswhich creates the condition — so the repair reaches a single-repo session, which runs the hook and never the bootstrap. - Harden the moved function for the hook's
set -euo pipefail(the bootstrap runsset -uonly, and two of its command substitutions would abort the hook as written). - Regenerate the four organs' hook copies, keep the bootstrap's seam and its
--checkleg working, and add a hook-level version of the uv-repair test. - File a separate Mind prompt for the 30-repo hook-propagation backlog this task deliberately leaves (see "Scope" below).
Scope: the prompt's --check clause cannot be met, and why
The prompt says the hook "is generated into all four organs". It is generated into 34 repos — write_session_hooks iterates the whole manifest, not just organs. repos_sync.py --check is already red before this task touches anything:
check session-start hooks (generated): 34 mismatch(es)
✗ 'PyAutoMemory' … 'PyAutoFit' … 'autolens_workspace' … (30 stale copies)
✗ 'euclid_assistant': no .claude/hooks/session-start.sh
✗ 'admin_jammy': no .claude/hooks/session-start.sh
The four organs are clean; every other repo is a hook-pass behind, because the 2026-08-26 regeneration wave (PyAutoHeart 701b578, PyAutoHands d78ea54) also only saw four repos. No gate catches this: firewall_gate.yml checks out exactly Mind + Brain + Heart + Hands and skips absent repos, so the 30 stale copies are invisible to CI and visible only to a full local-workspace --check.
So this task regenerates the four organs — the set CI actually gates — and the acceptance bar becomes --check --root <four-organ worktree root> is clean. The 30-repo propagation is filed separately rather than folded in, since it would mean PRs against every library, workspace, HowTo and assistant repo.
Detailed implementation plan
Affected Repositories
- PyAutoMind (primary) — canonical hook, bootstrap, policy, tests
- PyAutoBrain — regenerated hook copy only
- PyAutoHeart — AGENTS.md markers + regenerated hook copy
- PyAutoHands — AGENTS.md markers + regenerated hook copy
PyAutoBrain is not listed in the prompt but must be claimed: it carries a generated hook copy that goes stale the moment the canonical hook changes, and firewall_gate.yml checks it.
Branch Survey
| Repository | Current Branch | Dirty? |
|---|---|---|
| ./PyAutoMind | main | clean |
| ./PyAutoBrain | main | clean |
| ./PyAutoHeart | main | clean |
| ./PyAutoHands | main | clean |
All four in sync with origin/main; worktree_check_conflict exits 0, no claims in active.md.
Suggested branch: feature/organ-remote-block-and-uv-hook-repair
Implementation Steps
1. Heart and Hands opt into the generated block
PyAutoHeart/AGENTS.md:49-70— delete the hand-written## Remote sessions: knock on the door firstsection, insert<!-- repos_sync:remote:begin -->/<!-- repos_sync:remote:end -->in its place, immediately above the existingrepos_sync:historyblock. MirrorsPyAutoMind/AGENTS.md:104andPyAutoBrain/AGENTS.md:163.PyAutoHands/AGENTS.md:41-68— same.repos_sync.py --writefills both frompolicy/remote_sessions.md.
Deliberate loss: the canonical text carries no per-repo halves by design (repos_sync.py:128 module comment; test_the_canonical_text_carries_no_per_repo_numbers pins it). Heart's "641 tests / 7.6s / 2.7s", Hands' "406 tests / 27s / 13s" and Hands' .claude/session-python.txt bullet all go. Nothing is lost operationally — the hook reads that file at run time.
2. Move repair_uv_tools into the hook
- Move the function and its comment block from
scripts/session_bootstrap.sh:120-146intopolicy/session_start_hook.shas leg 3b, directly afterretool_uv_tools(:327). say→log.set -ehardening (required). The hook runsset -euo pipefail; the bootstrap onlyset -u. Two assignments abort the whole hook as written —base="$("$VENV/bin/python" -c … 2>/dev/null)"and the post-repairprefix="$("$link" -c … 2>/dev/null)". Both get an|| base=""/|| prefix=""tail.- Call it from the main flow (
:477) directly afterretool_uv_tools, beforepoint_pytest_at_venv. - Add a
--repair-uv-toolsargv seam beside the existingPYAUTO_SESSION_DEFINE_ONLYseam (:455).
Placement note for the moved comment: the bug reaches a tool env by two paths, not one. retool_uv_tools rebuilds 3.11 tools and hands them the hijacked /usr/local/bin/python3; point_system_default separately breaks every pre-existing tool env that already symlinked there and that retool skips (is_py312 … && continue). Running after retool_uv_tools covers both, because point_system_default runs earlier.
3. Bootstrap keeps its call, forwarding to the hook
session_bootstrap.sh:158-163— the--repair-uv-toolsseam becomesCLAUDE_CODE_REMOTE=true exec "$HOOK" --repair-uv-tools(after the existing[ -x "$HOOK" ]guard). Kept as a subprocess so the hook'sset -euo pipefailnever leaks into the bootstrap, which is contractually "never a gate".session_bootstrap.sh:296— the trailing call becomes the same subprocess invocation, non-fatal on failure. Still worth keeping: it covers the case where the hook's python leg exited early.- The
--checkleg is untouched.
4. Tests
tests/test_session_bootstrap.py— the existing_repair()helper still drives the bootstrap seam, which now proves the forward works end-to-end.test_a_tool_env_pointed_at_the_session_venv_is_repairedandtest_a_healthy_tool_env_is_left_alonemust stay green unchanged (they assert on"repointed widget", whichlog()still emits).- Add the hook-level twin using the existing
_call_hook_function("repair_uv_tools", …)helper (:287) withPYAUTO_UV_TOOLS_DIR— no new seam needed for it. - Add a test that the hook's main flow actually calls the repair. That is the bug: a single-repo session runs the hook, and nothing else.
5. Regenerate, scoped
--write from the full local workspace would write all 34 repos. Run it from the task worktree root instead, where only the four claimed repos exist — absent repos are skipped by design:
python3 PyAutoMind/scripts/repos_sync.py --write --root ~/Code/PyAutoLabs-wt/organ-remote-block-and-uv-hook-repair
python3 PyAutoMind/scripts/repos_sync.py --check --root ~/Code/PyAutoLabs-wt/organ-remote-block-and-uv-hook-repair6. File the backlog prompt
New Mind draft under maintenance/organs/: the 30 stale hook copies + euclid_assistant and admin_jammy which carry none, and the fact that no gate sees them.
Key Files
PyAutoMind/policy/session_start_hook.sh— canonical hook; gainsrepair_uv_tools+ the argv seamPyAutoMind/scripts/session_bootstrap.sh— loses the function, keeps both call sites as hook invocationsPyAutoMind/policy/remote_sessions.md— canonical block, unchangedPyAutoMind/scripts/repos_sync.py— unchanged (keepsfirewall_gate.ymlfrom triggering; it is path-filtered to this file)PyAutoMind/tests/test_session_bootstrap.py— hook-level uv-repair coveragePyAutoHeart/AGENTS.md,PyAutoHands/AGENTS.md— marker pairs replacing hand-written text{PyAutoMind,PyAutoBrain,PyAutoHeart,PyAutoHands}/.claude/hooks/session-start.sh— regenerated
Work Classification
Library — organ code and generated copies. No workspace follow-up.
Worktree root
~/Code/PyAutoLabs-wt/organ-remote-block-and-uv-hook-repair/
Testing
python3 -m pytest -qin PyAutoMind (hook + bootstrap + repos_sync suites)repos_sync.py --check --root <worktree root>clean on every leg- Heart and Hands suites, since their AGENTS.md changes
Original Prompt
Click to expand starting prompt
# Two organs are missing this session's fixes, and the hook still ships the uv bug
Type: maintenance
Target: organs
Repos:
- PyAutoMind
- PyAutoHeart
- PyAutoHands
Difficulty: small
Autonomy: supervised
Priority: normal
Status: formalised
Filed: 2026-08-27
The 2026-08-27 mobile-workflow pass shipped two things that are, by
construction, only half-rolled-out. Both need a session with **all four organs
attached** — this one could not attach PyAutoHeart or PyAutoHands (`add_repo`
was refused), which is the same "a drift check is only as strong as the number
of repos your session can see" the last three passes each paid for once.
## 1. Opt Heart and Hands into the generated remote-session block
`PyAutoMind/policy/remote_sessions.md` is now the single source, written into
each repo's `AGENTS.md` between `<!-- repos_sync:remote:begin/end -->` by
`repos_sync.py --write`, with a drift check. Mind and Brain carry the markers;
Heart and Hands still carry their own hand-written text, which is already a pass
behind (it says "bootstrap if pytest misbehaves", a trigger that no longer
fires).
Add the markers to both, run `--write`, and confirm `--check` reports the block
for four repos rather than two. No gate order: `firewall_gate.yml` is
path-filtered to `scripts/repos_sync.py`, which this does not touch.
## 2. Promote the uv-tool repair from the bootstrap into the hook
`scripts/session_bootstrap.sh` gained `repair_uv_tools`: uv creates each tool
env's `bin/python` as a symlink to `/usr/local/bin/python3`, the hook replaces
that path with a wrapper that `exec`s the session venv, and the exec replaces
argv — so every tool env resolves `sys.prefix` to the venv and mypy, flake8,
black, poetry and pyright all die with `ModuleNotFoundError` naming themselves.
Measured 2026-08-27; `--check` called them all `3.12 OK` until the same commit
taught it to run each tool.
The fix belongs in `policy/session_start_hook.sh`, beside `retool_uv_tools`
which creates the condition — the hook is what a **single-repo** session runs,
and that session never calls the bootstrap. It was left in `scripts/` because
the hook is generated into all four organs and two of them could not be
attached: changing it here would have made `firewall_gate.yml` red on repos this
session could not regenerate.
So: move the function into the hook, regenerate all four copies, keep the
bootstrap's call (it is idempotent, and the bootstrap runs the hook anyway), and
keep the `--check` leg where it is.
## Done when
- `repos_sync.py --check` is clean with all four organs checked out, and the
remote-session block leg names four repos.
- A fresh remote session that runs only the SessionStart hook — no bootstrap
call — has a working `mypy`, `flake8` and `black`.
- The existing tests still pass, plus a hook-level version of
`test_a_tool_env_pointed_at_the_session_venv_is_repaired`.
Activity
prompt: file issue for pages-dashboard-publish-gap (#361) → start_lib…
prompt: route organ-remote-block-and-uv-hook-repair (#360) → start_li…
Jammy2211 commentedon Aug 27, 2026
Plan correction — step 5's
--writescoping does not work as written.The detailed plan says to run
repos_sync.py --write --root <task worktree root>so only the four claimed repos are written. Verified against the worktreeworktree_createactually builds, that is wrong and would be actively harmful:worktree_createsymlinks every unclaimed entry back to the canonical checkout, andPath.is_dir()follows symlinks — so--write --root <worktree>would resolve those 30 names to the live main workspace and write regenerated hooks straight into it, onmain, outside any branch. That is the opposite of scoping, and it would dirty repos other sessions are working in.Replacement for step 5: build a scratch root containing symlinks to only the four worktree repos, and point
--rootat that. Absent repos are skipped by design, so the other 30 are never reached, and the writes land in the worktree through the four symlinks.The acceptance bar in "Scope" is unchanged — it just has to be measured through
$SCOPE, not through the worktree root.Jammy2211 commentedon Aug 27, 2026
Library PRs created — shipped under an explicit Heart-RED override.
AGENTS.mdmarkers + regenerated hookAGENTS.mdmarkers + regenerated hookMerge order is Mind first — the three copies must never be ahead of the canonical they are generated from.
Suites: PyAutoMind 271, PyAutoBrain 584, PyAutoHeart 656, PyAutoHands 430 — all green.
The Heart gate, and the override
pyauto-heart readinesswas RED at ship time. Verbatim:The single RED reason is
release validation FAILED (stage integrate)— PyAutoLabs/PyAutoHeart run 33073386315, aworkflow_dispatch"Release Integrate" from 2026-08-27 12:45 UTC that failed on workspace script runs (autofit/plot,autolens_test/imaging,autolens_test/multi_dataset), not on anything in this change.ship_library's corrective-PR exception covers only a fix scoped to the RED reason, which this is not — so this was shipped and merged on an explicit human override, recorded here rather than left implicit.Worth noting: the third YELLOW is this task's own subject. Heart already sees the 34-mismatch hook drift that CI is structurally blind to (
firewall_gate.ymlchecks out four organs and skips absent repos). This change does not reduce that count — the four organs stay clean, the other 30 stay stale — which is what the follow-up prompt is for.Jammy2211 commentedon Aug 27, 2026
Shipped. All four PRs merged, in canonical-first order.
origin/main1dce71d8✓d9e1ad62✓0ab4f0bd✓b151636c✓Proved with
git merge-base --is-ancestoragainst unshallowed clones, per repo — not from the PR state alone.CI: every run and every matrix leg green. Mind
privacypass (driftisif: github.event_name != 'pull_request', skipped by design); Brain 3.12 + 3.13; Heart 3.12 + 3.13; Hands 3.12 + 3.13 + 3.14.firewall_gate.ymlcorrectly did not fire — it is path-filtered toscripts/repos_sync.py, which this task deliberately left untouched.What landed
policy/remote_sessions.md, up from two. Heart and Hands lost 23 and 28 hand-written lines each, including a trigger (No module named pytest) that the canonical text records as having stopped firing when the image moved to 3.12.repair_uv_toolsis leg 3b of the hook, so a single-repo session — which runs the hook and never the bootstrap — no longer ends withmypy,flake8,black,poetryandpyrightall dead from the breakage leg 2 caused on the way past.Three things found along the way
write_session_hookswalks the whole manifest. Thirty copies are a pass behind and two carry none;firewall_gate.ymlchecks out four organs and skips absent repos, so no gate sees it. Heart does (manifest drift: … 34 mismatch(es)). Filed asdraft/maintenance/organs/session_hook_reaches_only_four_of_thirty_four_repos.md.set -egap was real. Moving a function from aset -uscript into aset -euo pipefailone made two command substitutions, and a failingln -sfn, into whole-session-start aborts. All three are now non-fatal, matching every other leg's contract.--write --root <task worktree>is not a scoping mechanism.worktree_createsymlinks unclaimed repos back to the canonical checkouts andPath.is_dir()follows symlinks, so it would have regenerated hooks into the live main workspace onmain. Corrected in the plan comment above; the regeneration ran through a scratch root holding only the four claimed repos, and all 34 canonical checkouts were walked afterwards to confirm nothing leaked.Shipped under an explicit human override of Heart RED (
release validation FAILED (stage integrate)), unrelated to this change and recorded in the previous comment.complete: organ-remote-block-and-uv-hook-repair (#360) — four organs,…
complete: profiling-summary-v1 (autolens_profiling#359, PR #360)