Skip to content

A locked worktree says why before it is deleted, and auto-clean leaves it be - #217

Open
DrewBradfordXYZ wants to merge 1 commit into
AgentSystemLabs:mainfrom
DrewBradfordXYZ:worktree-lock-reason
Open

DrewBradfordXYZ wants to merge 1 commit into
AgentSystemLabs:mainfrom
DrewBradfordXYZ:worktree-lock-reason

Conversation

@DrewBradfordXYZ

Copy link
Copy Markdown
Contributor

One commit on main (d56195b). It follows up on #170/#183 (the DELETE CHECK): when that merged, you said the follow-ups could go against main. This is the small one, and worktree-safe-to-delete stacks on it.

Why

git worktree lock --reason … is git's way of saying "keep this checkout". Git's own worktree remove and worktree prune refuse a locked worktree. People lock checkouts, and so do agents and tools. One example is a CI or merge-queue tool that reuses a scratch worktree for every run: it locks it so nobody clears it in between.

nebula never showed a lock. Its delete unlocked any lock it found and went ahead. So a locked checkout looked like any other clean one, and one d removed it. Auto-clean merged worktrees could remove one without asking anyone.

What

  • The delete confirm leads with the lock. The DELETE CHECK reads it (git worktree list --porcelain), and the confirm's first line is ⚠ locked: <reason> — deleting unlocks it, or no reason given. A bulk delete's line says locked: <reason>. Nothing is assumed about who locked it or whether the lock is stale: the person reads the reason and decides.
  • Auto-clean skips a locked checkout and says so: auto-cleanup skipped <branch>: checkout is locked (<reason>).
  • Docs: docs/sessions.md (what the confirm lists), docs/keys.md and docs/configuration.md (auto-clean).

Protocol

Protocol 53, additive; MIN_COMPATIBLE_PROTOCOL is unchanged. WorktreeCheck::locked is its last field and stays off the wire when it is None, so a check of an unlocked worktree is byte for byte what it was. The daemon records each client's Hello version and clears the lock for a client older than 53. Compact MessagePack frames are arrays, so an older client fails on a field it doesn't know; serde(default) alone doesn't help in that direction.

53 assumes #174 takes 52. If #174 or #178 lands after this, or this lands first, the number moves to the next free one. It is one constant (WORKTREE_LOCK_PROTOCOL) plus PROTOCOL_VERSION.

How verified

  • cargo fmt --all --check and cargo clippy --workspace --all-targets -- -D warnings are clean, and cargo test --workspace passes 1,942 tests on macOS.
  • Tests: the git worktree list --porcelain parser picking the target stanza's lock and reason, C-style quoted reasons included; the lock read back from a real git worktree lock (git.rs); auto-clean skipping a locked checkout (registry.rs); the confirm leading with the lock and its reason (delete_check.rs); and a lock costing an older client nothing on the wire (protocol.rs).
  • Used daily since 2026-10-21 in a build of nebula with this patch, on a repo whose merge-queue tool locks its own probe worktree: its delete confirm leads with the tool's reason, and auto-clean leaves it.

…s it be

`git worktree lock --reason …` is git's way to say "keep this checkout":
git's own `worktree remove` and `prune` refuse a locked one. nebula never
showed a lock, and its delete unlocked any lock it met and went ahead, so
a checkout a person, an agent or a tool had locked looked like any other
clean one and went with one `d`. Auto-clean merged worktrees could take
one with no one asked at all.

- The DELETE CHECK reads the lock (`git worktree list --porcelain`) and the
  confirm leads with it: `⚠ locked: <reason> — deleting unlocks it`, or
  `no reason given`. A bulk delete's line says `locked: <reason>`. Nothing
  is assumed about who locked it or whether the lock is stale: the person
  reads the reason and decides.
- Auto-clean merged worktrees skips a locked checkout, with
  `auto-cleanup skipped <branch>: checkout is locked (<reason>)`.
- Protocol 53, additive. `WorktreeCheck::locked` is its last field and is
  left off the wire when None, so an unlocked check is byte for byte what
  it was; the daemon records each client's `Hello` version and clears the
  lock for a client older than 53.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant