Source profileQuality 93/100Review permissions

OpenDigitalProductFactory/opendigitalproductfactory/packages/dpf-skill-pack/skills/dpf-worktree-hygiene/SKILL.md

dpf-worktree-hygiene

Use when DPF worktree or Build Studio sandbox disk is sprawling, when leftovers remain after merges, or when deciding on janitor/GC flags.

Source repository stars
14
Declared platforms
0
Static risk flags
1
Last source update
2026-08-28
Source checked
2026-08-28

Decision brief

What it does: where it fits

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.

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.

Not for

  • Creating a new session worktree → dpf-worktree-per-session.
  • Normal PR finish of this branch's worktree → session SessionEnd reaper or dpf-finishing-a-development-branch.

Compatibility matrix

Platform support, with evidence labels

PlatformStatusEvidenceWhat to check
CodexNot declaredNo explicit evidencePortability before use
Claude CodeNot declaredNo explicit evidencePortability before use
CursorNot declaredNo explicit evidencePortability before use
Gemini CLINot declaredNo explicit evidencePortability before use
Open the compatibility checker

Installation

Inspect first. Install second.

The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

Source-detected install commandSource
npx skills add https://github.com/OpenDigitalProductFactory/opendigitalproductfactory --skill "packages/dpf-skill-pack/skills/dpf-worktree-hygiene"
Safe inspection promptEditorial

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

What the source asks the agent to do

  1. 01

    Ownership model (do not invert)

    Primary design: multi-client governance parity (BI-42FA7DD8, BI-8BD61C30, BI-A4BEFE99). Spec/plan under docs/superpowers/2026-07-26-multi-client-governance-parity.

    Primary design: multi-client governance parity (BI-42FA7DD8, BI-8BD61C30, BI-A4BEFE99). Spec/plan under docs/superpowers/2026-07-26-multi-client-governance-parity.
  2. 02

    Flags (install-local; not a PR)

    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.

    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.
  3. 03

    When to use

    Operator reports disk full, hundreds of worktrees, or leftover sandbox build/ branches.

    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.
  4. 04

    When NOT to use

    Creating a new session worktree → dpf-worktree-per-session.

    Creating a new session worktree → dpf-worktree-per-session.Normal PR finish of this branch's worktree → session SessionEnd reaper or dpf-finishing-a-development-branch.Portal self-upgrade / version skew → self-upgrade path, not janitor.
  5. 05

    Steps

    From the root clone (merge worktree):

    From the root clone (merge worktree):

Permission review

Static risk signals and limitations

Runs scripts

medium · line 51

The documentation asks the agent to run terminal commands or scripts.

node scripts/worktree-janitor.mjs --dry-run --json

Runs scripts

medium · line 54

The 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

Why each signal appears

EvidenceSourceComputedTestedEditorial
SignalValueEvidence typeMeaning
Quality score93/100ComputedDocumentation, specificity, maintenance, and trust rules
Repository stars14SourceRepository attention, not individual Skill quality
Compatibility0 platformsSourceDeclared in the catalog source record
Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

Pinned source

Provenance and original SKILL.md

Repository
OpenDigitalProductFactory/opendigitalproductfactory
Skill path
packages/dpf-skill-pack/skills/dpf-worktree-hygiene/SKILL.md
Commit
f795645f8fda2da7c172b32be5014f37517cee10
License
Apache-2.0
Collected
2026-08-28
Default branch
main
View the original SKILL.md

DPF Worktree & Sandbox Hygiene

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.

Ownership model (do not invert)

PathOwnerAgent 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 GCPortal Inngest (ops/worktree-janitor, ops/sandbox-build-gc) + env flagsNo — enable flags; do not hand-cron
Workroom reaper — transition dead workroom records (lease expired / build terminal / idle) workingabandoned; the DB half of hygiene (WS9)Portal Inngest (ops/taskrun-watchdog tick) + DPF_WORKCAPSULE_REAPER_* flagsNo — enable flags; observe via list_work_capsules staleOnly=true
Exceptional reclaim — live Tier-A bulk prune, force-delete locked dirs, kill locking shellsOperator + 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*.

Flags (install-local; not a PR)

