Skip to content

fix(classify): drop false-red exit-1 on blocked class#21

Merged
topcoder1 merged 1 commit into
mainfrom
fix/classify-no-false-red
May 2, 2026
Merged

fix(classify): drop false-red exit-1 on blocked class#21
topcoder1 merged 1 commit into
mainfrom
fix/classify-no-false-red

Conversation

@topcoder1

Copy link
Copy Markdown
Owner

Summary

pr-classify.yml has been exiting 1 on any PR that touches blocked paths (.github/workflows/**, Dockerfile*, **/secrets*, infra/**, etc.). That made both the PR Classify check and the PR Codex Review check (which calls the same reusable for its classify job) show red on every infra-touching PR — even when the actual code is fine and the maintainer is going to manually click merge anyway.

Why the exit-1 was dead code

Confirmed on topcoder1/webcrawl (the canonical caller):

$ gh api repos/topcoder1/webcrawl/rulesets/15787428 --jq '...'
required checks: [review / Claude Review, build, lint]

The classify check is not in the required-status-check list, so the exit-1 never actually blocked merge. It was pure UI noise.

The real auto-merge gate is claude-author-automerge.yml's risk-tier path regex (lines 100-115 of that workflow). It already includes ^\.github/workflows/.* and refuses to enable auto-merge when matched, posting a sticky comment instead. That gate works without failing the classify check.

What this PR changes

  • Replaces exit 1 with a ::warning:: annotation (visible in the Actions log).
  • Adds a sticky PR comment (<!-- pr-classify:blocked --> marker) explaining the manual-merge policy inline on the PR.
  • Keeps the risk:blocked label (downstream gates still consume it).
  • Keeps the job output class=blocked (downstream gates still consume it).
  • The classify job now always exits 0 — its job is to classify, not to gate.

Net effect: callers get a green-or-failed CI status that reflects code correctness. Auto-merge gating stays exactly where it was. The "manual merge required" semantics are now a comment + warning + label, not a fake-red check.

Verification

  • yaml parses; actionlint clean.
  • Bash syntax check on the extracted run block.
  • Sticky-comment marker (<!-- pr-classify:blocked -->) idempotent across re-runs.

Auto-merge rationale

This PR modifies .github/workflows/pr-classify.yml (a workflow file). Per CLAUDE.md auto-merge policy, that's a high-risk surface — manual merge is required. The classify workflow on this very PR will demonstrate the new behavior (warning + comment + green check, instead of red + exit-1).

Codex pre-review

Skipped (workflow-only diff, behavioral change is small and obvious).

🤖 Generated with Claude Code

The "Block merge for blocked class" step in pr-classify.yml has been
exiting 1 on any PR that touches blocked paths
(.github/workflows/**, Dockerfiles, secrets, infra, etc.). This makes
both the PR Classify check AND the PR Codex Review check (which uses
the same reusable for its classify job) report failure on the PR UI.

But the exit-1 was never doing real work: caller repos' branch-protection
rulesets only require the actual CI checks (Claude Review / build / lint),
not the classify check. The auto-merge gate that *actually* prevents
risky-PR auto-merge lives in claude-author-automerge.yml — it reads the
class output and the path regex, refuses to enable auto-merge, and
posts a sticky comment instead. That gate works without failing CI.

So the exit-1 is pure visual noise:
  - It doesn't block merge (branch protection doesn't require this check).
  - It conflates "code is broken" with "policy says manual merge".
  - Maintainers click merge anyway, alarm-fatigued by the red dot.

Fix: replace exit-1 with a ::warning:: annotation plus a sticky comment
on the PR explaining the policy. The classify job exits 0; the
risk:blocked label is still set; downstream gates still consume the
class output.

Verified on topcoder1/webcrawl PR #124: branch-protection ruleset
required-checks = [Claude Review, build, lint] (gh api repos/.../rulesets);
no classify check is required. Auto-merge gating happens in
claude-author-automerge.yml's own risk-tier path regex (which already
matched .github/workflows/** before this change).
@claude

claude Bot commented May 2, 2026

Copy link
Copy Markdown

No issues found. Diff is small and contained: removes exit 1, adds a ::warning:: annotation and an idempotent sticky comment. Class output and label are preserved for downstream consumers.

@topcoder1
topcoder1 merged commit 35c4c78 into main May 2, 2026
4 checks passed
@topcoder1
topcoder1 deleted the fix/classify-no-false-red branch May 2, 2026 18:21
topcoder1 added a commit that referenced this pull request May 5, 2026
)

## Summary

Mirrors the \`automerge_pat\` pattern already present in
[\`claude-author-automerge.yml\`](.github/workflows/claude-author-automerge.yml#L51)
(added in #21, verified on \`whois-api-llc/wxa_webcat#201\`). Without this,
callers that wire \`secrets: inherit\` get the PAT upgrade for the
Claude-author lane but not for safe-paths — every safe-paths auto-merge
still pushes as \`GITHUB_TOKEN\` and GitHub suppresses downstream push
workflows.

## Why


[whois-api-llc/wxa_vpn#251](whois-api-llc/wxa_vpn#251)
— three consecutive safe-paths auto-merges (wxa_vpn#247, #249, #250)
merged via the bot identity \`app/github-actions\` and produced **zero**
push-on-main workflow runs (no Lint, no Tests, no Build & Deploy). #249
in particular was a fix for a 25-hour production outage; the kill-after
fix sat in main for ~3 hours undeployed until a human kicked
\`workflow_dispatch\` manually.

#248 in the same window was human-merged and fired the full CI chain —
confirming the GITHUB_TOKEN loop-prevention is the load-bearing
variable.

## Behavior change

- **PAT wired in caller**: merge commit is attributed to the PAT
identity → downstream push-on-main workflows fire normally.
\`USING_PAT=1\` log line.
- **PAT NOT wired**: falls back to \`GITHUB_TOKEN\` (current behavior)
and emits a \`::warning::\` line so the silenced-deploy condition is
visible in the run log instead of failing silently.

## Auto-merge rationale

**Manual click-merge** — touches \`.github/workflows/\` (CI infra). The
reusable affects every fleet caller, so reviewer should confirm the
conditional fallback looks right before shipping.

## Codex pre-review

PASS — single-file YAML change, mirrors a verified pattern, adds an
opt-in path with safe fallback. The expression \`secrets.automerge_pat
!= '' && '1' || '0'\` is the same idiom used in
claude-author-automerge.yml line 366.

## Test plan

- [ ] Merge this PR
- [ ] In any caller repo with \`secrets: inherit\`, set repo secret
\`automerge_pat\` to a fine-grained PAT (contents r/w, pull-requests
r/w)
- [ ] Open a docs-only PR and confirm:
- Workflow log shows \`Auth: caller-supplied automerge_pat — downstream
workflows WILL trigger on merge.\`
- After auto-merge resolves, \`gh api
repos/.../actions/runs?head_sha=<merge_sha>\` returns Lint, Tests, and
Build & Deploy runs
- [ ] In a caller WITHOUT \`automerge_pat\` set, confirm the
\`::warning::\` line appears (and current behavior is unchanged
otherwise)

🤖 Generated with [Claude Code](https://claude.com/claude-code)
topcoder1 added a commit that referenced this pull request Jun 10, 2026
…otate (#95)

## Root cause this responds to

On 2026-06-10 15:20–16:21Z, GitHub had a platform incident: *"sporadic
authentication failures, impacting approximately 15% of API traffic.
Erroneous 401 responses"* (githubstatus.com). Valid `GITHUB_TOKEN`s were
intermittently treated as anonymous. In
whois-api-llc/disposable-email-domains (first blocked-path PR since
codex-review install), this reddened `classify / Classify PR Risk` — a
**required** check there — on two consecutive pushes (runs 27288936851,
27289393205, 27289393136). Same job, same token: `gh pr edit` succeeded
at 16:02:59 and a `gh` write 401'd at 16:03:01. A rerun 20h later passed
unchanged — **there was no token-plumbing bug** (the suspected missing
`GH_TOKEN`/`secrets: inherit` was a red herring; `github.token` was set
at every step).

The incident exposed two real structural weaknesses, fixed here:

## Changes

1. **`retry` on every gh call that gates the job** (3 attempts, 5s/10s
backoff). Stdout is buffered per attempt so command substitutions never
capture error bodies from failed attempts. Turns a 15% per-call platform
blip into ~0.3%.
2. **Annotate step is now truly non-fatal** — its own comment has always
promised "this step does NOT exit non-zero", but a gh failure violated
that on 2026-06-10. Comment-write failures downgrade to `::warning::`
(it's a policy notice; gating lives in the label +
claude-author-automerge regex).
3. **New `skip_label` input** — fleet repos run this reusable from TWO
workflows (standalone PR Classify + embedded in PR Codex Review), racing
to write the same label and sticky comment, and producing two check
contexts both named `classify / Classify PR Risk` (polluting
required-check evaluation). Embedded callers will pass `skip_label:
true` and just consume the `risk_class` output. Follow-up: fleet
caller-template rollout renames the embedded job to `codex-classify` so
the contexts stop colliding.
4. Fix stale 'exits 1' comment in this repo's own codex caller (exit-1
dropped in #21). NOTE: this repo's own caller does **not** get
`skip_label: true` — it's the only labeler here (no standalone classify
caller in ci-workflows).

Backward compatible: `skip_label` defaults to `false`; existing callers
see retry hardening only.

## Verification

- `actionlint` (incl. shellcheck on run blocks): PASS
- retry helper unit-tested locally: flaky-cmd (fail×2 with garbage
stdout, then success) → substitution captured only clean output, 3
attempts; always-fail in `$(… || true)` → empty capture, no script abort

**Codex pre-review:** PASS — no P1. Two P2s: (1) `gh pr view` in label
step un-retried → fixed in this PR (wrapped in retry); (2) pre-existing
word-splitting if a risk label name contained spaces → declined, label
names come from the classifier's fixed enum (no spaces possible).

**Auto-merge rationale:** manual click-merge — `.github/workflows/**` is
on the high-risk list, and this is the fleet-wide reusable: a bad merge
breaks classify on every PR in 40+ repos until reverted.

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

Co-authored-by: Claude Fable 5 <[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