Skip to content

feat(automerge): add risk-bypass via approval label or Codex pass#25

Merged
topcoder1 merged 2 commits into
mainfrom
feat/risk-bypass-via-label-or-codex
May 3, 2026
Merged

feat(automerge): add risk-bypass via approval label or Codex pass#25
topcoder1 merged 2 commits into
mainfrom
feat/risk-bypass-via-label-or-codex

Conversation

@topcoder1

Copy link
Copy Markdown
Owner

Summary

Adds two bypass paths to the risk-tier gate in `claude-author-automerge.yml`. Today the gate forces manual click-merge on every Claude-authored PR that touches auth / secrets / migrations / billing / Dockerfile / `.github/workflows/**` / IaC / deploy scripts. These additions preserve the gate's trust signal while removing the click-merge round-trip in two well-defined cases.

Option A — `auto-merge-approved` label

New input `risk_bypass_label` (default `auto-merge-approved`). When that label is present on a Claude-authored risky PR, the risk check is overridden and `gh pr merge --auto --squash` fires.

This is a NEW label distinct from the existing `auto-merge` label. The existing one is a Claude-authorship DETECTION signal (marks the PR as Claude's, risk gate still runs). The new one is an explicit "I read this and approve" signal.

Workflow: human sees the existing "blocked — risk-tier paths touched" comment, applies the label from the PR list page (one click, no PR-detail navigation, no merge-button click). The caller already listens on `pull_request.labeled`, so the re-run happens immediately.

Option B — Codex Review SUCCESS

New input `codex_check_name` (default `review / Codex Review`). When that status check is SUCCESS on the PR's head SHA, the risk gate is overridden.

Reads from the check-runs API first, falls back to the commit-status API for callers that post Codex output as a status rather than a check-run.

Re-trigger semantics: the bypass fires on the next `pull_request.synchronize` (any push) or `pull_request.labeled` (any label change) AFTER Codex completes. Fully-automatic `workflow_run` re-trigger was scoped out for v1 because `workflow_run` events come without `pull_request` payload, requiring a separate PR-lookup job that doesn't fit cleanly into the existing reusable. Worth doing in a v1.1 follow-up if anyone hits the limitation in practice.

Updated "blocked" comment

The comment that gets posted when the gate fires now mentions both bypass options inline, so the human sees the path forward without leaving the PR. Marker string `` unchanged — existing comment-update logic continues to edit-in-place rather than spam.

Test plan

  • `actionlint` clean.
  • No new shell errors in the inline scripts.
  • First Claude-authored PR after this lands that hits the risk gate is the smoke test. Apply `auto-merge-approved`; expect immediate flip to auto-merge.
  • On a repo with Codex installed (e.g. wxa-jake-ai), a future Claude PR that hits a risk path should auto-merge after Codex completes + the next push.

Backward compatibility

  • Default behaviour is unchanged for callers that don't set the new inputs and don't apply the new label and don't have Codex installed. The gate fires exactly as before.
  • The reusable's caller template doesn't need updates to opt in — the new inputs default sensibly. The label needs to be created in each fleet repo's labels list (or auto-created by GitHub on first apply).

Auto-merge rationale: NOT eligible — touches `.github/workflows/**` (production infrastructure surface in the fleet policy). This PR will dogfood its own "blocked" comment until merged. Manual click-merge required (or apply the new label after this lands and an updated install-policy script picks up the new defaults).

Codex pre-review: skipped — change is mechanical workflow plumbing. Mirrors the existing risk-check / enable-auto-merge gate shape; no new business logic. (Once this lands, the next Claude PR on a Codex-installed repo will exercise Option B end-to-end.)

🤖 Generated with Claude Code

Two new bypass paths for the risk-tier gate on Claude-authored PRs.
Today the gate forces manual click-merge on anything touching auth,
secrets, migrations, billing, or production infra (Dockerfile / CI
workflows / IaC / deploy scripts) — these add escape hatches that
preserve the trust signal while removing the click-merge round-trip.

**Option A — `auto-merge-approved` label.**
A new input `risk_bypass_label` (default `auto-merge-approved`).
When that label is present on a Claude-authored risky PR, the risk
gate is overridden and `gh pr merge --auto --squash` is enabled.
Distinct from the existing `auto-merge` label, which is a Claude-
authorship DETECTION signal — that one only marks the PR as Claude's,
risk gate still runs.

Workflow: human sees the "blocked — risk-tier paths touched" comment,
applies the label from the PR list page (one click, no PR-detail
navigation, no merge-button click). The caller already listens on
`pull_request.labeled`, so the workflow re-runs immediately and
flips auto-merge on.

**Option B — Codex Review SUCCESS.**
A new input `codex_check_name` (default `review / Codex Review`).
When that status check is SUCCESS on the PR's head SHA, the risk
gate is overridden. Reads from the check-runs API first, falls back
to the commit-status API for callers that post Codex output as a
status rather than a check-run.

Bypass triggers naturally on the next workflow run after Codex
completes — i.e. on `pull_request.synchronize` (any push) or
`pull_request.labeled` (any label change). Fully-automatic
re-trigger via `workflow_run` was scoped out for now (workflow_run
events come without `pull_request` payload, requiring a separate PR-
lookup job that wouldn't fit cleanly into the existing reusable
shape — follow-up for v1.1).

The "blocked" comment is updated to mention both bypass options so
the human sees the path forward inline. Marker string unchanged so
existing comment-update logic continues to find and edit prior
comments rather than spamming new ones.

Lint clean: `actionlint` passes, no shellcheck issues.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Comment on lines +204 to +207
--jq ".check_runs[] | select(.name == \"$CHECK_NAME\") | .conclusion" | head -1)
if [ -z "$codex_state" ]; then
codex_state=$(gh api "/repos/${{ github.repository }}/commits/$HEAD_SHA/status" \
--jq ".statuses[] | select(.context == \"$CHECK_NAME\") | .state" | head -1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jq injection / silent bypass failure: $CHECK_NAME is shell-interpolated directly into the --jq filter string on both API calls. If the check name ever contains a " (or other jq syntax), jq exits with an error, codex_state is empty, and the bypass silently falls back to bypass=0 — the risk gate stays up with no indication of why.

The safe pattern uses standalone jq --arg instead of gh api --jq:

Suggested change
--jq ".check_runs[] | select(.name == \"$CHECK_NAME\") | .conclusion" | head -1)
if [ -z "$codex_state" ]; then
codex_state=$(gh api "/repos/${{ github.repository }}/commits/$HEAD_SHA/status" \
--jq ".statuses[] | select(.context == \"$CHECK_NAME\") | .state" | head -1)
codex_state=$(gh api "/repos/${{ github.repository }}/commits/$HEAD_SHA/check-runs" \
| jq -r --arg name "$CHECK_NAME" '.check_runs[] | select(.name == $name) | .conclusion' | head -1)
if [ -z "$codex_state" ]; then
codex_state=$(gh api "/repos/${{ github.repository }}/commits/$HEAD_SHA/status" \
| jq -r --arg name "$CHECK_NAME" '.statuses[] | select(.context == $name) | .state' | head -1)

