SIGN IN SIGN UP

fix(work-task): refuse to delete a worktree that still holds uncommitted work

Removing a task's worktree ran `git worktree remove --force` with nothing
between the click and the removal to look at what was inside it. An agent that
edited files without committing them — or a user who edited them by hand — lost
that work outright, and nothing on the way in ever named it: the cancel dialog's
checkbox offers "its worktree and work branch", the card's button says "Delete
worktree", and neither mentions files left unstaged. A stop pressed mid-edit is
exactly when a checkout is dirty, so the likeliest case was the losing one. The
other two surfaces that remove a worktree already draw this line — `complete_task`
refuses with the same sentence, and the branch dropdown re-asks under an explicit
"those changes cannot be recovered".

`cleanup_task` now checks under the task lock, before the removal, and leaves the
worktree standing while it still holds uncommitted files. Committed work stays
deletable: `branch -D` is what the checkbox spells out and the reflog still holds
those commits. The probe behind the check fails CLOSED — a git error means the
directory could not be PROVEN safe to destroy, and the one reading of "cannot
ask" that is safe is the checkout already being off disk. Three call sites
carried their own fail-open copy of that question and now share the probe; the
reason clause they print is composed in one place too, so the card, the toast and
the timeline cannot drift into describing the same condition differently.

The refusal is both recorded on the row and returned, because the callers hear
different channels. The direct button surfaces the error as a toast.
`work_task_cancel_core` swallows it and the card's `cleanup_state` carries the
reason instead — turning it into an error would report a stop that already
happened as failed. `work_task_delete_core` stops on it: the tombstone it would
otherwise write takes the card, its `cleanup_state` and its retry entry in one
write, stranding a directory the user was told would be deleted with nothing left
anywhere to say why. `CleanupBlocked` is what lets that path tell a refusal from
a cleanup that merely failed, which it may still proceed over.
X
xintaofei committed
dde7e213bbe205011200a9a99cf5847781820f17
Parent: a5bebe1