Preventing Claude Code Worktree Auto-Delete with git worktree lock

Published · AI Daily — AI-assisted deep research, methodology & disclosure

This article analyzes the internal cleanup logic of Claude Code (v2.1.263) for worktrees created with the `--worktree` flag. When a session ends with no pending changes and no new commits, the worktree and its branch are silently deleted. By examining the bundled source code, the author reveals the exact conditions that trigger this removal and explains how Claude Code uses `git worktree lock` with a specific reason format to track its own sessions. The key finding is that any lock with a reason not matching the pattern `/^claude (?:agent|session) .{1,255} \( pid (\d{1,10})(?: start (.{1,255}))? \)$/` will be considered as an external lock and thus protected from cleanup. Practical solutions include manually unlocking and relocking the worktree with a custom reason, or automating this via a `SessionStart` hook script. This approach provides a reliable defense against accidental data loss while maintaining the flexibility of Claude Code's worktree workflow.

Background & Problem

Claude Code’s `--worktree` flag launches a session inside a dedicated Git worktree, isolating experimentation from the main workspace. However, a silent deletion trap awaits: when you run `claude --worktree feature-x`, perform some exploratory browsing, make no permanent changes, and then exit with `/exit`, the worktree and its associated branch vanish without any confirmation prompt. Many developers assume that as long as they leave the worktree “clean,” it will be preserved—only to later realize the entire environment, including terminal history and conversation context, has been wiped.

The official documentation confirms this is intentional: unnamed sessions with zero uncommitted changes and zero new commits are automatically cleaned up; named sessions present a “Keep/Remove” dialog. There is no configuration option to disable this behavior globally. This means every read‑only investigation or trial-and-error session that reverts all changes is at risk. Multiple GitHub issues (e.g., #27753, #46444, #58432) have been raised and closed as “expected behavior,” underscoring the community’s dissatisfaction with the design’s lack of user control.

Architecture & Mechanism

The cleanup logic is embedded in Claude Code’s bundled JavaScript and can be reverse‑engineered. On `/exit`, the tool performs two Git state checks. First, `git status --porcelain` detects any unstaged modifications or untracked files. Second, `git rev-list --count <session-start-HEAD>..HEAD` counts new commits made during the session. The combination determines three outcomes:

  • Both zero **and** session unnamed → silent deletion.
  • Both zero **but** session named → “Keep/Remove” dialog.
  • Either value non‑zero → dialog regardless of naming.

The critical case is the first: zero changes, zero commits, unnamed session. This is exactly the pattern of a “look but don’t touch” investigation.

Claude Code itself uses `git worktree lock` to manage session locks. On session start, it writes a lock reason with a strict format:

`claude session <name> (pid <PID> start <timestamp>)`.

On exit, it matches the lock reason against the regex

`/^claude (?:agent|session) .{1,255} \( pid (\d{1,10})(?: start (.{1,255}))? \)$/`.

If the reason does **not** match—for example, because you replaced it with a custom string—Claude Code treats the worktree as “locked by another process” and skips the cleanup. This is Git’s own safety net: the `sweep` command never releases a lock you placed yourself.

Benchmarks & Practical Impact

Reproducing the issue is straightforward. In a test repository, run `claude --worktree wt-clean`, immediately exit with `/exit`. The terminal briefly displays `Cleaning up worktree (no pending changes)…`, then the worktree directory and its branch are gone. `git worktree list` now shows only the main repository. The entire deletion takes under one second—there is no window to intervene.

The defensive fix leverages the lock mechanism. While the session is active, open another terminal (or use `!` inside Claude Code to run a shell command) and execute:

git worktree unlock .claude/worktrees/<name>
git worktree lock --reason "pinned by ryan" .claude/worktrees/<name>

The unlock must precede the lock because an existing lock blocks a new one. After re‑locking, `/exit` fails the regex match, so Claude Code leaves the worktree intact. Verification shows the worktree remains listed with `locked` status, and subsequent `claude --worktree wt-lock` sessions can reuse it normally.

For a fully automated workflow, create a `SessionStart` hook in `.claude/hooks/` (e.g., `pin-worktree.sh`). The script uses `git rev-parse --git-dir` and `--git-common-dir` to detect whether the current directory is a linked worktree (excluding the main repository). If so, it runs unlock then lock with a custom reason. This runs every time a session starts, ensuring protection without manual intervention.

#!/bin/bash
# pin-worktree.sh: auto-lock worktree on session start
if [ "$(git rev-parse --git-common-dir)" != "$(git rev-parse --git-dir)" ]; then
wtpath=$(git rev-parse --git-dir)
git worktree unlock "$wtpath" 2>/dev/null
git worktree lock --reason "pinned by ryan" "$wtpath"
fi

This script is trivially portable and can be included in project initialization templates.

Outlook & Industry Implications

The core issue reflects a mismatch between the tool’s automation logic and the user’s mental model. Developers treat worktrees as self‑contained development containers—even without Git changes, the session’s exploration state, terminal history, and chat context hold value. Claude Code’s single criterion of “no Git change” ignores the worktree’s role as an environment capsule.

From an engineering perspective, Claude Code’s lock mechanism inadvertently provides a flexible escape hatch. While the official stance offers no configuration toggle, the Git‑native lock override achieves equivalent protection. This highlights a broader design principle: powerful automation should always leave a controllable escape. Future versions could introduce a `--keep-worktree` flag or a `worktreeCleanupPolicy` config option to solve the problem at its source. The community’s proposal for a `WorktreeRemove` hook also deserves attention—it would allow users to inject custom pre‑removal checks.

For teams that rely heavily on Claude Code, incorporating the worktree locking script into the project’s onboarding template is a practical, low‑overhead step. It prevents accidental loss of temporary workspaces and avoids the collaboration chaos caused by missing branches. Small technical tweaks like this can substantially improve team‑wide development efficiency.

Sources

FAQ

How does Claude Code trigger automatic worktree cleanup?

When exiting a --worktree session with no changes, no new commits, and an unnamed session, the worktree and branch are silently deleted without prompt.

How to prevent worktree deletion?

Use `git worktree lock` to lock the worktree, or leverage Claude Code's SessionStart hook to automatically unlock and then lock the worktree.

Is there a configuration option to disable auto-cleanup in Claude Code?

The official documentation acknowledges it as a design feature and currently lacks a config switch; only external workarounds like manual locking are possible.