FlagDefaultMeaning
DPF_WORKTREE_JANITOR_ENABLEDoffWhen 1, schedule scans (dry-run unless AUTO_REAP)
DPF_WORKTREE_JANITOR_AUTO_REAPoffWhen 1 and ENABLED, live Tier A only
DPF_SANDBOX_BUILD_GC_ENABLEDoffWhen 1, daily GC for terminal/orphan .builds/*
DPF_SANDBOX_BUILD_GC_DELETE_BRANCHESoffWhen 1 and ENABLED, also age-delete build/* past grace
DPF_WORKCAPSULE_REAPER_ENABLEDoffWhen 1, the taskrun-watchdog tick scans for dead Workrooms (observe-only unless AUTO_REAP)
DPF_WORKCAPSULE_REAPER_AUTO_REAPoffWhen 1 and ENABLED, live-transition dead workrooms workingabandoned (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.

When to use

  • 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.
  • Agent is tempted to rm -rf a worktree or git worktree remove --force by hand.

When NOT to use

  • Creating a new session worktree → dpf-worktree-per-session.
  • Normal PR finish of this branch's worktree → session SessionEnd reaper or dpf-finishing-a-development-branch.
  • Portal self-upgrade / version skew → self-upgrade path, not janitor.

Steps

A. Observe (always safe)

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).

B. Fleet soak (prefer over agent loops)

  1. Confirm flags on portal: docker exec dpf-portal-1 printenv | grep -E 'WORKTREE_JANITOR|SANDBOX_BUILD_GC'.
  2. If soak is desired: set ENABLED flags only (no AUTO_REAP) via install config; recreate portal.
  3. Periodic work is then Inngest, not the agent rereading this skill every hour.

C. Live Tier-A host prune (destructive — needs explicit go)

Only after operator says go (or equivalent) on a dry-run report:

node scripts/worktree-janitor.mjs --live --tier-a-only --json
  • Uses junction-safe remove under the hood.
  • Never use bare git worktree remove --force on Windows (BI-F6AC1A56).
  • Never target the root clone path.

D. Unregistered leftovers and fake worktrees (exceptional)

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:

  • It has no .git file, so every git command run inside it silently operates on the ROOT clonegit status, git commit, and git worktree list all report the root clone's state.
  • The path is gitignored, so any edit made there is uncommittable.

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:

  1. Scan junction targets first — do not assume. Junctions inside a worktree usually point within that same worktree (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.
  2. Identify lockers by process cwd (e.g. orphaned bash/powershell with cwd in the path) — do not kill unrelated Claude Desktop / codex host processes. Stop only those PIDs.
  3. 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>"
  1. An empty husk that still refuses to unlink (0 files, 0 dirs, 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.
  2. Require operator go if the agent cannot prove the process is abandoned session debris.

E. Sandbox GC one-shot (destructive — needs go)

Prefer flag-enabled scheduled GC. Manual one-shot only with go:

  • Remove orphan .builds/FB-* (no FeatureBuild row) or terminal phases.
  • Keep currently checked-out / non-terminal (review, building, …) branches.
  • git worktree prune inside sandbox if a dir was deleted out from under git.

F. Stale Workroom records (the DB half of the sprawl — WS9 / BI-CBAAEA94)

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.

  • Observe (always safe): list_work_capsules staleOnly=true — the reap-candidate set, each with a liveness verdict and the trueLivenessAt that proves it. No writes.
  • Governed reaper (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 workingabandoned.
  • DB-only, so junction-safe: the reaper never touches the filesystem. Reaping the workroom record does not delete the worktree — that stays for worktree-janitor / section C after its own explicit go. Abandon is reversible: re-promote the backlog item or re-adopt the branch.
  • A terminal build now abandons its attached workroom in the same watchdog tick (no zombie working workroom left behind).

Per-worktree caches and links that make a fresh worktree look broken

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 generatednever 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.

Guardrails

  • Dry-run default. Live reap is never the default agent action.
  • No freestyle rm -rf / mass process kill as hygiene.
  • Junction-safe only for worktree directory removal on Windows.
  • Hide complexity from lay users — report outcomes (how many reaped, flags on/off), not raw flag names unless the operator is in ops mode.
  • Primary reaper remains session hooks — this skill does not replace SessionEnd hygiene.

Output template

**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

See also

Frequently asked questions

What to verify before installation and use

What does the dpf-worktree-hygiene source document cover?

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.

How do I install dpf-worktree-hygiene?

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.

Which permission-related actions were detected?

Static rules flagged exec-script in the source; the page lists the matching lines and excerpts.