feat(zsh): gate external-contributor PRs in ghsq/ghrb/ghrp - #348
Merged
Conversation
The merge wrappers exist for bulk-merging my own and bots' PRs, and the muscle memory is `ghsq` → ^a (select all) → a (all remaining). A drive-by PR from a stranger riding that reflex merges code nobody read — and a PR that adds a workflow, a hook, or a shell script executes on merge. laurigates/claude-plugins is public and just received one (#2231, one of a 284-repo spray). Three layers, none of which slow down the normal path: - The picker HIDES external PRs by default, so ^a cannot even select one. A stderr banner reports the count and lists them, so hiding a contribution never means ghosting it; `ghsq -x` / `ghrb -x` opts them in, rendered white-on-red. - Merging one requires typing `merge` — not a keypress. The prompt first prints the author, their author_association, the diff size, and specifically which touched paths EXECUTE (CI workflow, shell, .claude config, build entry point). - `a`=all-remaining cannot reach it: the gate lives in _gh_poll_merge, which `a` still routes through. The `ghsq 123` arg form bypasses the picker entirely, so it is gated separately at that call site. ghrp inherits the merge gate for free (shared batch engine) and gains an author column — a stranger's PR carrying an `autorelease: pending` label in an org repo is exactly the case worth seeing. The bit that took longest to get right: a bot author renders three different ways depending on which gh subcommand produced it. gh pr list → login "app/<name>", is_bot true, no type gh search prs → login "<name>[bot]", is_bot FALSE, type "Bot" Keying on is_bot alone silently classifies every App bot in search results as a stranger — which would make ghrp demand a typed word on every release PR and train me to stop reading the prompt. _gh_trust accepts all four spellings. Fails closed throughout: if the author cannot be read, or my own login cannot be resolved, the merge is skipped rather than performed. Cannot-verify is not safe. The agent-side counterpart is laurigates/claude-plugins#2240 (a PreToolUse hook denying the same merges from an agent), and the policy is documented in the portfolio rules (repos-claude-config .claude/rules/external-contributor-prs.md) rather than in the always-loaded global set, which is already near its budget.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
ghsq/ghrbare for bulk-merging my own and bots' PRs, and the muscle memory isghsq→^a(select all) →a(all remaining). A drive-by PR from a stranger riding that reflex is merged code nobody read — and a PR that adds a workflow, a hook, or a shell script executes on merge.laurigates/claude-pluginsis public and just received one (#2231, one of a 284-repo spray with zero merges anywhere). It added a CI workflow calling a third-party action on a floating@v0tag.Three layers, none of which slow the normal path down
^acannot even select one. A stderr banner reports the count and lists them (hiding a contribution must not mean ghosting it).-x/--externalopts them in, rendered white-on-red.mergeis required — not a keypress. First prints author,author_association, diff size, and specifically which touched paths EXECUTE (CI workflow, shell,.claude/, build entry point).a=all_gh_poll_merge, whichastill routes through. Theghsq 123arg form bypasses the picker entirely, so it's gated separately at that call site.ghrpinherits the merge gate for free (shared batch engine) and gains an author column — a stranger's PR carrying anautorelease: pendinglabel in an org repo is exactly the case worth seeing.The subtle part
A bot author renders three different ways depending on which
ghsubcommand produced it:loginis_bottypegh pr list/gh pr viewapp/laurigates-release-pleasetruegh search prslaurigates-release-please[bot]falseBotKeying on
is_botalone silently classifies every App bot in search results as a stranger — which would makeghrpdemand a typed word on every release PR and train me to stop reading the prompt._gh_trustaccepts all four spellings.Fails closed throughout: if the author can't be read, or my own login can't be resolved, the merge is skipped, not performed.
Verification
Live, against the real repo:
Plus a 39-assertion harness over the classifier and the gate (both bot renderings, flag-value-not-selector,
y/empty/MERGEanswers, unreadable-author and unresolvable-own-login fail-closed paths).chezmoi cat ~/.zshrc | zsh -nclean; template renders.Related
repos-claude-config), deliberately not the always-loaded global set, which the context-budget hook showed is already near its 114 KB ceiling.🤖 Generated with Claude Code
https://claude.ai/code/session_01VXmv48FuSrSLh9CKha7d3R