Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@
{
"name": "pr-cycle",
"source": "./commands/pr-cycle",
"description": "/pr-cycle — one-shot PR cycle command. Pushes the current branch (`git push -u origin HEAD`), `gh pr create --fill`, then SELF-MERGES with ` && gh pr merge --squash --admin --delete-branch` in the SAME command block — one command does create → merge. Refuses on main/master. Pass extra gh flags as args. 0.4.1 — command appends the merge ITSELF instead of relying on the `pr-cycle-hook` PreToolUse hook: slash-command `!`-exec does NOT route through the Bash-tool PreToolUse hook, so the hook never fired for /pr-cycle (PRs left created-but-unmerged); self-merge fixes it, idempotent with the hook (skips any command already containing `gh pr merge`). Command half of the pr-cycle split (routing hook = `pr-cycle-hook`, still serves agent Bash-tool `gh pr create`).",
"version": "0.4.1"
"description": "/pr-cycle — one-shot PR cycle command. Pushes the current branch (`git push -u origin HEAD`), `gh pr create --fill`, SELF-MERGES with `gh pr merge --squash --admin --delete-branch`, then SWEEPS merged agent worktrees in the SAME command block — one command does create → merge → clean. Refuses on main/master. Pass extra gh flags as args. 0.5.0 — POST-MERGE WORKTREE SWEEP closes the leak where `--delete-branch` removed the remote branch but left the `.claude/worktrees/agent-*` DIRECTORY piling up across /cycle-bg runs. After a successful merge the command cd's to the MAIN worktree and removes every linked agent worktree whose upstream is `[gone]` (the squash-safe 'merged + branch deleted' signal — `--is-ancestor` cannot detect a squash merge, but `git push -u` then `--delete-branch` reliably leaves the upstream gone), plus `git branch -D` + `git worktree prune`. cd-to-main first means even the just-merged CURRENT worktree (when /pr-cycle ran inside one) is swept. SAFETY: never touches a worktree with a live OR absent (`track=none`) upstream — those may hold un-pushed work — nor `locked` worktrees (another checkout); only paths under `/.claude/worktrees/` are eligible. 0.4.1 — command appends the merge ITSELF instead of relying on the `pr-cycle-hook` PreToolUse hook: slash-command `!`-exec does NOT route through the Bash-tool PreToolUse hook, so the hook never fired for /pr-cycle (PRs left created-but-unmerged); self-merge fixes it, idempotent with the hook (skips any command already containing `gh pr merge`). Command half of the pr-cycle split (routing hook = `pr-cycle-hook`, still serves agent Bash-tool `gh pr create`).",
"version": "0.5.0"
},
{
"name": "pr-cycle-hook",
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@ For the full audit trail, see `git log`.

---

## 2026-06-07 — 🧹 pr-cycle 0.5.0 — 머지 후 worktree sweep (누적 누수 차단)

`/cycle-bg` 를 돌릴수록 `.claude/worktrees/agent-*` worktree 디렉터리가 계속 쌓이던
누수를 차단. `gh pr merge --delete-branch` 는 **원격 브랜치만** 지우고 worktree
디렉터리는 남겼는데, pr-cycle 에 정리 단계가 없어 누적됐음 (pr→merge→clean 의 clean 부재).

- 🧹 **머지 후 sweep** — 머지 성공 후 MAIN worktree 로 cd → upstream 이 `[gone]` 인
`.claude/worktrees/agent-*` worktree 만 `git worktree remove --force` + `git branch -D`
+ `git worktree prune`. MAIN 으로 먼저 cd 하므로 /pr-cycle 가 worktree **안에서**
돌았어도 방금 머지된 자기 worktree 까지 정리됨.
- 🔑 **squash-safe 신호** — squash 머지는 worktree 커밋이 origin/main 의 조상이 안 돼
`--is-ancestor` 로 "머지됨" 판정 불가. 대신 `git push -u` → `--delete-branch` 후
upstream 이 `[gone]` 으로 변하는 걸 신호로 사용.
- 🔒 **안전** — upstream 이 살아있거나(`track=none` 미푸시 작업 가능성) `locked`(다른
체크아웃) worktree 는 절대 건드리지 않음. `/.claude/worktrees/` 경로만 대상.
- 검증: sh 문법 · 4-상태 worktree 분류(merged/`[gone]`·미푸시·locked·main) 보존 로직 ·
g22 lockstep(plugin 0.5.0 ↔ marketplace 0.5.0).

## 2026-06-07 — 🎓 domain 0.12.0 — 자동사용(USE) 절반 복원 · skillopt-hook 의 SessionStart 주입을 domain 안으로

0.11.0 통합에서 빠졌던 **자동사용 훅**을 domain 플러그인 내부에 포팅 — `activate` 가
Expand Down
4 changes: 2 additions & 2 deletions commands/pr-cycle/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "pr-cycle",
"description": "/pr-cycle — one-shot PR cycle command. Pushes the current branch (`git push -u origin HEAD`), `gh pr create --fill`, then SELF-MERGES with ` && gh pr merge --squash --admin --delete-branch` in the SAME command block — one command does create → merge. Refuses on main/master. Pass extra gh flags as args (e.g. --title \"...\" --body \"...\"). 0.4.1 — the command now appends the merge ITSELF instead of relying on the `pr-cycle-hook` PreToolUse hook: the command body executes via slash-command `!`-exec, which does NOT route through the Bash-tool PreToolUse hook, so the hook never fired for /pr-cycle and PRs were left created-but-unmerged. Self-merging fixes it and is idempotent with the hook (the hook skips any command already containing `gh pr merge`); the hook still serves agent-issued `gh pr create` Bash-tool calls + cross-repo/worktree cleanup. Command half of the pr-cycle split.",
"version": "0.4.1",
"description": "/pr-cycle — one-shot PR cycle command. Pushes the current branch (`git push -u origin HEAD`), `gh pr create --fill`, SELF-MERGES with `gh pr merge --squash --admin --delete-branch`, then SWEEPS merged agent worktrees in the SAME command block — one command does create → merge → clean. Refuses on main/master. Pass extra gh flags as args (e.g. --title \"...\" --body \"...\"). 0.5.0 — POST-MERGE WORKTREE SWEEP closes the leak where `--delete-branch` removed the remote branch but left the `.claude/worktrees/agent-*` DIRECTORY piling up across /cycle-bg runs. After a successful merge the command cd's to the MAIN worktree and removes every linked agent worktree whose upstream is `[gone]` (the squash-safe 'merged + branch deleted' signal — `--is-ancestor` cannot detect a squash merge, but `git push -u` then `--delete-branch` reliably leaves the upstream gone), plus `git branch -D` + `git worktree prune`. cd-to-main first means even the just-merged CURRENT worktree (when /pr-cycle ran inside one) is swept. SAFETY: never touches a worktree with a live OR absent (`track=none`) upstream — those may hold un-pushed work — nor `locked` worktrees (another checkout); only paths under `/.claude/worktrees/` are eligible. 0.4.1 — the command now appends the merge ITSELF instead of relying on the `pr-cycle-hook` PreToolUse hook: the command body executes via slash-command `!`-exec, which does NOT route through the Bash-tool PreToolUse hook, so the hook never fired for /pr-cycle and PRs were left created-but-unmerged. Self-merging fixes it and is idempotent with the hook (the hook skips any command already containing `gh pr merge`); the hook still serves agent-issued `gh pr create` Bash-tool calls + cross-repo/worktree cleanup. Command half of the pr-cycle split.",
"version": "0.5.0",
"author": { "name": "dancinlab" },
"repository": "https://github.com/dancinlab/sidecar",
"license": "MIT",
Expand Down
34 changes: 31 additions & 3 deletions commands/pr-cycle/commands/pr-cycle.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: /pr-cycle — one-shot PR cycle. Pushes the current branch (git push -u origin HEAD), runs gh pr create --fill, then SELF-MERGES with ` && gh pr merge --squash --admin --delete-branch` IN THE SAME command block. 0.4.1 — the merge is appended by the command itself (not left to the pr-cycle PreToolUse hook): the command body runs via slash-command `!`-exec, which does NOT route through the Bash-tool PreToolUse hook, so relying on the hook left PRs created-but-unmerged; self-merging fixes that and stays idempotent (the hook skips any command already containing `gh pr merge`). Refuses on main/master. Pass extra gh flags as args (e.g. --title "..." --body "...").
description: /pr-cycle — one-shot PR cycle. Pushes the current branch (git push -u origin HEAD), runs gh pr create --fill, SELF-MERGES with `gh pr merge --squash --admin --delete-branch`, then SWEEPS merged agent worktrees IN THE SAME command block. 0.5.0 — post-merge worktree sweep: after a successful merge it cd's to the MAIN worktree and removes every `.claude/worktrees/agent-*` linked worktree whose upstream is `[gone]` (the squash-safe "merged + branch deleted" signal — `--is-ancestor` can't detect squash merges, but `git push -u` then `--delete-branch` reliably leaves the upstream gone), plus `git branch -D` + `git worktree prune`. Fixes the leak where `--delete-branch` removed the remote branch but left the worktree DIRECTORY piling up across /cycle-bg runs. NEVER touches a worktree with a live or absent (`track=none`) upstream — those may hold un-pushed work — nor `locked` worktrees (another checkout). 0.4.1 — the merge is appended by the command itself (not the PreToolUse hook): the body runs via slash-command `!`-exec, which does NOT route through the Bash-tool PreToolUse hook, so relying on the hook left PRs created-but-unmerged; self-merging fixes that and stays idempotent. Refuses on main/master. Pass extra gh flags as args (e.g. --title "..." --body "...").
argument-hint: "[gh pr create flags, e.g. --title \"...\" --body \"...\"]"
allowed-tools: Bash
---
Expand All @@ -12,6 +12,34 @@ if [ "$BR" = "main" ] || [ "$BR" = "master" ]; then
echo " (git switch -c feat/<slug>)"
exit 0
fi
echo "▸ /pr-cycle on '$BR' — push + create + self-merge"
echo "▸ /pr-cycle on '$BR' — push + create + self-merge + worktree sweep"
git push -u origin HEAD 2>&1 | tail -2
gh pr create --fill $ARGUMENTS && gh pr merge --squash --admin --delete-branch`
if gh pr create --fill $ARGUMENTS && gh pr merge --squash --admin --delete-branch; then
# ── post-merge worktree sweep ──────────────────────────────────────────────
# Remove harness agent worktrees whose upstream is [gone] (= merged + branch
# deleted). Squash-safe: a squashed branch is NOT an ancestor of main, so we
# key off the deleted upstream, not --is-ancestor. cd to MAIN first so even the
# just-merged CURRENT worktree (if /pr-cycle ran inside one) becomes sweepable.
MAIN=$(git worktree list --porcelain | awk 'NR==1 && /^worktree /{print $2; exit}')
[ -n "$MAIN" ] && cd "$MAIN" 2>/dev/null || true
git fetch -p origin >/dev/null 2>&1 || true
git worktree list --porcelain | awk '
/^worktree /{wt=$2; br=""; lk=""}
/^branch /{br=$2; sub("refs/heads/","",br)}
/^locked/{lk="L"}
/^$/{if(wt!=""){print wt"|"br"|"lk; wt=""}}
END{if(wt!=""){print wt"|"br"|"lk}}' | while IFS="|" read -r wt br lk; do
case "$wt" in *"/.claude/worktrees/"*) ;; *) continue ;; esac # only harness agent worktrees
[ "$wt" = "$MAIN" ] && continue # never the main checkout
[ -n "$lk" ] && continue # never locked (another checkout)
[ -n "$br" ] || continue
track=$(git for-each-ref --format='%(upstream:track)' "refs/heads/$br" 2>/dev/null)
[ "$track" = "[gone]" ] || continue # only merged+deleted (squash-safe)
if git worktree remove --force "$wt" 2>/dev/null; then
git branch -D "$br" 2>/dev/null || true
echo " 🧹 swept merged worktree: $wt"
fi
done
git worktree prune 2>/dev/null || true
echo "✓ /pr-cycle done — PR merged + merged worktrees swept"
fi`