[BUG] Desktop resume restores a worktree without a current session lock during Bash execution (2.1.295)
Summary
In Claude Code 2.1.295 inside Claude Desktop, a session resumed in the worktree created by EnterWorktree, with the same session ID and a new process. Its Git worktree lock still named the previous, exited process. Later the lock disappeared without a manual lock change.
This is a report about the native lock lifecycle. A repository hook that requires positive native ownership evidence refused writes after resume; that refusal is repository policy, rather than a native Write error.
Environment
- Claude Code: 2.1.295, bundled native executable.
- Surface: Claude Desktop Code, local session.
- Desktop version initially recorded: 2.31226.1. The UI version was not separately captured after restart.
- OS: macOS 27.0.1, arm64.
- Permission mode: Auto. Sandbox was off in the initial status.
- The observed repository is takasek/pfdsl. Repository hooks remained enabled.
Observed sequence
- Start a local Desktop session. Use EnterWorktree once with a new name, creating a Git worktree under the repository's .claude/worktrees directory.
- In that worktree, ordinary Write and normal add/commit succeed, including a foreground subagent. Remove the test files and commit the removals. Keep the worktree and its local commits.
- Close and reopen the same Desktop conversation, then restart the Desktop app. Do not call ExitWorktree, recreate the worktree, or edit its Git metadata.
- Reopen the same conversation. The session ID is unchanged; the process has changed from PID 47148 to 63947. The old process no longer exists.
- The resumed process and an actual Bash pwd both point at the original native worktree. Its HEAD, branch, and clean state are preserved.
- Git worktree list and the Git metadata lock file still name the old process. The saved record was:
claude session <worktree-name> (pid 47148 start Tue Oct 13 04:32:56 2026)
The new process start was Tue Oct 13 08:35:30 2026 UTC. A second resumed Desktop session showed the same old-lock pattern.
- A later read found both lock files absent. We did not unlock, relock, remove, or alter either worktree. We did not identify the actor that removed the locks or the exact removal instant.
- The repository hook refused one own Write after resume. A separate, temporary diagnostic observer captured the same session ID, native target/cwd, primary CLAUDE_PROJECT_DIR, and direct parent PID 63947. It emitted no permission decision. The repeated diagnostic Write was also refused; the observer was removed and local settings restored byte for byte.
- In the same resumed session, a read-only git -C <primary-checkout> status --short was refused by the native worktree-isolation check, naming the correct native worktree. The refusal did not have the repository's PreToolUse hook prefix. The primary Git read did not run. This shows isolation enforcement for this command after resume; it does not establish every native protection or the ordering of all checks.
All refused writes left no file. No worktree was removed, no data loss was observed, and no guard bypass was used. Paths and session identifiers in this report are redacted; process numbers are from the observation.
Expected behavior
A resumed worktree session should maintain current native ownership/liveness evidence, preferably by reacquiring its normal Git lock with the resumed process identity. If it cannot reacquire the lock safely, an explicit failure is preferable to an apparently restored session without current lock ownership.
The worktree documentation describes a lock while an agent is running. In two subsequent, separate Bash calls inside the resumed session, echo "$PPID" returned 63947 and git -C <absolute-own-worktree> worktree list --porcelain returned the correct target block with no locked line. Both tools succeeded. This is an in-session tool-execution observation, separate from the earlier external process-liveness reads.
Evidence limits
The hook observer did not record the simultaneous lock contents or the repository guard's internal ownership facts. External reads show the stale-then-absent lock sequence; they do not prove which native cleanup path changed it.
The saved transcript still contains a worktree-state for the native path. That is a persisted resume binding, not proof of the current active assignment: the hook reference explicitly says transcript writes are asynchronous.
This report does not establish a previous working version, a CLI reproduction, future-version behavior, or a general isolation bypass.
Related
#93231 reports stale locks after VS Code shutdown on Windows. This observation adds a macOS Desktop conversation that resumed with a live new process while its lock still named the old one, followed by lock disappearance. I cannot establish whether they share an implementation cause.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