Summary
verifyZeroOwnedWorktree in internal/worktrees/worktrees.go proves a worktree is Zero-owned using two signals that a user can reproduce by hand: a predictable zero-worktree-<repoKey> ancestor directory name, and a lock reason starting with the zero: active task worktree prefix. A worktree created under that directory and manually locked with a matching reason passes the ownership check, so zero worktrees release will clear that lock, and a subsequent zero worktrees clean can then force-remove the checkout.
Raised by jatmn's review on PR #632 (#632 (comment)); not fixed there because it needs a migration decision first (see below).
Affected code
internal/worktrees/worktrees.go (verifyZeroOwnedWorktree, the strings.HasPrefix(entry.lockReason, leaseReasonPrefix) check)
Current behavior
Ownership is inferred entirely from a directory-name convention and a lock-reason string prefix, both fully known and reproducible by anyone reading the source or the lock reason on an existing Zero worktree.
Expected behavior
release and clean should only be able to act on worktrees Zero itself created, verified through something a user can't trivially reproduce by hand.
Why this wasn't just fixed alongside the other findings
internal/worktrees has been on main since #70, well before the PR that raised this. Any fix that makes a new ownership marker mandatory risks rejecting worktrees an already-installed zero created before the marker existed, breaking release/clean for them on upgrade. This needs a migration story, not just the check itself.
Suggested implementation
- Write a durable ownership marker when
Prepare creates a worktree, e.g. a file inside the worktree's own git administrative directory (not the visible working tree, so git worktree remove cleans it up automatically and it never appears as an untracked file).
- Decide the migration path for worktrees created before the marker existed: a grace period that accepts the current prefix-only check with a deprecation warning, a versioned marker, or similar. Don't make the marker mandatory on day one.
- Update
verifyZeroOwnedWorktree to check the marker once the migration window has been decided.
Suggested tests
- A worktree with a valid marker releases successfully.
- A worktree under the predictable directory with only a matching lock-reason prefix but no marker is handled per whatever migration behavior is chosen (accepted with warning, or rejected, depending on the decision above).
- A worktree with a mismatched/corrupted marker is rejected.
Summary
verifyZeroOwnedWorktreeininternal/worktrees/worktrees.goproves a worktree is Zero-owned using two signals that a user can reproduce by hand: a predictablezero-worktree-<repoKey>ancestor directory name, and a lock reason starting with thezero: active task worktreeprefix. A worktree created under that directory and manually locked with a matching reason passes the ownership check, sozero worktrees releasewill clear that lock, and a subsequentzero worktrees cleancan then force-remove the checkout.Raised by jatmn's review on PR #632 (#632 (comment)); not fixed there because it needs a migration decision first (see below).
Affected code
internal/worktrees/worktrees.go(verifyZeroOwnedWorktree, thestrings.HasPrefix(entry.lockReason, leaseReasonPrefix)check)Current behavior
Ownership is inferred entirely from a directory-name convention and a lock-reason string prefix, both fully known and reproducible by anyone reading the source or the lock reason on an existing Zero worktree.
Expected behavior
releaseandcleanshould only be able to act on worktrees Zero itself created, verified through something a user can't trivially reproduce by hand.Why this wasn't just fixed alongside the other findings
internal/worktreeshas been onmainsince #70, well before the PR that raised this. Any fix that makes a new ownership marker mandatory risks rejecting worktrees an already-installedzerocreated before the marker existed, breakingrelease/cleanfor them on upgrade. This needs a migration story, not just the check itself.Suggested implementation
Preparecreates a worktree, e.g. a file inside the worktree's own git administrative directory (not the visible working tree, sogit worktree removecleans it up automatically and it never appears as an untracked file).verifyZeroOwnedWorktreeto check the marker once the migration window has been decided.Suggested tests