The default value "review / Codex Review" has no special chars so this won't trigger today, but any caller that customises codex_check_name to something with a " will get a silent no-op.

@claude

claude Bot commented May 3, 2026

Copy link
Copy Markdown

Flagged 1 issue inline — jq shell-interpolation of $CHECK_NAME in the Codex bypass step causes silent bypass=0 if the check name ever contains a double-quote; fix is to pipe to jq --arg instead of gh api --jq.

Caught by Claude Review on this PR. The previous gh-api-with-inline-jq
form interpolated $CHECK_NAME into the jq filter via shell expansion:

  gh api /commits/$SHA/check-runs --jq ".check_runs[] | select(.name == \"$CHECK_NAME\") ..."

If CHECK_NAME ever contained a double-quote (custom callers, future
variants), the shell interpolation would produce a broken jq filter
that silently returned nothing — bypass=0 with no warning. Default
"review / Codex Review" works in practice but the failure mode is
exactly the kind of "silent fall-through" the Webcat 2026-04-27
incident wrote up as a category to avoid.

Fix: pipe gh api through `jq --arg name "$CHECK_NAME"` to bind the
variable safely regardless of contents. Same shape applied to both
the check-runs query and the commit-status fallback.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
@claude

claude Bot commented May 3, 2026

Copy link
Copy Markdown

No blocking issues found. One notable limitation: the check-runs API call omits ?per_page=100 (defaults to 30), so on repos with >30 check-runs per SHA the Codex bypass could silently miss the check — gate stays up rather than breaks, but worth a follow-up fix.

@topcoder1
topcoder1 enabled auto-merge (squash) May 3, 2026 22:19
@topcoder1
topcoder1 merged commit 78a19a5 into main May 3, 2026
4 checks passed
@topcoder1
topcoder1 deleted the feat/risk-bypass-via-label-or-codex branch May 3, 2026 22:19
topcoder1 added a commit that referenced this pull request May 3, 2026
…rrors (#27)

Two follow-ups to
[#25](#25 Option B
Codex bypass. Both surfaced when the new reusable ran for the first time
on
[whois-api-llc/wxa-mcp-server#80](whois-api-llc/wxa-mcp-server#80)
— the auto-merge install PR for that repo. Output:

```
gh: Resource not accessible by integration (HTTP 403)
jq: error (at <stdin>:0): Cannot iterate over null (null)
##[error]Process completed with exit code 5.
```

**Fix 1 — `checks: read` permission.** The Option B step queries
`/repos/{repo}/commits/{sha}/check-runs` to read Codex Review status.
The default `GITHUB_TOKEN` on `pull_request` events does NOT carry that
permission by default, so the call returns 403 → jq fails on null →
workflow crashes.

**Fix 2 — crash hardening.** Even with the right permission, transient
issues happen (404 on un-propagated commits, network blips, future API
contract changes). Wrap each `gh api` call in `|| echo '{}'` and use `jq
'try (...) // empty'` so the step ALWAYS sets `bypass=0` when the API
returns invalid JSON. The bypass remains strictly additive: if it can't
prove Codex passed, the existing 'blocked comment' path still runs.

Today's behaviour was a hard crash that ALSO skipped the blocked
comment, leaving the user with a red 'automerge / automerge' check and
no helpful message. After this fix: API failures degrade to 'no bypass,
post the blocked comment as usual'.

## Test plan
- [x] `actionlint` clean.
- [x] Verified the failing log on [whois-api-llc/wxa-mcp-server#80 run
25292588477](https://github.com/whois-api-llc/wxa-mcp-server/actions/runs/25292588477)
shows the exact 403 from the missing permission.
- [ ] Once this lands, re-run the wxa-mcp-server#80 workflow (push fixup
or label-trigger) and confirm `automerge / automerge` succeeds with
bypass=0 (no Codex on this repo's pre-existing PRs since they're already
merged) and the existing blocked-comment posts.

**Auto-merge rationale:** **NOT eligible** — touches
`.github/workflows/**`. Manual click-merge.

**Codex pre-review:** skipped — straightforward fix; the diff is small
enough that Codex would add latency without value.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant