Found in the adversarial review of #120.
revision() writes to each Mech repo's .git/index, so the pipeline is no longer read-only against the checkouts
Where: scripts/fleet/roots.py:107
Plain git status refreshes the index opportunistically: it takes index.lock and rewrites .git/index when stat data is stale. Before this PR the pipeline only read the Mech checkouts. Now every census or stats run takes locks in them, twice per Mech. The live checkouts are shared with a nightly fast-forward and with claw agent worktrees (ProteinTraitsMech has three linked worktrees). A concurrent git merge --ff-only or git add that hits the lock window fails with 'Unable to create .../index.lock: File exists'. The window is large for TaxonMech's 626k-entry index.
Failure scenario. Someone runs mech_stats.py against the default MECHS_ROOT during the ~23:13 scheduled fast-forward, or while a claw agent commits in ProteinTraitsMech. revision()'s status holds index.lock while it writes the refreshed index, and the other process's git command aborts.
Evidence. Scratch probe in a CommunityMech clone: touch one record, then GIT_OPTIONAL_LOCKS=0 git status leaves .git/index mtime at 1790307521. Calling roots.revision('CommunityMech') changes it to 1790307541. git worktree list on the live ProteinTraitsMech shows three linked worktrees, including one under culturebotai-claw/workspace/.
Verifier (partly, low). roots.revision() (scripts/fleet/roots.py:107) runs git status --porcelain without --no-optional-locks. On every call it takes .git/index.lock in the checkout under MECHS_ROOT for the whole status scan (about 5 s for TaxonMech), and it rewrites .git/index when stat data is stale. prefix_census.py and mech_stats.py each call it once per Mech. The pipeline does write into the default MECHS_ROOT when run as _fleet/README.md describes: those are the live shared checkouts, and on main the pipeline ran no git there. During that window, any concurrent index-writing git command in the same main worktree (a manual or agent git merge --ff-only, pull, checkout or add there) aborts with "Unable to create .../index.lock: File exists". Commits in linked worktrees, such as the claw worktrees of ProteinTraitsMech, use separate index files and are not affected. No scheduled nightly fast-forward exists on this machine; the fast-forwards in the reflog are ad hoc. The update-xmech-page skill's procedure points MECHS_ROOT at scratch sparse clones, so it only locks throwaway clones. Fix: git --no-optional-locks status --porcelain, or pass env GIT_OPTIONAL_LOCKS=0 to the subprocess. With that, contention dropped to 0 in my test.
Suggested fix. Use git --no-optional-locks status ... (or pass env GIT_OPTIONAL_LOCKS=0 to the subprocess).
Found in the adversarial review of #120.
revision() writes to each Mech repo's .git/index, so the pipeline is no longer read-only against the checkouts
Where:
scripts/fleet/roots.py:107Plain
git statusrefreshes the index opportunistically: it takes index.lock and rewrites .git/index when stat data is stale. Before this PR the pipeline only read the Mech checkouts. Now every census or stats run takes locks in them, twice per Mech. The live checkouts are shared with a nightly fast-forward and with claw agent worktrees (ProteinTraitsMech has three linked worktrees). A concurrentgit merge --ff-onlyorgit addthat hits the lock window fails with 'Unable to create .../index.lock: File exists'. The window is large for TaxonMech's 626k-entry index.Failure scenario. Someone runs mech_stats.py against the default MECHS_ROOT during the ~23:13 scheduled fast-forward, or while a claw agent commits in ProteinTraitsMech. revision()'s status holds index.lock while it writes the refreshed index, and the other process's git command aborts.
Evidence. Scratch probe in a CommunityMech clone: touch one record, then
GIT_OPTIONAL_LOCKS=0 git statusleaves .git/index mtime at 1790307521. Calling roots.revision('CommunityMech') changes it to 1790307541.git worktree liston the live ProteinTraitsMech shows three linked worktrees, including one under culturebotai-claw/workspace/.Verifier (partly, low). roots.revision() (scripts/fleet/roots.py:107) runs
git status --porcelainwithout --no-optional-locks. On every call it takes .git/index.lock in the checkout under MECHS_ROOT for the whole status scan (about 5 s for TaxonMech), and it rewrites .git/index when stat data is stale. prefix_census.py and mech_stats.py each call it once per Mech. The pipeline does write into the default MECHS_ROOT when run as _fleet/README.md describes: those are the live shared checkouts, and on main the pipeline ran no git there. During that window, any concurrent index-writing git command in the same main worktree (a manual or agentgit merge --ff-only,pull,checkoutoraddthere) aborts with "Unable to create .../index.lock: File exists". Commits in linked worktrees, such as the claw worktrees of ProteinTraitsMech, use separate index files and are not affected. No scheduled nightly fast-forward exists on this machine; the fast-forwards in the reflog are ad hoc. The update-xmech-page skill's procedure points MECHS_ROOT at scratch sparse clones, so it only locks throwaway clones. Fix:git --no-optional-locks status --porcelain, or pass env GIT_OPTIONAL_LOCKS=0 to the subprocess. With that, contention dropped to 0 in my test.Suggested fix. Use
git --no-optional-locks status ...(or pass env GIT_OPTIONAL_LOCKS=0 to the subprocess).