[BUG] Hook-created non-git --worktree directory is removed on exit without a prompt, discarding changes
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.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