anthropics / anthropics/claude-code

[FEATURE] Fire WorktreeRemove (or a notification event) when Claude Code removes a git-backed worktree

Abierto
#94,212 0 comentarios 0 reacciones 0 asignados Ver en GitHub
area:hooks enhancement platform:macos
Lenguaje dominante
Python
Estrellas
145k
Forks
23.1k
Métricas de merge de PR
Métricas de PR pendientes

Descripción

Split out of #74708 at the maintainer's suggestion. That issue reported `WorktreeRemove` never firing at session exit; it was assessed as intended behavior with a docs problem ([comment](https://github.com/anthropics/claude-code/issues/74708#issuecomment-5303442963)):

> `WorktreeRemove` is the cleanup counterpart to `WorktreeCreate`: it only runs for worktrees that a `WorktreeCreate` hook created (for example SVN or Perforce working copies), where it *replaces* `git worktree remove` and is expected to delete the directory itself. Worktrees Claude Code creates with `git worktree` are removed by git directly and never run the hook [...] If you want a notification hook for git worktree removal, that would be a feature request rather than a bug in the current behavior.

This is that feature request. The docs have since been clarified ("For git-based worktrees, Claude Code handles cleanup automatically with `git worktree remove`"), so what remains is the missing capability, not the wording.

## Problem Statement

There is no way to run teardown when Claude Code removes a git-backed worktree.

`WorktreeCreate` is an *override* hook — it replaces `git worktree add` — but it is also the only documented place to do per-worktree setup, and the docs actively point at side effects there:

> If you need to copy local configuration files like `.env` into the new worktree, do it inside your hook script.

Setup that copies an `.env` is cheap to leak. Setup that provisions out-of-band resources is not. In my case the `WorktreeCreate` hook creates the git worktree *and* clones a per-worktree PostgreSQL database plus a `DATABASE_SUFFIX` env file for it. Every worktree session strands a database, because nothing fires on the way out.

Concretely, on 2.1.270 (macOS), in a fresh repo with `WorktreeCreate`, `WorktreeRemove` and `SessionEnd` hooks in the same `.claude/settings.json`, each appending its stdin to one log:

- `claude --worktree fr-probe -p ...` → `WorktreeCreate` fired, `SessionEnd` fired, `WorktreeRemove` did not.
- A subagent spawned with `isolation: "worktree"` → same tally: `WorktreeCreate` fired with `{"hook_event_name":"WorktreeCreate","name":"agent-ad7796546418a1f0f"}`, `WorktreeRemove` did not.

