[BUG] Hook-created non-git --worktree directory is removed on exit without a prompt, discarding changes

Status Closed — not planned
Reported on v2.1.288
Maintainer reply None cached
Activity 1 comment · opened Oct 3, 2026 · closed Oct 3, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version of Claude Code

What's Wrong?

When a WorktreeCreate hook creates a --worktree session directory that isn't a git repository, which is the documented use for the hook (SVN, Perforce, other VCSs), exiting the session removes it without asking, even when it holds changes.

On /exit, Claude Code shows no Keep/Remove prompt and calls the WorktreeRemove hook straight away. In my run the directory still held work.txt, standing in for an uncommitted edit. With the docs' own example WorktreeRemove hook (jq -r .worktree_path | xargs rm -rf), that edit is deleted.

For comparison, a hook-created directory that is a git worktree with an untracked file gets the Keep/Remove prompt on exit, and keeping it skips WorktreeRemove.

The change check for the exit prompt appears to run git status in the worktree, and in a directory outside any git repository that fails. Per a reading of the 2.1.288 source, that failure is treated as clean.

What Should Happen?

A worktree whose state Claude Code can't check should get the Keep/Remove prompt, as a git-created worktree does when its state "can't be verified" (worktrees.md, Clean up worktrees). It shouldn't be removed silently.

Error Messages/Logs

The hook log from my run:

WorktreeCreate .../checkouts/ng1
WorktreeRemove .../checkouts/ng1, contents: work.txt

Steps to Reproduce

Investigated and reproduced with an empty CLAUDE_CONFIG_DIR (no user hooks, plugins or CLAUDE.md) by Claude Code 2.1.288 using Opus 5.5.

Run this script to set up a temp repo, a WorktreeCreate hook that makes a plain directory outside any git repository with a file in it, and a WorktreeRemove hook that only logs. Then, from bash in a tmux pane (a pty), in <tmp>/repo:

CLAUDE_CONFIG_DIR=<empty config dir> claude --model haiku                  # accept workspace trust, then /exit
CLAUDE_CONFIG_DIR=<empty config dir> claude --model haiku --settings <tmp>/settings.json --worktree svn1
> /exit

and read <tmp>/hook.log. The /exit comes straight after startup, with nothing else done in the session.

For comparison, the same setup with a WorktreeCreate hook that makes a git worktree containing an untracked file shows the Keep/Remove prompt on /exit.

<details>
<summary>repro-nongit-exit.sh</summary>

#!/usr/bin/env bash
# Setup for: a hook-created non-git --worktree directory is removed on exit without a prompt.
set -o errexit -o nounset -o pipefail

dir="$(mktemp -d)"
git init -q -b main "${dir}/repo"
git -C "${dir}/repo" commit -q --allow-empty -m init
mkdir "${dir}/checkouts"

# Stands in for an SVN or Perforce checkout: a plain directory, outside any git repository,
# holding an uncommitted edit.  WorktreeRemove only logs, so nothing is actually deleted.
cat > "${dir}/hook.sh" <<'EOF'
#!/usr/bin/env bash
input="$(cat)"
log="$(dirname "$0")/hook.log"
case "$(jq -r .hook_event_name <<< "${input}")" in
  WorktreeCreate)
    checkout="$(dirname "$0")/checkouts/$(jq -r .name <<< "${input}")"
    mkdir -p "${checkout}"
    echo "uncommitted edit" > "${checkout}/work.txt"
    echo "WorktreeCreate ${checkout}" >> "${log}"
    echo "${checkout}" ;;
  WorktreeRemove)
    path="$(jq -r .worktree_path <<< "${input}")"
    echo "WorktreeRemove ${path}, contents: $(ls "${path}")" >> "${log}" ;;
esac
EOF
chmod +x "${dir}/hook.sh"
cat > "${dir}/settings.json" <<EOF
{ "hooks": {
    "WorktreeCreate": [{ "hooks": [{ "type": "command", "command": "${dir}/hook.sh" }] }],
    "WorktreeRemove": [{ "hooks": [{ "type": "command", "command": "${dir}/hook.sh" }] }] } }
EOF

cat <<EOF
Setup done in ${dir}. Then:

  cd ${dir}/repo
  claude                    # once, to accept the workspace trust dialog, then /exit
  claude --settings ${dir}/settings.json --worktree svn1
  /exit                     # at the prompt, without doing anything else
  cat ${dir}/hook.log
EOF

</details>

Claude Model

Not sure / Multiple models

Is this a regression?

I don't know

Claude Code Version

2.1.288

Platform

Anthropic API

Operating System

macOS 14.8.9 (arm64)

Additional Information

Found while investigating #99270, the WorktreeRemove hook never firing for hook-created subagent worktrees.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