Skip to content
Back to skills

Worktree Lifecycle

BSecurity

Keep Chimaera's git worktrees and their Rust build output from filling the disk — when to remove your worktree, how to run scripts/worktree-gc (dry run, --apply, --self), what the SessionStart/SessionEnd hooks do, and the hard rule never to touch an ACTIVE worktree. Use when your PR has merged, when disk space is low or a session-start hook flagged it, before a big cargo build (just check, cargo test --workspace, an app build), or when asked to clean up worktrees or target/ dirs.

  • 3 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 29, 2026
ai-agentsrustgoshellbashnodetestinggit

Works with

  • claude code

Security analysis

B85/100
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned October 4, 2026

npx -y skills add martinappberg/chimaera --skill worktree-lifecycle --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Worktree Lifecycle?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Worktree Lifecycle
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/martinappberg-worktree-lifecycle-chimaera/badge)](https://www.skillsdirectory.com/skills/martinappberg-worktree-lifecycle-chimaera)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: worktree-lifecycle
description: Keep Chimaera's git worktrees and their Rust build output from filling the disk — when to remove your worktree, how to run scripts/worktree-gc (dry run, --apply, --self), what the SessionStart/SessionEnd hooks do, and the hard rule never to touch an ACTIVE worktree. Use when your PR has merged, when disk space is low or a session-start hook flagged it, before a big cargo build (just check, cargo test --workspace, an app build), or when asked to clean up worktrees or target/ dirs.
---

# Worktree lifecycle

Isolated agent sessions use their own git worktrees: Claude Code under
`.claude/worktrees/<name>`, the Codex app under `~/.codex/worktrees/<id>/chimaera`.
Each one grows its own cargo `target/` (the daemon workspace, plus
`crates/chimaera-app/target` and `plugins/target`), and single worktrees have reached
35–60 GB. On macOS most of that was stale debug objects: a dev build keeps each
executable's object files in `target/debug/deps` for the debugger, and every
incremental rebuild writes new ones without deleting the old. Left alone, worktrees
filled the maintainer's disk to 99%.

`scripts/worktree-gc` sorts every worktree of the repo (`git worktree list`) into one
group and prints why:

| Group | Meaning | What `--apply` does |
|---|---|---|
| **ACTIVE** | a process has its cwd or an open file inside it, it is `git worktree lock`ed, the script runs from it, or something in it changed in the last 6 h (unless its PR is merged — see REMOVE) | nothing, ever |
| **PREVIEW** | clean and pushed, HEAD belongs to a merged PR (not already in main), and only recognized dev previews and their children use it; no lock, current session, or nested worktree | sends SIGTERM to the previews, waits up to 10 seconds, re-checks processes and git state, then removes the worktree; never SIGKILL |
| **REMOVE** | PR merged or closed (or HEAD already in `origin/main`), no uncommitted changes, no unpushed commits. A merged PR skips the 6 h wait when the checkout *is* that PR: its own commits (not in `origin/main`) all sit in the PR's head | `git worktree remove` (never `--force`) + `git worktree prune`; the branch is kept |
| **TRIM** | not removable, idle for more than 24 h | deletes its git-ignored `target/` and `node_modules/` dirs; source is never touched |
| **KEEP** | everything else (an open PR idle < 24 h, unpushed commits, uncommitted changes) | nothing |

PREVIEW is the narrow exception to process-based activity: the executable must
be this checkout's `target/debug/chimaera serve`, its isolated `chimaera-dev`
app/daemon, or Node running this checkout's Vite entrypoint from `web-ui`.
The documented `npm run dev`, `npm --prefix web-ui run dev`, `npm exec vite`
and `just dev-ui` launcher chains are recognized too, as is `just app-dev-isolated`,
including their dedicated shell wrappers. Compound shell commands and wrappers with other children stay
protected. Daemon-owned test
agents shut down through their parent; GC never kills arbitrary `node`, `claude`,
`codex`, shells, or editors by name. An unrelated process, a preview touching a
sibling checkout, or an unrecognized wrapper keeps the checkout ACTIVE. A
verified launcher chain stops from leaf to root, waiting for each child to be
reaped before stopping its wrapper, within one shared ten-second deadline. A
preview that will not exit, or work written during shutdown, keeps the checkout
intact. Dry runs report PREVIEW without stopping anything; `--apply` and the
automatic session-start cleanup perform the guarded shutdown.

It judges a worktree only by its branch, that branch's PR (`gh`), git state and live
processes (`lsof`, or `/proc`) — **never by its folder name**. Both apps reuse folders
for unrelated sessions, so `.claude/worktrees/chimera-pro-cloud-…` may hold a
branch about something else entirely.

Why merged skips the wait: merged, clean and pushed leaves nothing in progress, and
archiving a session in the Claude app detaches its branch — a fresh `HEAD` and
reflog write that otherwise reads as "changed 0m ago" and holds the folder for 6 h.
A detached worktree finds its PR by commit, or through a local branch still pointing
at its HEAD (the PR's head may have moved on since the checkout last pushed). A
closed PR (it may be reopened) and a fresh worktree whose HEAD is in main still
wait — so do merged PRs in a repo that merges with merge commits, where the PR's
commits land in main. The cost: a merged session you return to after its folder
went has no worktree.

## The lifecycle

1. **Before a big build**, check `df -h ~`. Under ~50 GiB free (the session-start
   hook says so when it is), run `scripts/worktree-gc` and read the table. Its
   `--apply` only ever acts on PREVIEW, REMOVE and TRIM, which the hooks already apply
   automatically, so running it is safe; nothing else is yours to delete.
2. **After your PR merges**, your worktree should go. A session can't remove the
   worktree it runs in (it is ACTIVE: "this session runs here"), so:
   - stop every preview you started for this PR: the isolated app and daemon,
     Vite, and any preview launcher. Use the preview tool's stop action when
     available; otherwise verify the exact PID and executable, send SIGTERM,
     and wait for exit. Let daemons shut down their own child agents. Never
     use a broad process-name kill or stop another session's processes;
   - drop its build output now with `scripts/worktree-gc --self --apply`;
   - end the session. The next gc run (any session's start, at most hourly) removes
     the worktree once nothing uses it — merged, clean and pushed is REMOVE;
     recognized leftover previews are PREVIEW and shut down first — unless
     automatic cleanup is off on that machine (`worktree-gc.auto`, below); then say
     so and leave the removal to the human.
   - Working from another checkout? `git worktree remove <path>` — never `--force`,
     and never on a worktree the table calls ACTIVE.
   Treat this as part of finishing the PR, not optional housekeeping. If a
   preview does not exit gracefully or another session keeps the worktree
   ACTIVE, report the blocker and leave it intact. The next automatic GC is
   a fallback for leftover previews, not a reason to keep yours running.
3. **At session end** the SessionEnd hook runs `--self --sweep`: it deletes only the
   stale debug objects no binary in `target/` references, so the next build stays warm.
   `--self` without `--sweep` deletes this worktree's whole `target/` (the next build
   starts cold) and refuses while anything runs from it.

## Never

- touch an ACTIVE worktree: no `rm -rf` of its `target/`, no `git worktree remove`,
  no unlocking a lock you didn't take. Codex app-servers keep their cwd in a worktree
  for hours. Only the script's explicit PREVIEW group may stop recognized dev
  previews, after verifying the checkout is clean, pushed and merged.
- `git worktree remove --force`, delete branches, or clean another worktree by hand —
  run the script, which re-checks each worktree right before acting.
- trust `du` for what a delete frees. Cargo hardlinks objects between `deps/` and
  `incremental/`, so `du` of the parts overcounts. `--apply` reports the change in
  `df`'s free space instead.
- keep anything you need in a worktree's git-ignored files: REMOVE deletes the whole
  folder, and only committed-and-pushed work survives it.

## Commands

```sh
scripts/worktree-gc                    # dry run: the table + what --apply would do
scripts/worktree-gc --markdown         # the same, as a markdown table (PR bodies)
scripts/worktree-gc --apply            # stop PREVIEWs, remove REMOVE, trim TRIM; reports df before/after
scripts/worktree-gc --self [--sweep] [--apply]   # this worktree's own target/
bash scripts/worktree-gc.test.sh       # the safety rules, against a throwaway repo
```

Knobs: `git config worktree-gc.auto false` stops the hooks from deleting anything
(they still flag low disk; `git config --get worktree-gc.auto` shows whether a
machine opted out), `WORKTREE_GC=off` silences them entirely, and
`WORKTREE_GC_ACTIVE_HOURS` (6), `WORKTREE_GC_IDLE_HOURS` (24), `WORKTREE_GC_LOW_GB`
(50), `WORKTREE_GC_INTERVAL_MIN` (60) tune them. It acts only on the repo it lives
in, whatever the cwd. It fails safe: without `lsof`/`/proc`, or when it can't read a
worktree's files, that worktree is ACTIVE; without `gh` nothing is removable by PR
state.

## Hooks

- **Claude Code** (`.claude/settings.json`): SessionStart runs `--hook session-start`
  — prints one line into context when disk is low, and starts a detached
  `--apply` run at most once an hour. SessionEnd runs `--hook session-end` — a
  detached `--self --sweep --apply` (skipped on `/clear`).
- **Codex** (`.codex/hooks.json`): the same two hooks, in Codex's documented format.
  Codex runs project hooks only in a trusted project, after each is approved in
  `/hooks` — and it records that approval per `hooks.json` path, so each Codex worktree
  may ask again. `codex exec` 0.157.1 ran no project SessionStart hook at all in
  testing. So for Codex, treat this skill and the AGENTS.md rule as the mechanism
  and the hooks as a bonus.
- Background runs log to `<main checkout>/.git/worktree-gc/log`.

## Why the build is smaller now

The dev profile (root `Cargo.toml`, mirrored in `crates/chimaera-app/Cargo.toml`)
builds workspace crates with `debug = "line-tables-only"` and dependencies with
`debug = false`: backtraces and debugger breakpoints keep file:line, but locals are
not inspectable. When you need them, rebuild with full debug info:
`CARGO_PROFILE_DEV_DEBUG=full cargo build`.

On macOS, `export CARGO_PROFILE_DEV_SPLIT_DEBUGINFO=packed` in your own shell stops
the stale-object pile-up at the source (dsymutil writes a `.dSYM` per binary and the
objects are deleted). It is not the repo default: the profile key would change Linux
builds too, and a macOS-only rustflag would silently disable any `build.rustflags`
you set yourself.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.