Skip to content

ci(cla-rerun): start the CLA janitor on each CLA failure; re-run guard rejects on PRs that propose no policy - #25525

Merged
teamleaderleo merged 2 commits into
mainfrom
ci/cla-janitor-on-reject
Oct 26, 2026
Merged

teamleaderleo merged 2 commits into
mainfrom
ci/cla-janitor-on-reject

Conversation

@teamleaderleo

@teamleaderleo teamleaderleo commented Oct 26, 2026 •

Copy link
Copy Markdown
Collaborator

The CLA quota janitor (#25415) re-ran quota-caused CLA failures, but too late. p0-swift-linux reported that from about 12:40Z on 2026-10-26 the CLA policy guard rejected every PR, and #25466 was blocked for 80 minutes.

Root cause (from the run logs)

Change

  • ci-cla-quota-rerun.yml now also runs on workflow_run completed of CLA Assistant and CLA policy guard. A job starts only for a pull_request_target run that failed, or for a completed re-run (attempt above 1), so a backlog drains one pass after another. The cron stays as a fallback. No new secrets, no permission changes, and no pull_request_target. The job reads nothing from the triggering run and checks out its own default-branch script. SOURCE_WORKFLOW_PATHS is declared, as test_ci_workflow_run_sources requires.
  • Concurrency moves to the job, so the comment-triggered CLA runs (about one a minute, all skipped) never take the pending slot.
  • A pass that finds the budget spent now waits up to 40 min for the reset (was 15). Job timeout is 47 min. The App token is minted at job start and lives 60 min.
  • New guard evidence that needs no x-ratelimit header, no 403 wording and no CLA Assistant failure: if a PR's files (pulls/N/files, at most 3 pages) touch none of the paths validate-cla-policy.rb reads as policy (CLA_POLICY_PATHS + GUARD_PATHS, renames included), the PR proposes no policy and a failed read is the likely cause. This is a retry heuristic, not proof: a stale event base, or an old policy with no usable test-merge commit, can still be rejected for real. So it is used on attempts 1 and 2 only, and a real reject of that kind stays failed after that. A test keeps POLICY_PATHS equal to the guard's own lists.
  • Open PRs are now read most recently updated first (sort=updated). More than 500 are open, and a fresh reject's PR was just updated.

A real policy reject still fails: when a PR changes a policy path, the guard is re-run only on the existing API-failure evidence.

Not changed: the guard's own token

The guard's GH_TOKEN is pinned to ${{ secrets.GITHUB_TOKEN }} by validate-cla-policy.rb (GUARD_VALIDATE_ENV), and the guard workflow's bytes are digest-pinned (EXPECTED_GUARD_WORKFLOW_DIGEST). Moving it to the glaeda-route App budget, as #25408 did for risk tier, would mean exposing the App key to a pull_request_target control-plane workflow. That is a trust-policy change, so this PR does not make it.

Risk

Low and CI-only. A pass reads about 12 calls from the App's own budget and makes only the free rate_limit read of the workflow token. Failure triggers that arrive during a pass collapse into one pending pass. A run that keeps failing for real stops at MAX_GUARD_ATTEMPT / NO_POLICY_MAX_ATTEMPT.

Tests

cmux-ci test (fast-checks run 40357180685): tests/test_cla_quota_rerun.py plus the workflow_run-sources, concurrency, job-scoped-permissions, actionlint-coverage and workflow_run-budget suites gave 88 passed. The one failure, test_ci_failfast_keeps_failure_rollups_and_bounds_observed_tails ("ci.yml has no tests job"), fails the same way on main. check_workflow_concurrency.py and actionlint 1.7.7 on the workflow both pass. On the final head, ci-guards.yml runs tests/test_cla_quota_rerun.py (29 tests OK, and the wiring guard passes).

Review: an independent Codex gpt-6-astra xhigh reviewer gave SHIP on head e775a3f, with no blocking findings. Its one nonblocking note (call the no-policy evidence a heuristic) is addressed.

Agent session: agent session

🤖 Generated with Claude Code

…d rejects on PRs that propose no policy

The CLA quota janitor ran only on a cron GitHub fired late or not at all
(12:35Z to 14:19Z on 2026-10-26, while #25466 sat blocked 80 min), and a
pass that found the GITHUB_TOKEN budget spent with the reset over 15 min
away did nothing (15:42Z: 0 of 5000 left, reset 16:13Z).

- Trigger on workflow_run completed of CLA Assistant and CLA policy guard:
  a pull request's failure, or the completion of a re-run (so a backlog
  drains pass by pass). The cron stays as the fallback. Concurrency moves to
  the job so comment-triggered CLA runs, which start no job, never displace
  a pending pass.
- Wait up to 40 min (was 15) for a near reset; job timeout 47 min.
- New guard evidence that needs no x-ratelimit header or 403 wording: a pull
  request whose files touch none of the paths validate-cla-policy.rb reads as
  policy proposes no policy, so only a failed read can reject it (attempts 1
  and 2 only). A test keeps POLICY_PATHS equal to the guard's lists.
- Read open pull requests most recently updated first (over 500 are open).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-85ca59f74f02d81734c3
@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@teamleaderleo

Copy link
Copy Markdown
Collaborator Author

Owned by an agent session · session

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

cmux status (one comment, edited in place; each line is updated by the job that owns it)

Passes: CI passes on e775a3f6b0.

CI passes on e775a3f6b0 (run 40358917457 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's; yours means the failing file is one this PR changes, also red on main that main's latest full suite fails the same way, seen on other PRs that it failed on another pull request's run lately.

CI fast guards passes on e775a3f6b0 (run). This covers only the fast guards, not CI: CI's result is ci-status.

Merge receipt for e775a3f6b0: every check was green at merge (10 verified; 16 skipped by policy). Full suite runs on main after merge.

…ce a retry heuristic

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent-Session: https://cmux.com/deeplink/agent?id=aid-85ca59f74f02d81734c3
@teamleaderleo
teamleaderleo merged commit 42c27fa into main Oct 26, 2026
58 checks passed
@teamleaderleo
teamleaderleo deleted the ci/cla-janitor-on-reject branch October 26, 2026 16:29
teamleaderleo added a commit that referenced this pull request Oct 26, 2026
…B_TOKEN as fallback (#25611)

The guard's GitHub reads (the pull request, its files, the test merge and,
for a policy change, the reviews) now use a token minted from the
manaflow-glaeda-route App: GITHUB_TOKEN's core budget, shared by every
workflow, ran out hourly on 2026-10-26 and the guard then rejected every
pull request until the reset (#25525).

- cla-policy-guard.yml: a pinned actions/create-github-app-token step
  (read-only contents and pull-requests, this repository only,
  continue-on-error, 1-minute timeout) after the checkout verification; the
  validation step reads steps.glaeda-token.outputs.token ||
  secrets.GITHUB_TOKEN. Its shell is unchanged.
- validate-cla-policy.rb admits exactly that layout. Any write permission,
  other action, moved key, missing fallback or reordered step is rejected
  (new regression cases). EXPECTED_GUARD_WORKFLOW_DIGEST and
  EXPECTED_GUARD_SCRIPT_DIGEST move to the new bytes.
- tests/test_cla_guard_metadata_routing.py pins both digests to the files
  and checks the read-only, fallback and rejection contract.


Agent-Session: https://cmux.com/deeplink/agent?id=aid-85ca59f74f02d81734c3

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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