a7685e3b21 derived the worktree lab subnet as 10.244.<slot>.0/24, which
lies inside NETWORK_TOR_VIRTUAL_CIDR (10.240.0.0/12). On a swarm with
svc-net-tor the nft output chain redirects every TCP connection to that
range into Tor's TransPort, so the nodes could not reach each other: in
the slot-114 worktree `curl http://10.244.114.10:5000/v2/` timed out on
the manager itself and svc-prx-openresty died pushing to the stack
registry with Client.Timeout.
The prefix is now 10.160.<slot>, outside every range the platform
reserves in group_vars/all/08_networks.yml.
The lint now reads the reserved ranges from that file instead of a
hardcoded list: NETWORK_TOR_VIRTUAL_CIDR, NETWORK_MESH_POOL,
NETWORK_DOCKER_ADDRESS_POOLS, NETWORK_NESTED_DOCKER_ADDRESS_POOLS and
NETWORK_NESTED_DOCKER_BIP, plus the primary lab subnet and the worktree
INFINITO_SUBNET range. Against the 10.244 prefix it fails every slot on
the Tor range.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/system/worktree/up.sh pinned INFINITO_SWARM_LAB_SUBNET to
192.168.$((244 + slot)).0/24. From slot 12 on that third octet exceeds
255, so every worktree past slot 11 carries an invalid subnet
(192.168.358.0/24 for slot 114), and make swarm-zombie dies at the lab
network creation with `invalid subnet: netip.ParsePrefix(...)`.
The lab prefix is now 10.244.<slot>, valid and distinct for every slot
up to max_slot 255, and outside both the primary lab subnet
192.168.244.0/24 and the 172.30.0.0/16 worktree subnets. The host
daemon allocates nothing from 10.0.0.0/8.
A new lint renders the prefix for every slot and fails on an invalid,
repeated or overlapping subnet; against the old derivation it fails 247
of 255 slots.
Worktrees that were pinned before this fix keep their custom.env; the
ones at slot 12 or above need their INFINITO_SWARM_*_IP and
INFINITO_SWARM_LAB_SUBNET pins rewritten to 10.244.<slot>.x and a
make dotenv before they can run swarm.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
git worktree remove deletes the checkout first and the metadata dir
second. Under the agent sandbox, which holds config.worktree and
commondir as read-only bind mounts, it therefore dies with EBUSY after
the checkout is already gone, aborting make worktree-down with rc 255
even though the branch is in fact free. down.sh now falls back to the
unregistration prune.sh already used (drop gitdir + HEAD, best-effort
rm -rf) and reports any dir the sandbox still pins. That routine moves
into lib.sh so both callers cannot drift apart, and gains a third
outcome for the case where gitdir/HEAD themselves survive: only then is
the branch still claimed, and only then must the teardown fail.
The discriminator for the fallback is whether the checkout survived,
not a match on "Device or resource busy" — the identical half-removed
state arises from EACCES, EPERM and EROFS.
A failing git status probe no longer reads as clean: on a broken .git
link it exits 128 with empty output, which used to satisfy the guard,
tear down the compose stack, and only then die at git worktree remove
with an unexplained 128.
Measured against the unfixed scripts with real fixtures: pinned
metadata dir 255 -> 0, broken .git link 128 -> 1.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
It shipped with the persistent-base change; the constraint it described - git forbids a branch feature beside feature/x, so the nested directories cannot collide - belongs in that commit's message, where it already is.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The default base was /tmp, which is tmpfs on this host: a reboot took a whole worktree and its uncommitted work with it. Derive the base from the origin remote instead, as ~/.local/share/worktrees/<domain>/<account>/<repo>, and let the branch keep its slashes so it nests, which also makes the infinito-wt- prefix redundant. Without an origin remote it now fails loudly rather than inventing a location.
up.sh bumped only INFINITO_SWARM_LAB_SUBNET per slot while the five swarm lab node IPs stayed at default.env's 192.168.244.x, so every worktree from slot 1 on got a lab network in a different /24 than the nodes it places in it, and collided with the primary checkout's lab. Pin all six values from one octet.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
841a5e0e1 claimed to keep entries whose checkout cannot be inspected, but the check resolved dirname(dirname(gitdir)) -- the checkout's PARENT, e.g. /tmp -- which always exists. The branch was dead code and every entry fell through to `rm -f gitdir HEAD`, including a live worktree whose metadata git can no longer read: that path drops it from the liveness list and it was then unregistered, which is the reported symptom.
Make the checkout directory itself the criterion, and refuse to conclude anything when its parent is not traversable -- `test -d` succeeds on a directory without +x, so a first attempt using -d alone reproduced the same blind spot. Reproduced in throwaway repos: a live worktree with a 0-byte commondir keeps its registration, a deleted checkout is pruned, and a metadata corpse without gitdir is cleaned.
Remaining limit: `git worktree prune`, which the script runs first, shares the blind spot and wins where it can unlink; this guard therefore only decides in the sandbox-pinned case that produced the bug.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Staleness was decided by reading gitdir inside the metadata dir. Under the agent sandbox that dir is pinned as read-only bind mounts, so a live worktree can read as having no gitdir at all -- prune then removed gitdir and HEAD and left the checkout as 'fatal: not a git repository' with no way back. Decide liveness from git's own worktree list plus each checkout's .git pointer instead, keep entries whose checkout base is not visible rather than guessing, drop the expected EBUSY noise, and report the pinned leftovers once with the command that clears them.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
git worktree prune removes the metadata directory as a whole, but the agent
sandbox pins config.worktree and commondir as read-only bind mounts, so the
unlink fails with EBUSY and the stale entry keeps claiming its branch.
Dropping gitdir and HEAD unregisters it without needing the pinned files gone.
Parallel checkouts collided on the compose subnet, host ports and container
names, so a quality run in a worktree died at compose-up. Slot each checkout
via custom.env and layer two overrides on top: the shared cache network in
place of a second cache stack, and the shared .git directory without which
git aborts inside the container.
Both overlays gate on the resource they need, not on the slot number, so a
stray INFINITO_INSTANCE cannot make compose demand a value nobody set.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>