task-done --remove-worktree: force-delete reviewer branch on PR_NUMBER (#148)#153
Conversation
…dispatch of task-reviewer: force-delete on PR_NUMBER Branch on the `PR_NUMBER=` marker that `task-reviewer` writes into the worktree's `.info` file. When present, the worktree is a reviewer scaffold whose branch (`task/review-pr<N>`) is forked from the PR's head ref and will never merge into main — use `git branch -D` so a subsequent `task-reviewer <N>` isn't blocked by its per-PR branch guard. When absent (worker worktree), behavior is unchanged: `git branch -d` (safe-delete) so unmerged work isn't dropped. Lands across the `task-done-std` and `task-done-local` drift groups (7 binaries, byte-identical). New bats coverage exercises the reviewer-branch path for each of the 7 variants plus a worker regression for claude-gh; drift check stays green. Closes #148 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR Review — #153:
|
| Deliverable | Status |
|---|---|
Part A — Branch deletion in all 7 task-done variants conditioned on PR_NUMBER marker |
✅ |
| Part B — Audit of reviewer-state cleanup (no new leaks found) | ✅ (audit confirmed in PR description; table in spec was already green except for the branch) |
| Part C — Tests: reviewer force-delete (7 variants) + worker safe-delete regression | ✅ |
Part D — CHANGELOG [Unreleased] Fixed entry |
✅ |
The change is applied consistently across the task-done-std (5 binaries: claude-gh, claude-jira, claude-notion, kiro-gh, kiro-notion) and task-done-local (2 binaries: claude-local, kiro-local) drift groups, byte-identical within each group as required.
Code-review findings
Blocker — PR_NUMBER not zeroed before source "$INFO_FILE"
File: claude-gh/bin/task-done (and all 6 sibling variants) — sourcing region (~line 53)
The pre-source initialization pattern already exists for BASE_BRANCH:
BASE_BRANCH="main"
[[ -f "$INFO_FILE" ]] && source "$INFO_FILE"PR_NUMBER is not given the same treatment. A worker's .info file contains no PR_NUMBER= line, so sourcing it never writes that variable. If PR_NUMBER is set in the ambient shell environment (e.g., the user ran a task-reviewer session earlier in the same shell session, or has it exported), [[ -n "${PR_NUMBER:-}" ]] will fire on the worker worktree and call git branch -D on a branch that may contain unmerged commits — silently destroying work that was deliberately protected by git branch -d.
Fix: add PR_NUMBER= on the line before source "$INFO_FILE", mirroring BASE_BRANCH="main":
BASE_BRANCH="main"
PR_NUMBER=
[[ -f "$INFO_FILE" ]] && source "$INFO_FILE"Apply to all 7 variants.
Test gap: The regression test claude-gh: worker worktree (no PR_NUMBER) still uses safe-delete -d does not set PR_NUMBER=42 in the test environment before invoking task-done, so it passes even with the bug present and would not catch this scenario. A follow-on test that exports PR_NUMBER=42 and then runs task-done from a worker worktree (no PR_NUMBER in its info file) would close this.
Nit — Misleading message when reviewer branch is already gone
File: claude-gh/bin/task-done (and all 6 siblings) — new else branch in force-delete path
echo "Done. Branch '$BRANCH' could not be deleted — clean up manually: git branch -D $BRANCH"If git branch -D fails because the branch was already deleted (double-invocation, or the user ran git branch -D manually before task-done), this message instructs the user to run the exact command that already succeeded. The script exits 0 and the worktree is gone, so there's nothing to actually clean up. Not a blocker — the flow still exits cleanly — but worth noting if message accuracy matters for the dogfooding experience.
Minor — Worker regression test covers only claude-gh
File: tests/task_done.bats
The spec asked for a regression test against a non-reviewer worktree to confirm git branch -d behavior is unchanged. The test exists but only for claude-gh. The other 6 variants have the reviewer force-delete test but no corresponding worker-path assertion. This is a coverage gap; not a blocker given the drift-check suite, but a future copy-paste in one of the sibling task-work scripts that accidentally introduces PR_NUMBER= into a worker's info file would not be caught by the sibling's test.
Verdict: changes-requested
One blocker: PR_NUMBER must be zeroed before source "$INFO_FILE" in all 7 variants so an ambient shell export cannot trigger git branch -D on a worker branch with unmerged commits. The rest of the implementation (logic, drift consistency, CHANGELOG, test structure) is solid. The nits are non-blocking.
Changes requested — one real blocker (data-loss risk)Blocker —
|
…dispatch of task-reviewer: zero PR_NUMBER before sourcing $INFO_FILE Address PR #153 review blocker: without a pre-source default, an ambient shell export of PR_NUMBER (e.g., left over from an earlier `task-reviewer` session in the same shell) would survive into the new force-delete guard and trigger `git branch -D` on a worker branch with unmerged commits — silently destroying them. Mirror the existing `BASE_BRANCH="main"` default with `PR_NUMBER=` so a worker .info file (which contains no PR_NUMBER= line) always ends up with an empty value regardless of the ambient environment. Lands in the `worktree-context` region across the `task-done-std` and `task-done-local` drift groups (7 binaries, byte-identical). New bats coverage: `claude-gh` and `claude-local` ambient-export regressions export PR_NUMBER=42, run task-done from a worker worktree with an unmerged commit, and assert that: - the safe-delete fallback message ("still has unmerged commits") is emitted, - the reviewer-branch message is NOT emitted, - the branch object still exists at the unmerged commit sha. Cross-drift-group coverage (std + local) keeps a future worktree-context drift from quietly regressing one group. Refs #148 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-review — PR #153 (round 2)ContextThis is a re-review after the changes-requested findings from round 1. The prior blocker was: Changes-requested blocker: resolved ✅All 7 PR_NUMBER=
[[ -f "$INFO_FILE" ]] && source "$INFO_FILE"This is byte-identical to the The ambient-export regression test (
The test correctly exports Code-review findings (this round)Nit — ambient-export regression test covers 2 of 7 variants
Mitigating factor: Residual risk: if a future change splits a variant out of its drift group and does not add a dedicated ambient test, the regression would go undetected for that variant. Worth noting as a follow-on if the drift groups are ever refactored. Nit — worker safe-delete regression covers only
|
| Deliverable | Status |
|---|---|
Part A — PR_NUMBER zero-init + force-delete in all 7 variants |
✅ |
| Part B — audit confirms no new leaks beyond the branch | ✅ |
| Part C — reviewer force-delete tests (7) + worker regression + ambient-export tests (2) | ✅ |
Part D — CHANGELOG [Unreleased] Fixed entry |
✅ |
tools/check-drift.sh should stay green — both the worktree-context and confirm-and-cleanup regions are byte-identical within each drift group.
Verdict: clean-with-nits
The round-1 blocker is resolved. The PR_NUMBER= zero-init is in place across all 7 variants, the ambient-export regression test covers one file per drift group and the test logic is sound. Remaining findings are all nits — coverage gaps documented and mitigated by drift enforcement, not holes. Ready to merge at PM's discretion.
Summary
PR_NUMBER=marker thattask-reviewerwrites into the worktree's.infofile. Reviewer worktrees getgit branch -D(theirtask/review-pr<N>branch is pure scaffolding forked from the PR's head ref and is never going to land in main); worker worktrees keep the safe-deletegit branch -d.task-reviewer(Error: a review worktree or branch already exists for PR #N) that bit every re-review round-trip and forced a manualgit branch -D task/review-pr<N>between cycles.task-done-stdandtask-done-localdrift groups (7 binaries, byte-identical).tools/check-drift.shstays green — the new conditional lives inside the existingconfirm-and-cleanupregion.Closes #148
Test plan
./run_tests.sh task_done— 60 tests pass, including 7 new reviewer-branch force-delete tests (one per variant) plus a worker regression for claude-gh../run_tests.sh drift_check— 8 tests pass;task-done-stdandtask-done-localconfirm-and-cleanupregions remain byte-identical.tools/check-drift.sh— green (21 groups checked)../run_tests.sh— full suite green (783 tests).task-reviewer 42→task-done --remove-worktree→ re-dispatchtask-reviewer 42succeeds without manualgit branch -D.🤖 Generated with Claude Code