feat(gatekeeper): add optional exempt_paths input#55
Conversation
Adds a backward-compatible `exempt_paths` input to the reusable gatekeeper workflow: a newline-separated list of regexes matched against changed file paths. A bot-authored PR is exempt from the 2-human-approval requirement when every changed file matches either the built-in docs patterns or one of these expressions. Defaults to empty, so existing callers (docs-only exemption) are unaffected. The internal `docs_only` signal is generalized to `exempt`. This lets consuming repos carve out low-risk paths (e.g. a web app) without forking the workflow. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f1ded9b094
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| echo "$PR_FILES" | jq -r ' | ||
| EXTRA_PATTERNS=$(printf '%s' "${EXEMPT_PATHS:-}" | jq -R -s 'split("\n") | map(select(length > 0))') | ||
| EXEMPT=$( | ||
| echo "$PR_FILES" | jq --argjson extra "$EXTRA_PATTERNS" -r ' |
There was a problem hiding this comment.
Slurp paginated file pages before evaluating exemptions
When an exempt_paths PR spans more than one GitHub API page, this jq invocation processes each gh api --paginate page as a separate JSON array/object (the GitHub CLI docs for --paginate say to use --slurp to wrap all pages). An all-exempt changeset then sets EXEMPT to multiple lines such as true\ntrue, so the later [ "$EXEMPT" = "true" ] check fails and the bot PR still requires two approvals. This breaks the new exemption for large generated/app-only updates; slurp and flatten the paginated pages before applying all(...).
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Thanks, but I believe this is a false positive. gh api --paginate already merges JSON-array responses into a single array, so jq runs once and EXEMPT is a single value — --slurp does the opposite (it nests pages into an array-of-pages).
Verified empirically against a 14-item array forced into 7 pages:
$ gh api ".../labels?per_page=2" --paginate | jq -r 'length'
14
If pages were emitted separately, that would print 2 seven times; instead jq sees one merged array of 14. The pre-existing docs-only exemption already relies on this same auto-merge, and this PR doesn't change the gh api --paginate fetch. So no change is needed here.
What
Adds an optional, backward-compatible
exempt_pathsinput to the reusable Bot Proxy Gatekeeper workflow.It's a newline-separated list of regular expressions matched (via
jq'stest()) against changed file paths. A bot-authored PR is exempt from the 2-human-approval requirement when every changed file matches either the existing built-in docs patterns or one of these expressions — the sameall(...)semantics already used for docs-only changes.Why
Consuming repos want to carve out low-risk paths from the double-human-review requirement (our motivating case: the
apps/price-terminalweb app inpyth-lazer) without forking this workflow. Since the exempt path is repo-specific, it belongs in the caller's wrapper as an input rather than hardcoded here.A caller opts in like so:
Backward compatibility
exempt_pathsdefaults to"". Callers that pass nothing get the exact existing docs-only behavior (verified with unit tests of the jq predicate).docs_onlyis renamed toexempt(it now covers more than docs). These are step-local outputs only — not exposed viaworkflow_call.outputs— so no caller can depend on them.Bot Proxy Gatekeeper (Human Review)) and PR comment marker are unchanged, so existing required-status-check rulesets keep working.Testing
Validated the jq exemption predicate against: price-terminal-only (exempt), mixed price-terminal + Rust (not exempt), docs-only (exempt), price-terminal + docs (exempt), non-exempt code (not exempt), empty file list (not exempt), and both docs-only and code-only with no
exempt_pathsset (back-compat: unchanged). All pass.Follow-up
A companion PR in
pyth-lazerwill update its wrapper to passexempt_paths: '^apps/price-terminal/'. That change must land after this merges tomain, since the wrapper pins@mainand passing an undeclared input would error.🤖 Generated with Claude Code