Skip to main content

ops-worktrees

Git worktrees as leases that expire, with unmerged work surfaced and archived recoverably instead of stranded. Use when worktrees have accumulated, when a ticket close is blocked by the worktree retention audit, when work may have been left unmerged in a worktree, or to schedule automatic reclamation. Also owns scoped landing: commit and push ONLY named files onto origin/main from any dirty checkout, worktree, or branch via `run.sh land`.

Informações da origem

Repositório
grahama1970/agent-stack-public
Última atividade na origem
24 de setembro de 2026 às 15:51
Idioma detectado do SKILL.md
inglês
Estrelas
0
Forks
0

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Explorador de arquivos
9 arquivos

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
ops-worktrees
description
Git worktrees as leases that expire, with unmerged work surfaced and archived recoverably instead of stranded. Use when worktrees have accumulated, when a ticket close is blocked by the worktree retention audit, when work may have been left unmerged in a worktree, or to schedule automatic reclamation. Also owns scoped landing: commit and push ONLY named files onto origin/main from any dirty checkout, worktree, or branch via `run.sh land`.
triggers
["commit and push","commit and push only relevant files","land these files on main","push to main from a worktree","scoped commit","git commit push","gcp","worktree sprawl","clean up worktrees","too many worktrees","worktree audit failed","did I forget to merge","unmerged work","stranded commits","archive worktree","reap worktrees"]
allowed-tools
Bash
metadata
{"short-description":"Worktree leases, unmerged-work detection, recoverable archive"}
provides
["ops-worktrees"]
composes
["cleanup","agentic-evals"]
disciplines
["developer-tooling","observability-operations"]
# ops-worktrees Worktrees accumulate because creation is automated and reclamation is not. In this repo three skills run `git worktree add` and none has ever run `git worktree remove`; `cleanup_worktree.py` and `audit-worktrees.sh` both classify and explicitly delete nothing, and no cron ran either. The count could only go up: 183 registered, 8 created in a single day. Two distinct losses come out of that, and they need different fixes. **Sprawl** is worktrees nobody reclaims. **Stranding** is work that was never merged — commits sitting in a worktree that no branch name leads back to. The second is the expensive one, and refusing to delete it does not fix it: the work is not deleted, it is simply never found again. ## A dirty tree is not a finding Stop re-deriving this. In `~/workspace/experiments/agent-skills`, the primary checkout, `git status` is expected to be dirty and local `main` is expected to be diverged. Measured 2026-08-14: 192 ahead / 332 behind `origin/main`, 375 files differing from local `HEAD`. That is the normal steady state, not damage and not something to reconcile. The reason: work lands on `origin/main` by plumbing (`read-tree` -> `update-index` -> `write-tree` -> `commit-tree` -> `push <sha>:main`) because several cron lanes write tracked files in this same checkout mid-run. Local `HEAD` is therefore stale by design, and every file that moved on the remote shows as modified or deleted locally. So a bare `git status` answers a question nobody asked. The only question that matters is whether the files YOU touched differ from the remote: ```bash git fetch -q origin main git diff --stat origin/main -- <the paths you edited> git log --oneline origin/main..HEAD -- <the paths you edited> # must be empty ``` Rules: - Do not `git add -A`, `git commit -a`, `git stash`, `git checkout .`, or `git reset --hard` in this checkout. Another lane's uncommitted state is in there, and a running job may be mid-write. - Do not "clean up" untracked files. `sparta_metrics.py` was untracked, a cleanup swept it away, and ArangoDB health checks failed for five days. - Do not report tree dirtiness as a problem, a blocker, or a finding. Report it only if a file you were asked to change is unexpectedly modified by something else. - Generated bulk is ignored, not deleted: `skills/dogpile/local/search-runs/` (62,265 files), `skills.pre-symlink-*/`, `site/.next.stale-*/`. That cut the untracked count from 67,807 to 475. An agent reading a 67,807-line status learns nothing and starts guessing. If the tree looks alarming, run the audit and read its verdict instead of inferring one: ```bash skills/ops-worktrees/run.sh audit --json ``` ## Agent dispatch: worktrees are allowed again, via `wt` only The worktree ban existed because agents forget the finish steps (merge, remove, branch delete) 100% of the time. The fix is not remembering — it is making cleanup nobody's job but the cron's. - **Create/switch/finish through [worktrunk](https://github.com/max-sixty/worktrunk)** (`wt`, installed at `~/.local/bin/wt`, v0.75.0). `wt switch -c <branch>` to dispatch, `wt merge main` or `wt remove` to finish. Raw `git worktree add` stays banned. - **Agents are not trusted to clean up.** The scheduler job `worktree-reap` runs `scripts/reap_worktrees.sh` hourly (`15 * * * *`) with `WORKTREE_REAP_APPLY=1 WORKTREE_GRACE_DAYS=3`: landed worktrees are removed, unmerged work is bundle-archived, active/dirty/in-TTL trees are kept. An agent that forgets its worktree loses nothing and strands nothing. - **Merged-ness caveat in this repo:** local `main` is permanently diverged (plumbing pushes), so `wt list`'s `main_state` reads `would_conflict` for everything. The reaper's landed check uses `@{upstream}..HEAD` / `origin/main..HEAD`, not `wt`'s local-main comparison. Trust the reaper's classification, not `wt list` merged-ness, here. ## Commands ```bash skills/ops-worktrees/run.sh land -m "msg" <path>... # scoped landing on origin/main skills/ops-worktrees/run.sh unmerged # what work never reached origin/main skills/ops-worktrees/run.sh reap # preview: remove / archive / keep skills/ops-worktrees/run.sh reap --apply # act on it skills/ops-worktrees/run.sh archive <path> # archive one worktree skills/ops-worktrees/run.sh backlog # classify pre-lease worktrees skills/ops-worktrees/run.sh register <path> --purpose <why> ``` ## Scoped landing: `land` `run.sh land -m "message" <paths...>` is the ONLY sanctioned way for an agent to commit-and-push. It lands exactly the named paths onto `origin/main` by plumbing (`read-tree origin/main` -> scoped `add` -> `write-tree` -> `commit-tree -p origin/main` -> `push <sha>:main`), from any dirty checkout, worktree, or branch, without switching branches, stashing, or staging anything else. It refuses repo-wide pathspecs (`.`, `-A`, `*`), retries push races, and verifies the commit is an ancestor of `origin/main` before reporting success. After landing from a worktree, do NOT merge or delete the worktree yourself — the hourly reaper classifies it as landed and removes it. There is no separate "git commit push" skill; this command plus the reaper is the whole contract. ## Three dispositions, never two A two-way remove/keep split sends everything uncertain into `keep`, which preserves nothing — the work stays unfindable and the sprawl stays. | disposition | when | | --- | --- | | `remove` | provably clean, landed, lease expired, owner process gone | | `archive` | dirty, unmerged, unprovable, or unregistered past the grace period | | `keep` | owner PID alive, or inside its TTL / grace | ## Archiving preserves; moving does not A moved directory is not preservation — whoever deletes that folder later deletes the work. Archiving: 1. WIP-commits dirty files, so uncommitted work is preserved **in git** rather than in a tarball nobody will open. 2. Bundles the unmerged commits with `git bundle`, restorable into any clone independently of the directory. 3. **Verifies the bundle**, then unregisters. Verifying afterwards means discovering the bundle was empty once the tree is already gone. 4. Writes `MANIFEST.json` carrying the exact restore command. Bundle the **branch**, never `origin/main..HEAD`: a range ending in HEAD records no named ref, so the bundle verifies and then cannot be fetched by name. A verified archive nobody can restore is worse than no archive. Restore: ```bash git -C <repo> fetch /mnt/storage12tb/worktrees/deprecated/<name>/<name>.bundle \ <branch>:recovered/<branch> ``` ## Every rule is a refusal Deleting an unpushed day of work is unrecoverable in a way that leaving a directory is not, so the reaper defaults toward keeping: - a live owner PID is never touched — work in flight - a dirty tree is never removed — uncommitted changes are the work - commits not reachable from the remote are never removed — `git status` cannot see committed-but-unpushed work - inside the TTL is never touched — a job between steps looks identical to one that finished - unregistered inside the grace period is never touched — no owner is recorded, and something created two days ago may still be in use This matters concretely: of 181 pre-lease worktrees, 48 were dirty and 9 held unpushed commits. An age-based "delete old worktrees" script would have destroyed all 57. ## Leases Whoever creates a worktree records owner PID, purpose and TTL, so a later reaper knows whether the work is abandoned. `project-watchdog` and `orchestrate` register automatically. Registration failure never fails the work that needed the worktree. ## Scheduling `scripts/reap_worktrees.sh` previews by default; set `WORKTREE_REAP_APPLY=1` to act. Without a schedule the count only grows — that absence is the original bug, not an oversight to repeat. ## Proof boundary Tests run against a real repository with a real remote, because unpushed work is undetectable without one. The archive tests assert the work comes **back**: a committed file and a never-committed file are both fetched out of the bundle. Preserving is only real if the work returns.
Ver no GitHub