fix(kvb-cleanup): hand sandbox worktree removal to the operator

An agent sandbox mounts every .git/worktrees/<id> read-only, so git worktree remove unregisters the worktree and then fails with EBUSY; container-owned files fail with EACCES. The worktree rules now forbid removal inside the sandbox and hand the operator the ownership count, chown and removal command in one message. A dirty worktree is a candidate only when all its changed and untracked content is preserved at a branch tip, and an untagged image with RepoDigests counts as a medium-risk digest pull. Both files were trimmed so the skill text does not grow.

🎯 Fix: 92 %
💥 Break: 2 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 90
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-07 09:34:03 +00:00
1 parent db3c5264f2
commit fea80f40f7
2 files changed
+32 -33

No files matched your search

+15 -21
View File
@@ -2,31 +2,25 @@
## Inventory ## Inventory
`docker system df -v`; containers with state and compose project label, `docker system df -v`: containers with state and compose project, volumes
volumes with the containers using them (`docker ps -a --filter with their users (`docker ps -a --filter volume=<name>`), dangling and unused
volume=<name>`), dangling and unused images, build cache, unused networks. images, build cache, unused networks. An untagged image with `RepoDigests`
For every stopped container (`exited`, `created`) record its volumes is a digest-pinned pull: medium. Per stopped container its volumes
(`{{.Mounts}}`); for every volume no running container uses, record size, (`{{.Mounts}}`); per volume without a running user its size, `CreatedAt` and
`CreatedAt` (`docker volume inspect`) and compose project label, anonymous compose project, anonymous ones included.
ones included.
## Volumes checklist ## Volumes checklist
Stopped containers and volumes always get their own checklist Stopped containers and volumes get their own checklist (`multiSelect: true`,
(`multiSelect: true`, header `⚠ Volumes`, in a further call when call 1 is header `⚠ Volumes`, a further call when call 1 is full), each one unless a
full). Every stopped container and every volume without a running container peer claimed it, `unknown` and anonymous ones included. Question text:
appears there unless a peer claimed it; none is dropped as "not proposed",
`unknown` and anonymous ones included. The question text carries the warning:
`Deleting a volume is irreversible; its data does not come back unless the `Deleting a volume is irreversible; its data does not come back unless the
description says how.` One option per stopped container together with its description says how.` One option per stopped container with its volumes,
volumes (removing the container alone frees nothing of them), one per one per unattached volume, anonymous ones grouped; each names size,
unattached volume, anonymous ones grouped. Each description gives size, `CreatedAt`, owner or project, and what is lost.
`CreatedAt`, owner or compose project, and what is lost.
## After removing images ## After removing images
Locally built images free little on their own: their layers stay behind as Built images leave their layers as build cache: re-measure `docker system
build cache. Re-measure `docker system df`; the layers show up as reclaimable df`, then offer `docker builder prune --filter until=24h`, after asking the
build cache. Offer `docker builder prune --filter until=24h` as a follow-up peers that claimed build cache, outside their builds.
candidate, ask the peers that claimed build cache first, and run it outside
their running builds.
+17 -12
View File
@@ -2,20 +2,25 @@
## Inventory ## Inventory
Per worktree: size (`du -sh`), dirty state (`git status --short`), whether Per worktree: size (`du -sh`), dirty state, whether its branch is pushed and
its branch is pushed, and whether its HEAD is already contained in an whether its HEAD is in an integration line (the default branch and every
integration line: the default branch and every branch the project merges branch the project merges features into; ask the operator or the project's
features into (ask the operator or the project's memory which ones). With memory). With `pkgmgr`, `pkgmgr branch gc --json --sizes --merged-into
`pkgmgr` installed, `pkgmgr branch gc --json --sizes --merged-into <line>` <line>` (one per line) measures it all; merged-clean items go to the
(one per line) measures all of it; its merged-clean items go to the
`kvb-git-branch-gc` skill. `kvb-git-branch-gc` skill.
A clean worktree whose HEAD is in an integration line is medium risk: its Clean and in a line: medium, its ignored files (`node_modules`, build output,
ignored files (`node_modules`, build output, evidence) are lost, no commit. evidence) are lost. Dirty: a candidate only when every changed, staged and
untracked file is a blob at some branch tip and a detached HEAD is reachable
from another ref; else it is unique work and stays.
## Removal ## Removal
Remove a worktree the way it was made: a slot worktree through the project's The way it was made: a slot worktree through the project's teardown target
teardown target (e.g. `make worktree-down`), run outside the sandbox; a plain (e.g. `make worktree-down`), a sidetree through `git worktree remove`,
sidetree through `git worktree remove` without `--force`, which refuses a `--force` only for a dirty one cleared above. Never in an agent sandbox: its
dirty tree. read-only `.git/worktrees/<id>` mounts make git unregister and then fail with
`Device or resource busy`, container-owned files with `Permission denied`.
Hand the operator in one message, per worktree, its count of `find <wt> -not
-user "$USER" -prune | wc -l`, `sudo chown -R "$USER": <wt>` where it is not
0, then the removal command.