Skip to content

The Prescribed Post-Merge Remote-Branch Delete Cannot Run Where the Procedure Puts It #1682

Description

@ptr727

drive-pr "The Drive Loop" step 4 and merge-and-release "The Procedure" step 8 both end a task's cleanup with git push origin --delete -- "<branch>", and both place that delete after the worktree has been removed. Once the worktree is gone, the base clone is the only checkout left to run it in, and the base clone is a primary checkout.

gh-write-guard.py denies push in a primary checkout unconditionally, by design. Its own comment beside the denied-subcommand set states the reason: "A push doesn't mutate the local working tree or HEAD the way the rest of this set does, but it publishes whatever is there, and no documented fleet workflow ever pushes from a primary checkout: every push runs from a task's own worktree instead." That premise is what the two procedures above contradict, since they are documented fleet workflows and the step they prescribe has nowhere else to run.

Observed, driving a feature pull request to develop on a machine carrying the agent-safety install, following step 4 in the order it is written:

$ git -C <base-clone> push origin --delete -- "<branch>"
This `git push` runs directly against a primary checkout (<base-clone>), not a linked
worktree. A mutating git operation there can destroy another task's uncommitted work.

The delete still has to happen, so the agent either reaches for GH_WRITE_GUARD_ALLOW_PRIMARY_CHECKOUT, which is a grant it must not give itself and which would lift the guard for every other mutating subcommand too, or it substitutes something the procedure does not name. What it actually substituted was gh api -X DELETE repos/<owner>/<repo>/git/refs/heads/<branch>, which is a GitHub write rather than a git one and so is judged by the gh-write rules instead, within the same owner and against an object id read live in the same run.

Two other facts bound the fix:

  • git branch -D in the base clone is not denied, so only the remote half of the cleanup is blocked. Measured in the same run: git -C <base-clone> branch -D <branch> succeeded.
  • The worktree cannot simply be kept until after the delete in merge-and-release step 8, whose first part removes the promotion worktree specifically so the base clone can check develop out.

Candidate fixes, one to pick rather than a menu to carry:

  1. Reorder both procedures so the verify-then-delete of the remote branch runs from the task's own worktree, before that worktree is removed. This keeps the guard's premise true and needs no new mechanism. The verification the two skills already require, headRefOid captured before the merge against git ls-remote --heads --exit-code, is unaffected by where it runs.
  2. Name gh api -X DELETE repos/<owner>/<repo>/git/refs/heads/<branch> as the prescribed form for the remote delete, on the grounds that deleting a merged remote branch is a GitHub write and never needed a local checkout at all.
  3. Exempt push --delete in the guard. This is the weakest of the three: the flag shape is easy to spoof next to a --delete that is not what it looks like, and it widens the one subcommand whose denial is about publishing rather than about local destruction.

Done is one of those chosen and applied to every surface stating this step, which is drive-pr step 4, merge-and-release step 8, and repo-worktree "Listing and Cleanup" where it describes the same remote delete, with the guard's own comment updated if and only if fix 3 is the one chosen.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agentsAgents instructionsbugSomething isn't workingskillsAgent skill

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions