From ccf0cb475f52b8374acdb2fd1a2a698b4de03f23 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 13:08:38 +0000 Subject: [PATCH 1/2] prm: don't attempt remote branch deletion where the proxy blocks it Close-out step 5 assumed every environment could delete a remote ref, so a proxied web session (Claude Code on the web) burned turns on pushes that can never land: the egress proxy returns HTTP 403 on the git-receive-pack POST carrying a ref deletion, and the GitHub MCP surface exposes no delete verb. Add a one-shot environment probe before the close-out, skip the remote half of step 5 outright when it answers, and defer those branches to /repo_cleanup (its Bucket B enumerates origin branches, so a local sweep picks them up). Also correct the remote-environment note, which claimed step 5 ran "as usual" over gh, and record deferred branches in the ledger alongside deleted ones. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DHmxnBNwDGedeUoRJk9iFz --- skills/prm/prm.md | 23 +++++++++++++++++---- skills/prm/reference.md | 44 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 59 insertions(+), 8 deletions(-) diff --git a/skills/prm/prm.md b/skills/prm/prm.md index 1201dc17..84fc1f07 100644 --- a/skills/prm/prm.md +++ b/skills/prm/prm.md @@ -37,6 +37,16 @@ checkout required. Detect which you are in: - **Remote (mobile/codex)** — no multi-repo checkout. Resolve the PR from the argument or by listing candidates (below), and **skip the local-only cleanup** with a one-line note. Never `cd` into a repo that isn't there. +- **Proxied web session (Claude Code on the web)** — remote as above, **plus it + cannot delete remote branches at all**: the egress proxy refuses ref deletions. + Probe once, before the close-out, and remember the answer for the whole run: + + ```bash + curl -sf "$HTTPS_PROXY/__agentproxy/status" >/dev/null && echo "ref deletes blocked" + ``` + + An answer means every `git push origin --delete` in this run will fail. Do not + attempt one — see step 5. ## The routine @@ -119,14 +129,19 @@ step 6. Order is forced by the tooling, so do not reorder: registered in `active.md` — which is exactly why step 3 comes first. 5. **Branches** — delete the remote `feature/` per proven-merged repo, plus any local branch left in the canonical checkout. Never delete a branch whose - merge you did not prove in sub-step 1. + merge you did not prove in sub-step 1. **If the environment cannot delete + remote refs** (proxied web session, above), skip the remote half outright and + list those branches in the ledger as deferred to `/repo_cleanup` — never spend + turns on a push the proxy has already refused. 6. **Report the ledger** — PRs merged, issue closed, record path, `active.md` - released, worktree removed, branches deleted, and anything skipped. + released, worktree removed, branches deleted **or deferred**, and anything + skipped. -**Remote (mobile/codex):** sub-steps 1, 2 and 5 run over `gh`/`git ls-remote` as +**Remote (mobile/codex):** sub-steps 1 and 2 run over `gh`/`git ls-remote` as usual. Mind (3) works if PyAutoMind is checked out; otherwise say the record is pending. The worktree (4) is local-only — name it as outstanding rather than -implying it ran. +implying it ran. Branches (5) delete normally from mobile and Codex, but **not** +from a proxied web session — there they are deferred, not attempted. ### 6. The only guards that stop you diff --git a/skills/prm/reference.md b/skills/prm/reference.md index 3a3e54de..46029e04 100644 --- a/skills/prm/reference.md +++ b/skills/prm/reference.md @@ -189,7 +189,40 @@ directory listing is the only way to find it. ### 5. Branches -Local branches go with the worktree; the remote ones do not: +Local branches go with the worktree; the remote ones do not. **First establish +whether this environment may delete remote refs at all** — one probe, once per +run, before any repo: + +```bash +curl -sf "$HTTPS_PROXY/__agentproxy/status" >/dev/null && echo "ref deletes blocked" +``` + +An answer means a proxied web session (Claude Code on the web). There, a ref +deletion dies on the `git-receive-pack` POST: + +``` +error: RPC failed; HTTP 403 curl 22 The requested URL returned error: 403 +send-pack: unexpected disconnect while reading sideband packet +``` + +That 403 is generated in front of GitHub — the ref advertisement to the same host +seconds earlier returns 200 with an `X-Github-Request-Id`, the 403 carries none — +so credentials, `gh` scopes and branch protection are **not** the cause, and no +retry, repo, or token changes it. The GitHub MCP surface has no delete verb +either (`create_branch` exists; nothing deletes a ref). So in that environment: +delete the local branches if a checkout exists, **skip the remote half entirely**, +and put one line per branch in the ledger: + +``` +branches: deferred — PyAutoMind claude/ (merged, proxy blocks remote delete) → /repo_cleanup +``` + +`/repo_cleanup` is the destination: its Bucket B enumerates origin branches +directly, so a deferred branch is picked up by the next local sweep. Repo-side, +"Automatically delete head branches" (Settings → General → Pull Requests) removes +the need for the step at all. + +Otherwise — local CLI, mobile, Codex — delete as usual: ```bash git -C push origin --delete feature/ @@ -198,11 +231,14 @@ git -C fetch --prune ``` Only branches proven merged in step 1. `git ls-remote --heads origin feature/` -is the ground truth that the delete landed. +is the ground truth that the delete landed. If a delete 403s where you expected it +to work, treat it as the proxied case from that point on: **stop after the first +refusal**, do not repeat it per repo, and defer the rest. ### 6. The ledger Report, per line: PR(s) merged (URL + `MERGED`), issue closed (number + state), record path under `complete///`, `active.md` claim released, worktree -removed, branches deleted, and **anything skipped** with the reason. A close-out -that quietly skipped a step reads exactly like one that finished. +removed, branches deleted **or deferred** (with the destination), and **anything +skipped** with the reason. A close-out that quietly skipped a step reads exactly +like one that finished. From 98d3aac85eda1e0e7c00a62c57b3d6045ab2aec3 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 25 Aug 2026 13:11:04 +0000 Subject: [PATCH 2/2] prm: drop the branch sub-step silently where deletion is impossible The previous commit stopped the wasted push attempts but still reported the branches as deferred to /repo_cleanup. Behind the proxy that line would appear on every close-out saying the same thing, which is noise rather than a finding. Treat step 5 as out of scope in that environment: attempt nothing, report nothing, and omit branches from the ledger entirely. Nothing is lost, because /repo_cleanup Bucket B enumerates origin branches directly and rediscovers them on the next local sweep without being told. The ledger's "a skipped step must read as skipped" rule gains one stated exception for a step the environment makes impossible. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01DHmxnBNwDGedeUoRJk9iFz --- skills/prm/prm.md | 21 +++++++++++---------- skills/prm/reference.md | 31 +++++++++++++++++-------------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/skills/prm/prm.md b/skills/prm/prm.md index 84fc1f07..7bcf9e08 100644 --- a/skills/prm/prm.md +++ b/skills/prm/prm.md @@ -45,8 +45,9 @@ checkout required. Detect which you are in: curl -sf "$HTTPS_PROXY/__agentproxy/status" >/dev/null && echo "ref deletes blocked" ``` - An answer means every `git push origin --delete` in this run will fail. Do not - attempt one — see step 5. + An answer means every `git push origin --delete` in this run will fail. Branch + cleanup is then **out of scope for the run**: don't attempt it, and don't + mention it — see step 5. ## The routine @@ -129,19 +130,19 @@ step 6. Order is forced by the tooling, so do not reorder: registered in `active.md` — which is exactly why step 3 comes first. 5. **Branches** — delete the remote `feature/` per proven-merged repo, plus any local branch left in the canonical checkout. Never delete a branch whose - merge you did not prove in sub-step 1. **If the environment cannot delete - remote refs** (proxied web session, above), skip the remote half outright and - list those branches in the ledger as deferred to `/repo_cleanup` — never spend - turns on a push the proxy has already refused. + merge you did not prove in sub-step 1. **Where the environment cannot delete + remote refs** (proxied web session, above), this sub-step does not exist: + attempt nothing, and report nothing about it. `/repo_cleanup` finds those + branches on origin by itself, so silence here loses nothing. 6. **Report the ledger** — PRs merged, issue closed, record path, `active.md` - released, worktree removed, branches deleted **or deferred**, and anything - skipped. + released, worktree removed, branches deleted, and anything skipped. Branches + are simply absent from the ledger where sub-step 5 did not apply. **Remote (mobile/codex):** sub-steps 1 and 2 run over `gh`/`git ls-remote` as usual. Mind (3) works if PyAutoMind is checked out; otherwise say the record is pending. The worktree (4) is local-only — name it as outstanding rather than -implying it ran. Branches (5) delete normally from mobile and Codex, but **not** -from a proxied web session — there they are deferred, not attempted. +implying it ran. Branches (5) delete normally from mobile and Codex; a proxied +web session drops the sub-step silently. ### 6. The only guards that stop you diff --git a/skills/prm/reference.md b/skills/prm/reference.md index 46029e04..bb1c2792 100644 --- a/skills/prm/reference.md +++ b/skills/prm/reference.md @@ -209,18 +209,19 @@ That 403 is generated in front of GitHub — the ref advertisement to the same h seconds earlier returns 200 with an `X-Github-Request-Id`, the 403 carries none — so credentials, `gh` scopes and branch protection are **not** the cause, and no retry, repo, or token changes it. The GitHub MCP surface has no delete verb -either (`create_branch` exists; nothing deletes a ref). So in that environment: -delete the local branches if a checkout exists, **skip the remote half entirely**, -and put one line per branch in the ledger: +either (`create_branch` exists; nothing deletes a ref). So in that environment +**this whole sub-step is out of scope**: run no delete, and write no line about +branches in the ledger — not "deferred", not "blocked", not "see /repo_cleanup". +The step is structurally impossible there and would say the same thing on every +close-out, so it is noise, not a finding. -``` -branches: deferred — PyAutoMind claude/ (merged, proxy blocks remote delete) → /repo_cleanup -``` +Nothing is lost by the silence: `/repo_cleanup` Bucket B enumerates origin +branches directly (`gh api repos///branches`), so it rediscovers +these on the next local sweep without being told. Repo-side, "Automatically +delete head branches" (Settings → General → Pull Requests) removes the need for +the sub-step altogether. -`/repo_cleanup` is the destination: its Bucket B enumerates origin branches -directly, so a deferred branch is picked up by the next local sweep. Repo-side, -"Automatically delete head branches" (Settings → General → Pull Requests) removes -the need for the step at all. +Report a blocked delete only if the user **asks** where a branch went. Otherwise — local CLI, mobile, Codex — delete as usual: @@ -233,12 +234,14 @@ git -C fetch --prune Only branches proven merged in step 1. `git ls-remote --heads origin feature/` is the ground truth that the delete landed. If a delete 403s where you expected it to work, treat it as the proxied case from that point on: **stop after the first -refusal**, do not repeat it per repo, and defer the rest. +refusal** and drop the sub-step — do not repeat the push per repo, and do not +narrate the failure. ### 6. The ledger Report, per line: PR(s) merged (URL + `MERGED`), issue closed (number + state), record path under `complete///`, `active.md` claim released, worktree -removed, branches deleted **or deferred** (with the destination), and **anything -skipped** with the reason. A close-out that quietly skipped a step reads exactly -like one that finished. +removed, branches deleted, and **anything skipped** with the reason. A close-out +that quietly skipped a step reads exactly like one that finished — with one +deliberate exception: a sub-step the environment makes impossible (step 5 behind +the proxy) is omitted outright rather than reported as skipped.