Repository navigation
Read Completed, Pruned, and Slash-Remote Push Targets in the Write Guard - #3023
Conversation
A destination outside refs/ that starts with heads/ is now read as the
branch after it as well as by its literal name, since git completes it
under refs/ and writes that branch. --prune, whole or cut short, adds a
delete to every branch a pattern, a matching refspec, --all, or a bare
push may match, and --mirror is read as a force and a delete. A bare
push's @{push} target is read by its full name, with the remote stripped
by matching the configured remote names, so a local branch named like a
remote-tracking ref and a remote whose name holds a slash both resolve to
the branch the push writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The full @{push} name is now also read whole and with its first segment
dropped, so no name the --abbrev-ref read gave is lost where a fetch
refspec writes under another remote's name. The git stdout read and the
temporary-repository build each move into one helper both callers use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The comment that moved into _git_stdout still described one caller's outcome, which two of its callers no longer have. It now says what the lenient decode protects for any caller. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 23 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #3023 +/- ##
==========================================
Coverage ? 60.98%
==========================================
Files ? 17
Lines ? 9010
Branches ? 0
==========================================
Hits ? 5495
Misses ? 3515
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🔵 Needs a closer look
Protected-branch enforcement needs human validation, especially with unresolved option-order behavior and missing Windows test coverage.
2 open findings
What changed in this PR
This PR tightens the agent-safety write guard so it recognizes more pushes that can change protected branches.
Changes:
- Recognizes
heads/destinations and treats--pruneand--mirroras potential deletions. - Reads full
@{push}references to handle remote names containing slashes. - Adds push-classification and temporary-repository test cases.
| File | Description |
|---|---|
host-setup/agent-safety/claude/gh-write-guard.py |
Updates push-target parsing and adds regression cases. |
🧠 Review effort: Lite
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Git applies --prune and --no-prune in order, so the later one wins, and a push carrying --prune then --no-prune deletes nothing. The guard now reads the two the same way, whole or cut short. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Git reads every word after -- or --end-of-options as a refspec, so a --no-prune there cancels nothing and the push still prunes. The guard read it as the option, which let such a push through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
A recorded local strict-review pass covers head |
|
@coderabbitai review |
|
…ng, and backlog-burndown Re-Dispatch Rules to Main (#3030) - #2997 adds an updater that keeps the coding-agent tools already installed on a host current, without installing a tool the maintainer has not chosen - #3017 brings the agent-safety READMEs up to the write guard's #3011 readings: `--admin` read inside a quoted `eval` argument, clustered and cut-short push options, `--branches` as `--all`, `refs/` pattern destinations, and a double-quoted list expansion as a directory or commit value failing closed - #3022 pairs a Copilot overview-table or `Review findings:` entry with a review thread by the file it names, replacing word overlap, compares the headline count against everything the round and the pull request account for, and makes `pr_review.py cleanup` read every page of the head pull requests, counting only this repository's own - #3018 has `backlog-burndown` name a new branch when an issue is dispatched again, name the open pull request a pushed branch still heads when cleanup reports it, and splits the long cleanup sentence - #3028 keeps an abandoned group's issue out of the `backlog-burndown` ranking while that group is stopped for the maintainer, and records the release on the claim comment - #3023 makes `gh-write-guard` read a `heads/` push destination as the branch git completes it to, read `--prune` beside a pattern, `:`, `--all`, or bare push as a delete of each branch matched, read `--mirror` as a force and a delete, and read the `@{push}` target against the configured remote names. A differential against the previous guard found no deny-to-allow flip - #3032 subtracts every thread id an open entry links, back-references included, from the earlier threads `pr_review.py`'s headline shortfall counts, so a back-referenced thread is no longer counted twice - #3035 covers the bootstrap upgrade handoff on Linux and Windows, the root and elevated skips included, and has `bootstrap.ps1` say when the agent tools report fails - #3037 makes `gh-write-guard` fail closed when a push target resolves but the remote names cannot be read After this merges, the host-safety installer payload changes, so the maintainer runs the installer after promotion. Fixes #3009 Fixes #2985 Fixes #3003 Fixes #2993 Fixes #3016 Fixes #3005 Fixes #3007 Fixes #2973 🤖 Generated with [Claude Code](https://claude.com/claude-code)
… Narrative Findings, and Bootstrap Handoff Tests to Main (#3051) - #3042 brings the agent-safety READMEs up to the write guard's #3023 and #3037 readings: `--prune` beside a pattern, `:`, `--all`, or a bare push, a `--mirror` push with no refspec, a `heads/` destination outside `refs/`, the full `@{push}` read, and a bare push denied when `git remote` cannot list the remotes - #3041 has `backlog-burndown` keep an abandoned group's claim comment until cleanup decides its release, say stopped on each path that sends a group to the maintainer, and name a branch no pull request has used when a claim comment first names it - #3044 tests `bootstrap.sh`'s `--report` handoff to the agent tools step, tests the `--agents` handoff on Linux and through `Invoke-AgentsInstall` under `pwsh`, and moves the unprivileged-root probe into `tests/host_capability.py` - #3045 makes `pr_review.py` read an `Open findings remain:` list in a Copilot review and count each entry no thread of the round accounts for, pairing by severity tier and file, so the count can only rise - #3049 makes `gh-write-guard` read a bare push's destination from `push.default` and `branch.<name>.merge`, read a persisted `remote.<name>.mirror`, deny when the `@{push}` read times out or cannot run, give a dash-named word after `--` or `--end-of-options` a reading as a remote or refspec, and judge a push moved into another checkout against that checkout's origin as well. A differential against the previous guard found no deny-to-allow flip. It addresses one item of #2943, which stays open - #3054 makes `gh-write-guard` answer unresolved, and so deny, when its `push.default` read times out or cannot run, a promotion fix for this pull request's first review - #3062 brings both agent-safety READMEs up to #3054: an unset `push.default` is read as `simple`, both destinations are read for a value git does not define or a read exiting with any other error, and a read that times out or cannot run is denied - #3059 bumps `actions/create-github-app-token` from 3.2.0 to 3.3.0 After this merges, the host-safety installer payload changes, so the maintainer runs the installer after promotion. Fixes #3021 Fixes #3026 Fixes #3027 Fixes #3039 Fixes #3033 Fixes #3038 Fixes #3034 Fixes #3031 Fixes #3019 Fixes #3020 Fixes #3029 Fixes #3036 Fixes #3052 🤖 Generated with [Claude Code](https://claude.com/claude-code)

Summary
Three push-target readings in the write guard, each of which let a push that writes a protected branch through.
refs/with itsrefs/%srule, soHEAD:heads/developupdatesdevelopand:heads/developdeletes it. New_dst_branchesreads aheads/destination as the branch afterheads/as well as by its literal name. No other matching rule writes a branch:refs/tags/%s,refs/remotes/%s, andrefs/remotes/%s/HEADare not branches, andrefs/heads/%sand the unmatched fallback write the literal name, which was already read. A colon-lessheads/developrefspec goes through the same reading.--pruneand--mirror.--prune, whole or cut short, adds a delete to every branch a pattern refspec, a:refspec,--all/--branches, or a bare push may match. A bare push is included since a configured push refspec orpush.default=matchingmay be a pattern.--prunebeside an explicit refspec adds nothing, which is what git does.--mirroris read as a force and a delete.@{push}read. It now reads--symbolic-full-name @{push}rather than--abbrev-ref. Each remote namegit remotelists that prefixes the text afterrefs/remotes/is stripped, so a remote named likeup/streamloses both segments. The text is also read whole and with its first segment dropped, so every name the old--abbrev-refread gave is kept. A local branch named likeorigin/developno longer turns the read intoorigin/develop.Verification
HEAD:heads/developupdateddevelop, and:heads/developdeleted it. An unmatchedheads/heads/developcreated the branchheads/heads/develop.--prunebesiderefs/heads/*:refs/heads/*, beside:, beside--all, cut short as--prun, and on a bare push with a configured pattern each deleted a remote branch with no local counterpart.--prune developdeleted nothing, and--pris refused as ambiguous.--abbrev-ref @{push}printedremotes/origin/developonce a local branchorigin/developexisted, andup/stream/developfor a remote namedup/stream.classifywas run over every_GIT_CASESrow, about 8,700 generated push shapes, the new real-repository@{push}cases, and_PUSH_DIR_CASES. Every changed verdict is allow -> deny, inheads/,--prune,--mirror, and@{push}shapes only. Nothing develop denied is allowed._push_targetsand_current_push_branchswapped back in, each new deny case in the selftest fails.gh-write-guard.py --selftest,test_install.py, the roottests/suite, ruff format and check, mypy,prose_lint.py, and the eol gates pass locally.Local Review
A frontier
local-strict-reviewpass ran over each pushed head. Its findings outside these three issues are filed rather than chased here:remote.<name>.mirrormakes a bare push a mirror.--or--end-of-optionsis not read. The--no-prunereading here stops at that boundary, so it cancels nothing past it.--pruneis already listed on Close the Write Guard's Remaining Push Detection Gaps Found Reviewing the #2936 Fix #2943.From Copilot's first round, a later
--no-prunenow cancels an earlier--prune. The same class for the other negatable push options is #3025, and running the real-repository selftests on Windows is #3024.The
commentslabel is on this pull request because the shared_git_stdouthelper moves two existing comment lines.Closes on promotion: #3005, #3007, #2973
🤖 Generated with Claude Code