Best for
- Operator reports disk full, hundreds of worktrees, or leftover sandbox build/ branches.
- After a merge wave when many topic worktrees are still registered.
- Deciding whether to enable soak flags on a live install.
OpenDigitalProductFactory/opendigitalproductfactory/packages/dpf-skill-pack/skills/dpf-worktree-hygiene/SKILL.md
Use when DPF worktree or Build Studio sandbox disk is sprawling, when leftovers remain after merges, or when deciding on janitor/GC flags.
Decision brief
Creates are covered by dpf-worktree-per-session. This skill is the reaping / disk-hygiene half: what runs automatically, what the portal schedule does when flags are on, and what an agent may run only with clear operator go.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/OpenDigitalProductFactory/opendigitalproductfactory --skill "packages/dpf-skill-pack/skills/dpf-worktree-hygiene"Inspect the Agent Skill "dpf-worktree-hygiene" from https://github.com/OpenDigitalProductFactory/opendigitalproductfactory/blob/f795645f8fda2da7c172b32be5014f37517cee10/packages/dpf-skill-pack/skills/dpf-worktree-hygiene/SKILL.md at commit f795645f8fda2da7c172b32be5014f37517cee10. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Primary design: multi-client governance parity (BI-42FA7DD8, BI-8BD61C30, BI-A4BEFE99). Spec/plan under docs/superpowers/2026-07-26-multi-client-governance-parity.
Wire via host .env and/or gitignored docker-compose.override.yml on the portal service, then recreate portal so printenv shows the flags. Do not set AUTOREAP or BRANCH delete until observe-only soak looks clean.
Operator reports disk full, hundreds of worktrees, or leftover sandbox build/ branches.
Creating a new session worktree → dpf-worktree-per-session.
From the root clone (merge worktree):
Permission review
The documentation asks the agent to run terminal commands or scripts.
node scripts/worktree-janitor.mjs --dry-run --jsonThe documentation asks the agent to run terminal commands or scripts.
docker exec dpf-sandbox-1 sh -c 'ls /workspace/.builds 2>/dev/null; git -C /workspace branch --list "build/*"'Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 93/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 14 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
Creates are covered by dpf-worktree-per-session. This skill is the reaping / disk-hygiene half: what runs automatically, what the portal schedule does when flags are on, and what an agent may run only with clear operator go.
| Path | Owner | Agent freestyle? |
|---|---|---|
Primary reaper — this session's worktree on SessionEnd when Tier-A (merged + clean). Never on Stop: that fires every turn, and a live tree becomes Tier-A the moment its own PR merges (BI-E5D810B8) | Client hooks (worktree-session-hygiene.mjs) | No — automatic when dpf-platform / global hooks are installed |
| Fleet soak — scheduled observe / optional Tier-A, sandbox leftover GC | Portal Inngest (ops/worktree-janitor, ops/sandbox-build-gc) + env flags | No — enable flags; do not hand-cron |
Workroom reaper — transition dead workroom records (lease expired / build terminal / idle) working→abandoned; the DB half of hygiene (WS9) | Portal Inngest (ops/taskrun-watchdog tick) + DPF_WORKCAPSULE_REAPER_* flags | No — enable flags; observe via list_work_capsules staleOnly=true |
| Exceptional reclaim — live Tier-A bulk prune, force-delete locked dirs, kill locking shells | Operator + explicit "go" | Only after dry-run report + operator approval |
Primary design: multi-client governance parity (BI-42FA7DD8, BI-8BD61C30, BI-A4BEFE99). Spec/plan under docs/superpowers/*2026-07-26-multi-client-governance-parity*.
| Flag | Default | Meaning |
|---|---|---|
DPF_WORKTREE_JANITOR_ENABLED | off | When 1, schedule scans (dry-run unless AUTO_REAP) |
DPF_WORKTREE_JANITOR_AUTO_REAP | off | When 1 and ENABLED, live Tier A only |
DPF_SANDBOX_BUILD_GC_ENABLED | off | When 1, daily GC for terminal/orphan .builds/* |
DPF_SANDBOX_BUILD_GC_DELETE_BRANCHES | off | When 1 and ENABLED, also age-delete build/* past grace |
DPF_WORKCAPSULE_REAPER_ENABLED | off | When 1, the taskrun-watchdog tick scans for dead Workrooms (observe-only unless AUTO_REAP) |
DPF_WORKCAPSULE_REAPER_AUTO_REAP | off | When 1 and ENABLED, live-transition dead workrooms working→abandoned (reversible; DB-only) |
Wire via host .env and/or gitignored docker-compose.override.yml on the portal service, then recreate portal so printenv shows the flags. Do not set AUTO_REAP or BRANCH delete until observe-only soak looks clean.
build/* branches.rm -rf a worktree or git worktree remove --force by hand.dpf-worktree-per-session.dpf-finishing-a-development-branch.From the root clone (merge worktree):
# Host worktree classification (default dry-run)
node scripts/worktree-janitor.mjs --dry-run --json
# Sandbox leftovers
docker exec dpf-sandbox-1 sh -c 'ls /workspace/.builds 2>/dev/null; git -C /workspace branch --list "build/*"'
Report: counts of PRUNE_TIER_A / KEEP / SKIP, and whether sandbox has orphan dirs vs an active build/FB-* (do not delete review/in-progress builds).
docker exec dpf-portal-1 printenv | grep -E 'WORKTREE_JANITOR|SANDBOX_BUILD_GC'.Only after operator says go (or equivalent) on a dry-run report:
node scripts/worktree-janitor.mjs --live --tier-a-only --json
git worktree remove --force on Windows (BI-F6AC1A56).A directory under the worktrees root that is not in git worktree list is not a worktree — it is a fake worktree, and it is actively dangerous:
.git file, so every git command run inside it silently operates on the ROOT clone — git status, git commit, and git worktree list all report the root clone's state.Detect before trusting a directory:
git worktree list | grep -F "<path>" # absent, or marked "prunable"
test -e "<path>/.git" || echo "FAKE worktree"
git worktree prune removes only the metadata — it never deletes the directory, and it routinely surfaces more fake worktrees (their gitdir file points at a non-existent location). Re-sweep the worktrees root after every prune; one cleanup pass typically uncovers several more.
Before deleting a .git-less directory, prove it holds no unsaved work by diffing it against its branch with a throwaway index (read-only, touches nothing):
export GIT_INDEX_FILE=$(mktemp)
git --git-dir=<root>/.git --work-tree=<path> read-tree <branch>
git --git-dir=<root>/.git --work-tree=<path> diff --ignore-cr-at-eol --numstat
unset GIT_INDEX_FILE
Read the output correctly or you will scare yourself: the first status column is branch-vs-root-HEAD noise, not disk state — only the second column reflects the directory. On Windows the raw diff is dominated by CRLF churn, so --ignore-cr-at-eol is what separates real edits from line-ending noise. A file that differs but does not exist on disk is an absence, not lost work.
Then remove:
apps/web/node_modules/@dpf/* → packages/*), which is harmless. The root-clone-eating case is a junction whose target lies outside the tree. Read the targets, then unlink every reparse point with cmd /c rmdir before any recursive delete.rd /s /q the tree. It will fail on node_modules paths over MAX_PATH (260 chars) — deep expo / xcframework prebuild trees are the usual culprit, and the failure looks like an unexplained partial delete. Purge with robocopy, which handles long paths natively, then remove the shell:robocopy "$(mktemp -d)" "<path>" /MIR /NFL /NDL /NJH /NJS /R:1 /W:1
cmd /c rd /s /q "<path>"
rd exit 32) is inert — no files, no .git, no junctions, no metadata, so the trap is already defused. Retrying is futile; leave it for the next reboot rather than terminating another session's processes to reclaim an empty folder.Prefer flag-enabled scheduled GC. Manual one-shot only with go:
.builds/FB-* (no FeatureBuild row) or terminal phases.review, building, …) branches.git worktree prune inside sandbox if a dir was deleted out from under git.Disk hygiene reaps worktrees and sandboxes; this reaps the Workroom row — the coordination record that shows on the Work Control board and in list_work_capsules. They drift apart: a Build Studio workroom is born at the daily 14:00 governed-backlog tee-up and, if its build stalls, is never written again, so updatedAt freezes at ...T14:00:00 while status still says working. Dozens of dead workrooms then read as active, jam the Build Studio WIP cap, and become the mechanism by which work is silently duplicated.
updatedAt is not liveness. True liveness (apps/web/lib/work-capsules/liveness.ts) is derived from signals that only advance with real work: an open PR, a lease-backed executor's leaseExpiresAt (external Claude/Codex/Grok), the linked build's phase + activity (a null-lease BS workroom's only real signal), and lastSyncedAt. The board, list_work_capsules (see its livenessSummary and staleOnly=true), and the reaper all share that one classifier.
list_work_capsules staleOnly=true — the reap-candidate set, each with a liveness verdict and the trueLivenessAt that proves it. No writes.apps/web/lib/work-capsules/work-capsule-reaper.ts) runs on the taskrun-watchdog tick: observe-only when DPF_WORKCAPSULE_REAPER_ENABLED=1, live only with DPF_WORKCAPSULE_REAPER_AUTO_REAP=1. It transitions dead workrooms working→abandoned.worktree-janitor / section C after its own explicit go. Abandon is reversible: re-promote the backlog item or re-adopt the branch.working workroom left behind).A new worktree does not inherit the root clone's tool caches or generated artifacts. Each of these presents as a hang or a scary failure and is really just an unseeded worktree.
gitleaks re-downloads per worktree. The pre-commit scan caches under the worktree's git dir, not the root clone's. A cancelled download leaves a half-written zip that makes the next commit appear to hang. Seed it from the root clone:
V=<version>; T="$(git rev-parse --git-path dpf-tools/gitleaks/$V)"
mkdir -p "$T" && cp "<root-clone>/.git/dpf-tools/gitleaks/$V/gitleaks.exe" "$T/"
packages/db tests need the generated Prisma client. Junction it rather than regenerating per worktree — on Windows the link name must be relative (an absolute target path errors with "Parameter format not correct"):
cd packages/db && MSYS_NO_PATHCONV=1 cmd /c "mklink /J generated <root-clone>\packages\db\generated"
Remove that junction with cmd /c rmdir generated — never rm -rf, which follows the link and eats the target's contents.
The junction then trips the pre-commit Prisma staleness guard, which compares mtimes (schema.prisma newer than generated/client/client.ts) and tries to regenerate. In a fresh worktree the schema is simply newer than the borrowed artifact. Confirm the schemas are identical (md5sum both), then remove the junction, commit, and recreate it — do not regenerate through the junction, which writes into the root clone.
A stale index.lock after a killed commit. Check the holder before removing anything: wmic process where processid=<pid> get commandline. With many concurrent sessions, a live git.exe usually belongs to another worktree and does not hold your lock. Only then remove .git/worktrees/<name>/index.lock.
Commit messages: use git commit -F <file>. PowerShell here-strings (@'…'@) are not parsed by the Bash tool and leak a literal @ into the subject line. Verify with git log -1 --format=%s.
rm -rf / mass process kill as hygiene.**Worktree/sandbox hygiene**
- Dry-run: Tier-A=N, keep=N, skip=N
- Sandbox: .builds=N, build/*=N (active kept: …)
- Flags: JANITOR_ENABLED=… AUTO_REAP=… SANDBOX_GC=… BRANCH_DELETE=…
- Live actions taken: none | Tier-A live prune | sandbox one-shot (operator go: yes)
- Next: enable observe-only soak | wait soak | stop
dpf-worktree-per-session — create pathscripts/worktree-janitor.mjsscripts/lib/junction-safe-worktree-remove.mjsapps/web/lib/operate/scheduled-jobs/catalog.ts (worktree-janitor, sandbox-build-gc)Frequently asked questions
Creates are covered by dpf-worktree-per-session. This skill is the reaping / disk-hygiene half: what runs automatically, what the portal schedule does when flags are on, and what an agent may run only with clear operator go.
The source record exposes this install command: npx skills add https://github.com/OpenDigitalProductFactory/opendigitalproductfactory --skill "packages/dpf-skill-pack/skills/dpf-worktree-hygiene". Inspect the command and pinned source before running it.
Static rules flagged exec-script in the source; the page lists the matching lines and excerpts.