So the control is built in: the two worktree hooks sit in one file, one of them dispatches for a git worktree and the other never does. On the removal path itself, [the maintainer's own repro on 2.1.233](https://github.com/anthropics/claude-code/issues/74708#issuecomment-5303442963) shows the worktree and its branch being removed cleanly with no `WorktreeRemove` entry in the log.

Two things make this hard to work around rather than merely missing:

- `SessionEnd` runs while the session is still live and knows nothing about the worktree's fate — the exit prompt has not been answered yet, and on the subagent-finish path there is no session end at all.
- The failure is silent. `WorktreeRemove` output is debug-only and cannot block removal, so a cleanup script wired to it looks correct, passes review, and does nothing — worse than a hook that fails loudly.

The asymmetry is the core of it: the documented setup hook fires for git worktrees, the documented teardown hook does not. Third-party tooling has been built on the assumption that they pair (for example [rohansx/workz](https://github.com/rohansx/workz)), and it silently no-ops today.

## Proposed Solution

Dispatch a hook event when Claude Code removes a git-backed worktree, at each point where it removes one today: the `--worktree` exit prompt answered with "remove", a subagent with `isolation: "worktree"` finishing, `ExitWorktree`, and a background-session delete.

Preferred shape — **fire the existing `WorktreeRemove` event for git worktrees too**, with the payload it already documents:

```json
{
"session_id": "abc123",
"transcript_path": "/Users/.../*.jsonl",
"cwd": "/Users/...",
"hook_event_name": "WorktreeRemove",
"worktree_path": "/Users/.../my-project/.claude/worktrees/feature-auth",
"branch": "worktree-feature-auth"
}
```

with two differences from the non-git contract, both necessary to keep the current git path intact:

- **Notification, not override.** Claude Code still runs `git worktree remove` and still deletes the branch. The hook is told what happened; it does not become responsible for deleting the directory. A non-zero exit is logged, not treated as "removal failed" — otherwise adding the hook would change whether the worktree is removed at all.
- **Ordering.** Either before removal (so the hook can read files out of the worktree) or after, as long as it is documented which. Before-removal is more useful and matches the non-git contract; a `removed: true|false` field would let one script serve both.

Adding `branch` to the payload matters for the git case specifically: per-worktree resources are usually keyed by branch name, and `worktree_path` alone can't recover it once the worktree is gone.

If reusing the event is undesirable because the two contracts differ (override vs. notification), a separate event — `WorktreeRemoved`, say — with the same payload would serve just as well. What I need is *an* event; which name it has is secondary.

## Alternative Solutions

- **`SessionEnd` hook.** Fires too early and in the wrong place: it runs from inside the live session, before the keep/remove choice is resolved, and it does not know whether the worktree survived. For the subagent-`isolation: "worktree"` path there is no session end to hook at all.
- **Polling `git worktree list`.** What we do now, and what at least one other reporter in #74708 fell back to. It works, but it is a reconciler for an event that is documented to exist, it needs a schedule and a place to run, and it cannot distinguish "removed" from "user is mid-rebase".
- **Not using the hook for provisioning.** Means giving up per-worktree databases and doing setup by hand in every worktree, which is exactly what `WorktreeCreate` exists to avoid.
- **A wrapper that outlives the session.** Launching `claude --worktree` from a shell function that cleans up afterwards works for the interactive path, but nothing covers subagent worktrees, which are created and destroyed inside the session.

## Use Case Example

1. `.claude/settings.json` registers a `WorktreeCreate` hook. It runs `git worktree add`, clones the project's dev database into `picpac_`, writes `DATABASE_SUFFIX=` into the worktree's `.env.local`, and prints the worktree path.
2. `claude --worktree issue-1234` — the session gets an isolated checkout *and* an isolated database, so migrations and destructive specs in one worktree can't touch another.
3. Work finishes; `/exit`; the worktree is clean, so Claude Code removes it and deletes the branch.
4. **Today:** `picpac_issue-1234` is still there. It is still there after the next fifty worktrees too, and nothing in the session output ever said so.
5. **With this feature:** `WorktreeRemove` fires with `worktree_path` and `branch`, the hook drops the database, and the invariant "a worktree owns a database for exactly as long as it exists" actually holds.

The same applies, more sharply, to subagents with `isolation: "worktree"`: those are created and removed without any user interaction, so there is no manual moment at which cleanup could be attached.

## Additional Context

- Split out of #74708 (closed as intended-behavior + docs fix).
- Related but distinct: #92342 asks for a hook that fires late enough — after the session process has released the worktree — for *adopted* worktrees Claude Code did not create, which on Windows is what makes `git worktree remove` succeed at all. This request is narrower: worktrees Claude Code creates and removes itself, where removal already succeeds and the only thing missing is that anyone is told.
- Related: #36205 (`EnterWorktree` ignores both worktree hooks), #29716 (neither hook called in Claude Desktop).
- Version checked: 2.1.270 (macOS).

Guía de contribución

No hay ninguna guía de contribución indexada para este repositorio

Línea de trabajo

Trace the existing WorktreeCreate and WorktreeRemove dispatch, then follow the four removal paths named here: --worktree exit, subagent isolation, ExitWorktree, and background-session deletion. Use the .claude/settings.json hook configuration and the reported repro commands to verify that a notification is emitted with worktree_path and branch without changing git cleanup behavior.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
git, postgresql
Área
cli, devtools
Tipo de issue
Nueva funcionalidad
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Activo
Claridad
Bastante claro
Aptitud para principiantes
48/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.