Skip to content

[codex] Harden release gate required check matching#3763

Merged
justin808 merged 13 commits into
mainfrom
codex/issue-3413-release-gate-app-id
Jun 7, 2026
Merged

[codex] Harden release gate required check matching#3763
justin808 merged 13 commits into
mainfrom
codex/issue-3413-release-gate-app-id

Conversation

@justin808

@justin808 justin808 commented Jun 7, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #3413.

  • Preserve branch-protection checks[].app_id instead of flattening required checks to plain names.
  • Match modern required check runs by both context and pinned app ID, with null and -1 treated as app wildcards.
  • Fetch commit statuses for legacy contexts so status-API-only required contexts are evaluated, including same-name legacy status failures that GitHub branch protection would still reject.
  • Treat GitHub mirrored legacy contexts and modern checks entries as one required gate while preserving app-pinned check enforcement.
  • Keep the Claude main-CI hook app-aware for modern check matching while preserving its fail-open display role.

Decision log

  • Legacy contexts are evaluated alongside modern checks; a fetched failing legacy status blocks even when a same-named modern check run passes, matching GitHub branch-protection behavior.

  • GitHub can mirror the same required check in both legacy contexts and modern checks; mirrored contexts are compatibility data, not a second gate. The GitHub REST docs describe legacy contexts as closing down and checks as the app-aware shape for fine-grained control (https://docs.github.com/en/rest/branches/branch-protection#update-status-check-protection). App-wildcard checks can be satisfied by either a matching Checks API run or a legacy commit status, while app-pinned checks still require the matching app check run.

  • NON_BLOCKING_DECISION after connector P1: keep same-name mirrored-context dedup for app-pinned checks. Treating a mirrored legacy contexts entry as a separate required Commit Status would false-block app-pinned Checks API gates that GitHub reports through both response fields for compatibility.

  • App-pinned modern checks keep prerelease filtering app-aware; a same-name check from a different GitHub App must not block a prerelease when the required app-pinned check passed.

  • If distinct required gates still share a display label, abort messages annotate the duplicate label with a gate count in both Required: and Missing: output.

  • The release gate fetches commit statuses whenever branch protection has legacy contexts. Deferring that API call until a missing-context gap is visible is a possible performance/resilience follow-up, but this PR keeps the conservative behavior so same-name legacy failures are surfaced.

  • If the legacy status fetch returns nil after a dry-run/override warning, the validator keeps evaluating fetched check runs with an empty legacy-status set; strict mode aborts before that path can return.

  • The hook intentionally remains a fail-open status display. It mirrors app-aware modern check matching, uses app-agnostic legacy context presence for display, does not fetch commit statuses, and leaves actual enforcement to the Ruby release gate.

  • Silent nil/blank legacy status target URLs are intentionally omitted from release-gate output; this avoids printing nil for legacy statuses without a target URL and is part of the output cleanup.

  • NON_BLOCKING_DECISION after Claude advisory pass: do not restart CI for comment-only, boolean-local naming, or parallel-path coverage polish after current-head validation. Reporting failed required checks before missing/in-progress required checks is intentional operator UX; the release gate has no supported downstream error-kind parser contract. The legacy-status-unavailable dry-run/override path already emits the fetch warning and then avoids a misleading green final status.

  • FOLLOWUP: optional readability/coverage polish can tighten the shell hook comment pointer, spell out the ISO 8601 nil-sort invariant, rename the boolean local for clarity, and add a mirror spec for the allow-override legacy-status-unavailable path.

  • NON_BLOCKING_DECISION after second Claude advisory pass: keep the conservative release gate semantics. A stale failing legacy commit status can still block even if a same-name modern check run passes; --allow-override remains the explicit escape hatch. The stable-release healthy count uses the distinct required-gate count when branch-protection metadata is available, while raw evaluated-run length is only the fallback when that metadata is missing.

  • FOLLOWUP: optional auditability polish can add a nil/-1 wildcard duplicate-label fixture, a legacy-only override fetch-failure fixture, and a display-only hook annotation for commit-status-only legacy contexts. These do not change the merge qualification for this PR.

Validation

Head SHA: 9cff463d4852d053a226e7281b3e3a44d7a84cda

  • ruby -c rakelib/release.rake - Syntax OK.
  • ruby -c react_on_rails/spec/react_on_rails/release_rake_helpers_spec.rb - Syntax OK.
  • bash -n .claude/hooks/main-ci-status.sh - passed.
  • cd react_on_rails && bundle exec rspec spec/react_on_rails/release_rake_helpers_spec.rb - 114 examples, 0 failures.
  • bundle exec rubocop --ignore-parent-exclusion rakelib/release.rake react_on_rails/spec/react_on_rails/release_rake_helpers_spec.rb - 2 files inspected, no offenses detected.
  • git diff --check origin/main...HEAD && git diff --check - passed.
  • script/ci-changes-detector origin/main - recommends full CI and benchmarks for release-gate changes.
  • codex review --base origin/main - no actionable correctness issues found; reran release helper specs, shell syntax checks, and RuboCop during review.
  • Pre-commit hook - trailing-newlines, autofix, RuboCop passed.
  • Pre-push hook - branch-lint/RuboCop and markdown-links passed.

Label recommendation

full-ci, benchmark

Adversarial review gate

Head SHA: 9cff463d4852d053a226e7281b3e3a44d7a84cda

  • BLOCKING: none found after current-head review fixes.
  • DISCUSS: none requiring maintainer input.
  • FOLLOWUP: consider adding a dedicated shell-test harness for .claude/hooks/main-ci-status.sh; current validation covers shell syntax and the changed jq expression by inspection. Consider deferring the legacy Statuses API call until only missing app-wildcard contexts need fallback data, extracting a shared gh JSONL API helper, and adding an optional stable-release pending-legacy-status contract spec. Keep the current conservative fetch in this PR.
  • NON_BLOCKING_DECISION: the hook intentionally remains fail-open and only mirrors display behavior; the Ruby release gate owns mirrored-context normalization, legacy commit-status fallback, and enforcement.
  • NOISE: no changelog entry needed because this is release tooling behavior, not user-facing gem/runtime behavior.

Data sources: PR metadata/checks/reviews/comments, inline review comments, review threads, changed files, full diff via gh, and the local validation commands above.

Summary by CodeRabbit

  • New Features
    • Release gating now understands both legacy status contexts and modern app‑pinned checks, evaluating them together when determining release readiness.
  • Bug Fixes
    • Deduplication improved so same‑named checks from different apps are treated separately; release messages now show required and missing checks with app context.
  • Tests
    • Specs expanded to cover app‑pinned required checks, legacy status fallback, and combined legacy/modern behavior.

Note

Medium Risk
Changes the release gate’s blocking behavior for origin/main CI (app matching, legacy statuses, evaluation order); mistakes could block or allow releases incorrectly, though coverage is expanded in specs.

Overview
Hardens release and main CI display logic so branch-protection required checks match GitHub more closely: modern gates keep app_id, dedupe includes app, and missing/success messaging lines up with the Ruby gate.

The release gate in rakelib/release.rake stops flattening required checks to plain names. It reads legacy contexts and app-aware checks, drops mirrored duplicate names, fetches commit statuses for legacy/wildcard gates, and treats failing legacy statuses as blocking even when a same-named check run passes. Prerelease filtering matches required runs by context and pinned app; stable still evaluates all visible runs but reports healthy counts from required gates only.

The Claude main-CI hook mirrors app-aware modern matching and mirrored-context handling for display; it still does not fetch commit statuses (fail-open; enforcement stays in Ruby).

Specs in release_rake_helpers_spec.rb cover app-pinned checks, legacy status fallback, and mirrored-context counting.

Reviewed by Cursor Bugbot for commit 9cff463. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Branch-protection required-checks are normalized to {contexts, checks} with optional app_id; check runs and legacy commit statuses are parsed, aggregated and deduped by (suite_id, name, app_id); evaluation and missing-check reporting are performed with app_id-aware matching.

Changes

App-ID-aware required check validation in release gate

Layer / File(s) Summary
GH JSONL parsing & required-check payload normalization
rakelib/release.rake
Adds parse_gh_jsonl, fetch_main_commit_statuses, normalize_status_as_check_run, latest_commit_statuses, and normalizes branch-protection required checks into {contexts, checks} where modern checks include {context, app_id}.
Check run aggregation & app_id-aware deduplication
rakelib/release.rake, .claude/hooks/main-ci-status.sh
Hook and rake aggregation include app_id; dedupe/grouping uses (suite_id, name, app_id) so same-named checks from different apps are not collapsed; replaces manual JSONL parsing with parse_gh_jsonl.
App_id-aware matching and evaluated run set
rakelib/release.rake
Adds check_run_app_id, wildcard support, required_check_matches_run?, derives legacy status runs for legacy contexts, and filters evaluated runs for prerelease using the normalized {contexts, checks} payload.
Missing required checks detection & reporting
rakelib/release.rake, .claude/hooks/main-ci-status.sh
Replaces set-difference logic with label-driven Required/Missing lists that include app_id when applicable; introduces missing_required_checks and format_ci_status_run_line for consistent violation output.
validate_main_ci_status! gating & evaluated set changes
rakelib/release.rake
Gating no longer blocks solely on empty modern check_runs; it incorporates legacy status-derived runs and blocks only if both modern and legacy-derived runs are absent; prerelease evaluation includes legacy-derived runs when appropriate.
Specs: stubs and new test contexts
react_on_rails/spec/react_on_rails/release_rake_helpers_spec.rb
Tests updated to expect {contexts, checks} from required_check_names_for_main; new helpers and contexts cover app-pinned checks, same-name different-app cases, legacy-context status fallback, jq extraction expectations, and empty-response fallback.
Markdown formatting only
analysis/vm-script-caching-investigation-2026-06-07.md
Presentation-only reformatting of tables, code blocks, and spacing; no behavior changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • shakacode/react_on_rails#3407: The main PR extends the existing main-branch CI gate introduced in #3407 by changing main-ci-status.sh/rakelib/release.rake required-check normalization and dedupe/missing-required logic to incorporate app_id.

Suggested labels

review-needed, full-ci, P2, codex

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The analysis/vm-script-caching-investigation-2026-06-07.md changes are formatting/presentation-only and unrelated to the release gate hardening scope, representing out-of-scope whitespace/markdown normalization. Remove or defer the markdown document reformatting changes to a separate PR focused on documentation maintenance.
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'Harden release gate required check matching' directly summarizes the main change: improving the logic for matching required checks by adding app_id awareness.
Linked Issues check ✅ Passed The PR comprehensively addresses all coding requirements from #3413: app_id-aware matching, check deduplication by [name, app_id], legacy context fallback via Commit Statuses API, structured required-check objects, and expanded test coverage.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/issue-3413-release-gate-app-id

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread rakelib/release.rake
@claude

claude Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Code Review — PR #3763: Harden release gate required check matching

Overall: Well-structured change that correctly solves the app-ID matching gap. The separation of legacy contexts from modern checks is the right model, and the fail-open philosophy is consistently applied. No critical bugs found.


✅ What works well

  • The {contexts, checks} split is the correct model — the old flat-union approach silently lost app_id information.
  • required_check_app_wildcard? / app_wildcard in jq correctly treat both null and -1 as wildcards, matching GitHub's own semantics.
  • Dedup grouping extended to (suite_id, name, app_id) in both Ruby and the shell hook — the two stay in sync.
  • legacy_status_runs_for_required_contexts correctly avoids double-counting when a check run with the same name already exists.
  • The return if statuses.nil? guard mirrors the fetch_main_ci_checks pattern consistently.

⚠️ Issues to address

1. Missing unit tests for fetch_main_commit_statuses error paths

fetch_main_commit_statuses has three distinct error paths (ENOENT, non-zero exit, JSON parse failure), but none are covered by direct unit tests. The function is only mocked at the integration level. Given that this introduces a new network call on the release critical path, direct error-path tests would meaningfully increase confidence.

2. No test for a failing legacy commit status

The only test for legacy context satisfaction ("uses commit statuses to satisfy legacy contexts that are not check runs") uses state: "success". There is no test verifying that a legacy context with state: "failure" (or "error") actually blocks the release. The normalize_status_as_check_run mapping (state == "success" ? "success" : state) does produce a non-passing conclusion, but this is untested end-to-end.

3. normalize_status_as_check_run with nil state

If the GitHub API returns a status object without a state field (malformed response), conclusion will be set to nil. The code downstream handles nil gracefully (run["conclusion"] || "incomplete" in format_main_ci_status_violation), so this is not a crash, but an explicit guard or test would make the intent clearer:

"conclusion" => state == "success" ? "success" : (state || "error")

4. Shell hook shows legacy contexts as "missing" when satisfied only by commit status

The hook's missing_required jq expression checks only $all (Checks API runs). If a required context is exclusively satisfied by a commit status — and no same-named check run exists — the hook will display it as missing even though the Ruby release gate would accept it. This is documented as a design decision in the PR, but it means the hook and the release gate can disagree on the same commit. A one-line comment near the contexts filter in the hook would help future readers understand why this asymmetry exists.

5. parse_gh_jsonl duplicates fetch_main_ci_checks inline parsing

The new helper:

def parse_gh_jsonl(output)
  output.lines.reject { |line| line.strip.empty? }.map { |line| JSON.parse(line) }
end

is identical to the inline block in fetch_main_ci_checks (lines 611-613). fetch_main_ci_checks could use it too — not a bug, but the duplication will diverge over time.


💬 Discussion / Follow-up

  • The FOLLOWUP from the adversarial gate (dedicated shell-test harness for main-ci-status.sh) is the right call; the jq -n --argjson pattern makes it tractable to test the new missing_required expression with fixture JSON without a live GitHub API.
  • The required_check_labels(...).uniq dedup is correct but silently collapses cases where a contexts entry and a checks entry produce the same label (e.g., contexts: ["Lint"] + checks: [{context: "Lint", app_id: nil}]). This edge case is unlikely in practice but could produce a misleading "no required checks found" message.

Comment thread rakelib/release.rake Outdated
Comment thread react_on_rails/spec/react_on_rails/release_rake_helpers_spec.rb
Comment thread .claude/hooks/main-ci-status.sh
Comment thread rakelib/release.rake
@justin808 justin808 force-pushed the codex/issue-3413-release-gate-app-id branch from 4363095 to 1b185cf Compare June 7, 2026 18:45
@justin808 justin808 marked this pull request as ready for review June 7, 2026 18:46
Comment thread rakelib/release.rake

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b185cfa45

ℹ️ 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".

Comment thread rakelib/release.rake Outdated
@justin808 justin808 force-pushed the codex/issue-3413-release-gate-app-id branch from 1b185cf to 087a9ae Compare June 7, 2026 18:51
Comment thread rakelib/release.rake Outdated
Comment thread rakelib/release.rake Outdated
Comment thread rakelib/release.rake
Comment thread .claude/hooks/main-ci-status.sh
@justin808 justin808 force-pushed the codex/issue-3413-release-gate-app-id branch from 087a9ae to 1debfb2 Compare June 7, 2026 18:55
@greptile-apps

greptile-apps Bot commented Jun 7, 2026

Copy link
Copy Markdown

Greptile Summary

This PR hardens the release gate in rakelib/release.rake so that branch-protection required checks are matched by both context name and GitHub App ID, and legacy contexts entries (old Commit Status API) are resolved against actual commit statuses when no Checks-API run exists for that context. The session-time main-ci-status.sh hook is updated in parallel to mirror the app-aware dedup and structured required-check shape, but intentionally does not fetch commit statuses.

  • required_check_names_for_main now returns a {contexts:, checks:} hash preserving app_id per modern check entry, replacing the flat name array; all downstream helpers, tests, and the shell hook are updated accordingly.
  • fetch_main_commit_statuses is introduced and legacy_status_runs_for_required_contexts maps the latest status per context into a normalized check-run shape, allowing status-only required contexts to satisfy the missing-required-check gate.
  • Check-run dedup key is extended from [suite_id, name] to [suite_id, name, app_id], preventing a same-named run from a different app from masking the pinned one.

Confidence Score: 4/5

Safe to merge with awareness of one narrow edge case in the missing-check categorization logic.

The rework is well-structured and the new spec cases cover app-pinned and legacy-status paths. The one finding worth watching is in validate_main_ci_status!: when a context name appears in both the contexts and checks lists (possible during a Statuses→Checks migration), and only a commit status satisfies the legacy path, the all-missing length comparison misfires and emits the wrong error kind. Blocking behavior is still correct, but the operator sees a misleading 'no required checks found' message. Everything else — dedup key change, app_id matching, legacy status normalization, and the hook update — looks correct and well-tested.

rakelib/release.rake — the missing_names.length == required_labels.length block around line 915 and normalize_status_as_check_run around line 668.

Important Files Changed

Filename Overview
rakelib/release.rake Core release-gate logic refactored: required checks now carry separate contexts/checks structures, dedup key extended to include app_id, and legacy commit-status fallback added. One edge case in the all-missing detection when a context name appears in both lists; otherwise logic is sound.
.claude/hooks/main-ci-status.sh jq dedup key extended to include app_id; required-check structure changed from flat array to {contexts, checks} object; missing-required logic mirrors the Ruby gate but correctly omits commit-status fetching (fail-open display tool). The check_label function and app_wildcard predicate look correct.
react_on_rails/spec/react_on_rails/release_rake_helpers_spec.rb Spec updated to use the new required_checks/required_check helpers; new contexts for app-pinned requirements and legacy commit-status fallback are well-covered. Helper mocks are consistent with the actual call sites in validate_main_ci_status!.

Sequence Diagram

sequenceDiagram
    participant Rake as validate_main_ci_status!
    participant GH_Checks as gh Checks API
    participant GH_Protection as gh Branch Protection API
    participant GH_Statuses as gh Commit Statuses API

    Rake->>GH_Checks: fetch check runs (JSONL)
    GH_Checks-->>Rake: raw check_runs[]
    Note over Rake: dedup by [suite_id, name, app_id]

    Rake->>GH_Protection: required_status_checks
    GH_Protection-->>Rake: "{contexts:[], checks:[{context,app_id}]}"
    Note over Rake: normalize to required_names

    alt required_names has legacy contexts
        Rake->>GH_Statuses: "commits/{sha}/statuses"
        GH_Statuses-->>Rake: commit statuses[]
        Note over Rake: latest per context → legacy_status_runs
    end

    Note over Rake: guard: check_runs.empty? && legacy_status_runs.empty?

    alt "prerelease && required_names"
        Note over Rake: evaluated = matching check_runs + legacy_status_runs
    else stable release
        Note over Rake: evaluated = all check_runs + legacy_status_runs
    end

    Note over Rake: missing required? → block
    Note over Rake: any failed? → block
    Note over Rake: any in_progress? → block
    Note over Rake: ✓ healthy
Loading

Reviews (1): Last reviewed commit: "ci: harden release gate required check m..." | Re-trigger Greptile

Comment thread rakelib/release.rake
Comment thread rakelib/release.rake

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1debfb2998

ℹ️ 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".

Comment thread rakelib/release.rake Outdated
@justin808 justin808 force-pushed the codex/issue-3413-release-gate-app-id branch from 1debfb2 to a554d83 Compare June 7, 2026 18:58
@claude

claude Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Code Review: Harden release gate required check matching

Overall: Well-structured fix. Separating legacy contexts from modern checks with app_id preservation is the right approach, and the Ruby helper decomposition is clean. One real bug to fix; the rest are design concerns and minor nits.

Real bug — status["url"] fallback is a REST API endpoint, not a web URL

normalize_status_as_check_run line 675 falls back to status["url"] when target_url is absent. The url field in the GitHub Commit Statuses API is the API endpoint (e.g. https://api.github.com/repos/owner/repo/statuses/sha), not a browser page. Release failure messages would display a URL that opens raw JSON. See inline comment on line 675.

Design concern — statuses API failure early-returns past Checks API results

return if statuses.nil? at line 873 causes validate_main_ci_status! to exit before evaluating the Checks-API runs already fetched. In the allow_override: false path abort has already fired, so it is harmless. In dry-run / override paths the operator sees only the statuses-API error and misses actual check-run failures. The pattern mirrors how fetch_main_ci_checks failures are handled — worth a clarifying comment. See inline comment on line 873.

Missing tests for fetch_main_commit_statuses error paths

The three error branches (ENOENT, non-200 status, JSON parse failure) are not exercised by any spec. The legacy-status-context tests cover the happy path well; a small describe block for the error paths would complete the coverage.

Minor nits (see inline comments)

  • check_label jq function in the shell hook should be named required_check_label to signal it operates on required-check entries, not observed check runs.
  • required_check_labels may produce duplicate strings when the same name appears in both contexts and a wildcard checks entry; .uniq handles it correctly but a comment or edge-case test would clarify the intent.

Comment thread rakelib/release.rake Outdated
@justin808 justin808 force-pushed the codex/issue-3413-release-gate-app-id branch from a554d83 to 8e29b31 Compare June 7, 2026 19:02
@claude

claude Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Code Review: Harden release gate required check matching

Overview

This PR correctly addresses a real class of bugs: the old code flattened modern checks[].app_id away, meaning a same-named check run from the wrong GitHub App could satisfy a pinned required check, and status-API-only legacy contexts would be reported as missing even when passing. The architecture is sound — keeping legacy contexts and modern checks separate, matching by (context, app_id), falling back to commit statuses for legacy contexts only.

The new helper functions are well-factored and the test additions cover the main new paths. A few concerns worth addressing before merge:


1. Potential false-positive block when the same context appears in both contexts and checks (wildcard) and is satisfied only by a commit status

In missing_required_check_labels, missing_modern is computed against check_runs only — legacy_status_runs are never consulted for the modern (checks) side:

missing_modern = required_checks[:checks].reject do |required_check|
  check_runs.any? { |run| required_check_matches_run?(required_check, run) }   # only check_runs
end

Consider this scenario (which the GitHub API can produce during a Status-API to Checks-API migration):

  • required_names = { contexts: ["Travis"], checks: [{ context: "Travis", app_id: nil }] }
  • There are no check runs for "Travis"
  • A passing Travis commit status exists, so legacy_status_runs contains "Travis"

Result:

  • missing_legacy correctly excludes "Travis" (found in legacy_status_runs)
  • missing_modern incorrectly includes { context: "Travis", app_id: nil } (no check run)
  • required_labels = ["Travis"] (after uniq); missing_names = ["Travis"]
  • missing_names.length == required_labels.length triggers :no_required_checks abort on a healthy commit

Suggested fix — when a required checks entry has a wildcard app_id, also accept satisfaction from legacy_status_runs:

missing_modern = required_checks[:checks].reject do |required_check|
  check_runs.any? { |run| required_check_matches_run?(required_check, run) } ||
    (required_check_app_wildcard?(required_check[:app_id]) &&
     legacy_status_runs.any? { |run| run["name"] == required_check[:context] })
end

The test "reports a failed legacy status before same-label missing modern checks" hits this setup with a failing status, so the failed block (which now runs first) masks the bug in that test. A green-status version of the same scenario would expose the false positive.


2. No test for stable release (is_prerelease: false) with legacy status contexts

All three new legacy-context test cases use is_prerelease: true. For stable releases, evaluated = check_runs + legacy_status_runs (all runs, not just required ones), but the gate behaviour (fail/missing/in_progress) follows the same logic. Worth adding one stable-release case — at minimum the "blocks when legacy status has failed" path — to guard that code path.


3. Ordering change: failed now fires before missing_required

The commit moves the failure check before the missing-required check. This is the right call for the legacy-status overlap scenario described in the comment, and the test "reports a failed legacy status before same-label missing modern checks" validates the motivating case. For stable releases with no branch protection (required_names = nil), the missing-required block is skipped entirely, so ordering does not matter there.


4. Minor: check_label jq function operates on required-check objects, not runs

In the shell script, def check_label: is called via map(check_label) on $required_names.checks entries ({context, app_id} objects). This is correct. However, since check_matches takes $required as an explicit parameter and operates on a run, the naming is asymmetric. required_check_label would make the implicit input type clear. Non-blocking.


Validation gaps

bash -n .claude/hooks/main-ci-status.sh checks syntax only. A bats/shunit2 harness for the hook jq logic would catch future drift between the shell and Ruby implementations (the adversarial gate FOLLOWUP also notes this).


Summary

Blocking concern: Issue 1 — false-positive block on green commits when the same context appears in both contexts and checks. The fix is a small addition to missing_required_check_labels.

Low-risk but worth adding: Issue 2 — one stable-release test for the legacy status path.

Everything else is non-blocking.

Comment thread rakelib/release.rake
Comment thread rakelib/release.rake
Comment thread rakelib/release.rake
@claude

claude Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overall: Good architectural approach — preserving app_id through the whole pipeline is the right fix for issue #3413, and the Ruby helpers are well-decomposed. Two correctness bugs need fixing before merge; everything else is minor.


What the PR does

  • Replaces the flat required_names string array with a structured { contexts: [...], checks: [{context, app_id}] } payload, so GitHub App-pinned required checks can be matched precisely.
  • Adds a fetch_main_commit_statuses + normalize_status_as_check_run path for legacy Status API contexts (e.g. Travis) that branch protection requires but that never emit a Checks API run.
  • Mirrors the dedup/matching logic in .claude/hooks/main-ci-status.sh for display consistency.
  • Moves failure reporting before missing-check reporting, so a failed legacy status surfaces immediately rather than being masked by a "missing required check" message.

Bugs

1. normalize_status_as_check_run leaks API URL into output (inline comment on line 675)

The || status["url"] fallback resolves to the REST API resource URL (https://api.github.com/repos/.../statuses/SHA), which is not a browser link. format_ci_status_run_line only skips empty strings, so the API URL would be printed. This contradicts the test "does not print the commit-status API URL as a browser link", which would fail.

Fix: use only status["target_url"]; the .to_s + empty-guard in format_ci_status_run_line already handles nil safely.

2. "All required missing" check uses dedup'd label count (inline comment on line 931)

required_check_labels calls .uniq, collapsing duplicate label strings. When the same name (e.g. "Travis") appears in both contexts and checks, required_labels.length becomes 1. If the legacy status passed but the modern check run is absent, missing_names.length is also 1 — so 1 == 1 incorrectly triggers the "no required CI check runs found" error instead of "some required checks are missing." This breaks the test "reports same-label legacy success plus missing modern check as partially missing".

Fix: compare against the raw sum required_names[:contexts].length + required_names[:checks].length, not the dedup'd label count.


Minor observations

missing_required_check_labels is defined but unused.
validate_main_ci_status! calls missing_required_check_labels (line 926), but missing_required_checks (the underlying method that returns {count:, labels:}) is also defined. It would be cleaner to call missing_required_checks directly in validate_main_ci_status! and use the :count field for the "all missing" comparison — that also eliminates the dedup-count bug above.

normalize_status_as_check_run"error" is not a valid check-run conclusion.
GitHub commit status states are success | failure | error | pending. Mapping state directly as conclusion means "error" becomes the conclusion, but GitHub Checks API conclusions are failure | success | neutral | cancelled | skipped | timed_out | action_required | stale. In practice "error" is not in CI_PASSING_CONCLUSIONS so it will correctly block release — but it may produce a confusing label in output. Consider mapping "error""failure" for clarity.

Hook / Ruby divergence risk.
The PR adds a "Keep the two in sync" comment, which is the right call. Worth considering a simple smoke-test (e.g. a bats/shellspec test) for the hook's jq expressions in a follow-up, per the FOLLOWUP note already in the PR description.

fetch_main_commit_statuses repeats fetch_main_ci_checks error-handling boilerplate.
The two functions share nearly identical Errno::ENOENT / status.success? / JSON::ParserError handling. Not a blocker given the project's preference against premature abstraction, but worth noting if a third similar function is needed.


Test coverage

New specs cover app-pinned requirements, legacy-only contexts, mixed same-label legacy/modern cases, and the "no API URL in output" case. Coverage is thorough — the two bugs above are the only gaps relative to the scenarios described.

Comment thread rakelib/release.rake Outdated
Comment thread rakelib/release.rake
Comment thread rakelib/release.rake
Comment thread rakelib/release.rake
@claude

claude Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Code Review

Overall: This is a well-constructed PR that correctly models the GitHub branch-protection model (app-pinned checks, legacy commit-status contexts, mirrored deduplication). The decision log in the description is excellent, and the 114-spec test suite covers the important behavioral contracts. A few observations below.


Behavioral change: failure check moved before missing-required check

The failed block now runs before the missing-required block (previously the order was missing → failed → in_progress). When both conditions exist simultaneously (a run fails and a separate required check is missing) the operator now sees the failure first. This is better UX, but it is a change in observable output ordering. Scripts or tests outside this repo that parse the error messages and rely on ordering should be audited. The inline comment at the move site captures the reasoning well.


legacy_status_fetch_unknown — silent return path

At the end of validate_main_ci_status!:

return if legacy_status_fetch_unknown

In dry-run/override mode, if the legacy-status fetch fails, fetch_main_commit_statuses prints a warning via handle_main_ci_status_violation! (dry-run wrapper), sets legacy_status_fetch_unknown = true, and then the function silently returns here with no green output. This is correct: don't show "✓ Main CI is healthy" when you couldn't verify legacy statuses.

However, the silent return means an operator running in --dry-run mode sees a warning but no final status line — which could look like a hang or incomplete output. Consider a short puts "⚠️ Skipping final status: legacy commit status unavailable." before the return to make the terminal output unambiguous.


Potential duplicate run entries in evaluated on stable releases

legacy_status_runs are appended to evaluated without deduplication against check_runs. On a stable release with required_names[:contexts] containing "Travis":

  • check_runs may contain a "Travis" check run (not filtered since is_prerelease is false)
  • legacy_status_runs will contain a normalized "Travis" status run

If both fail, both appear in failed and the error output lists "Travis" twice. GitHub branch-protection would indeed block on either, so the blocking behavior is correct. The display duplication is a minor UX wart worth a follow-up comment or test case to document the intentional behavior.


latest_commit_statuses — string sort on created_at

context_statuses.max_by { |status| [status["created_at"].to_s, status["id"].to_i] }

ISO 8601 UTC strings (2026-06-07T20:00:01Z) sort correctly as strings, so this works. The nil.to_s"" tiebreaker means entries without created_at sort earlier (treated as oldest), which is conservative and correct. Worth a brief comment so future readers don't "fix" this to Time.parse.


Nit: format_ci_status_run_line nil-URL guard is a real bug fix

The old code unconditionally appended \n #{run['html_url']} — when html_url is nil (e.g. a legacy status with no target_url), this would print the literal string nil. The new url.strip.empty? guard is correct and worth calling out as a silent bug fix in the PR description or CHANGELOG (currently not mentioned anywhere).


Minor: normalize_required_check_entries .uniq after filter_map

def normalize_required_check_entries(checks)
  Array(checks).filter_map do |check|
    ...
    { context:, app_id: check["app_id"]&.to_i }
  end.uniq
end

.uniq on a hash array uses value equality, so {context: "Lint", app_id: nil} appearing twice collapses to one. This is correct. Note that app_id is converted with &.to_i — if GitHub returns an integer app_id, 42.to_i is a no-op; if it returns a string "42", this normalizes it. Defensive and fine.


Shell/Ruby parity comment accuracy

The comment # Mirrors the Ruby dedup in validate_main_ci_status! (rakelib/release.rake) now needs updating — the Ruby dedup is in normalize_required_checks_payload, not validate_main_ci_status!. Tiny nit.


Summary: The logic is sound and the tests are thorough. The evaluation-order change and the legacy_status_fetch_unknown silent exit are the two items worth a second look before merge. All other observations are minor or follow-up material.

Comment thread rakelib/release.rake
Comment thread rakelib/release.rake
Comment thread rakelib/release.rake
Comment thread .claude/hooks/main-ci-status.sh
Comment thread rakelib/release.rake
@claude

claude Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Code Review: Harden release gate required check matching

Overview

This PR correctly addresses #3413 by preserving app_id on modern required checks, adding legacy Commit Status API fallback for legacy contexts, and deduplicating mirrored GitHub entries. The logic is sound, the behavioral intent matches the GitHub branch-protection specification, and the test suite expansion is thorough.


Correctness

Evaluation order change (intentional and correct): Failures are now surfaced before missing-check detection. Previously, a failing required check run could be obscured by a "missing required check" message if a same-name legacy status had not been fetched yet. The new order is strictly better and the comment at the call site explains the rationale clearly.

Double-inclusion in evaluated (intentional design): For both stable and prerelease releases, evaluated = check_runs + legacy_status_runs. When a required wildcard context appears as both a check run and a legacy status, both entries land in evaluated. A pending legacy status therefore blocks even when the same-named check run has already passed. The PR description and decision log document this as intentional ("same-name legacy failures that GitHub branch protection would still reject"), but reviewers should verify this matches their actual GitHub branch-protection configuration before merging. There is no test explicitly documenting this as the intended outcome for the in-progress case (pending legacy + passing check run on a required wildcard context).

App wildcard vs. -1: Both Ruby (required_check_app_wildcard?) and the shell jq (app_wildcard) treat nil/null and -1 as wildcards. This correctly mirrors the GitHub API contract.

normalize_status_as_check_run: Correctly omits app_id (legacy statuses have no app concept), so required_check_present? can never let a legacy status satisfy an app-pinned check.


Minor Issues

matching_check_run naming in legacy_context_present? (rakelib/release.rake): The variable holds a boolean (the result of any?), not a check run object. Rename to check_run_present or collapse the local entirely — see inline comment.

Test gap — override mode with legacy_status_fetch_unknown: The test suite covers dry_run: true for the "does not print green when legacy status data is unavailable" scenario but has no corresponding test for allow_override: true, dry_run: false. The code path is the same (unless allow_override || dry_run admits both), but the contract is only verified for one of the two admitted modes.

Performance note (acknowledged): The Statuses API call is skipped when there are no legacy contexts (required_status_contexts.any?), so modern-only repos pay zero extra latency. The PR notes a follow-up opportunity to defer the call further; the conservative current behavior is appropriate for a first pass.


Shell Script

The jq restructuring in main-ci-status.sh is clean. The modern_contexts helper function is defined and used exactly once; inlining it would shorten the expression at the cost of readability — current form is fine. The \{*\} guard replacing \[*\] in the case statement is correct for the new object-shaped required_json.


Test Coverage

The new test contexts are comprehensive: app-pinned blocking, wrong-app non-blocking, legacy context fallback (success/failure/pending/unknown), dry-run with nil statuses, URL sanitization, mirroring dedup count, and stable-release legacy blocking. The helper builders (required_checks, required_check, app_id: keyword on run builders) keep fixtures readable. No gaps noticed beyond the override-mode variant noted above.


Summary

Approve with two minor asks:

  1. Rename matching_check_runcheck_run_present (or inline the expression).
  2. Add a test for allow_override: true, dry_run: false with legacy_status_fetch_unknown to complete coverage of the two paths admitted by unless allow_override || dry_run.

Comment thread rakelib/release.rake
Comment thread react_on_rails/spec/react_on_rails/release_rake_helpers_spec.rb
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Pro Node Renderer Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
Pro Node Renderer: simple_eval (non-RSC) 2170.49 ▼1.3% (2199.91) 4.17 ▲1.0% (4.13) 5.2 ▼1.1% (5.26) 200=65123
Pro Node Renderer: react_ssr (non-RSC) 1906.8 ▼1.3% (1932.04) 4.7 0.0% (4.7) 6.12 ▲1.3% (6.04) 200=57207

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@claude

claude Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Code review posted as inline comments - see individual file threads.

@claude

claude Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Code review: Overall assessment -- well-structured PR that addresses a real gap in required check matching. The decomposition is clean, coverage thorough (114 examples), fail-safe/fail-open behaviors correct. Five issues worth discussing follow as inline comments on the relevant lines.

Comment thread rakelib/release.rake
Comment thread rakelib/release.rake Outdated
Comment thread rakelib/release.rake
Comment thread rakelib/release.rake
Comment thread .claude/hooks/main-ci-status.sh
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Pro (shard 2/2) Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
/empty: Pro 1318.97 ▲6.3% (1241.3) 4.57 ▼23.0% (5.94) 7.98 ▼11.6% (9.03) 200=39842
/ssr_shell_error: Pro 380.89 ▲12.6% (338.17) 20.52 ▼6.4% (21.92) 32.51 ▼7.3% (35.08) 200=11509
/ssr_sync_error: Pro 367.01 ▲10.2% (332.95) 21.2 ▲2.0% (20.78) 33.69 ▼5.3% (35.58) 200=11090
/rsc_component_error: Pro 310.92 ▼6.5% (332.58) 18.51 ▼13.5% (21.4) 65.8 ▲87.5% (35.09) 200=9395
/non_existing_stream_react_component: Pro 329.2 ▼4.3% (343.87) 17.45 ▼15.5% (20.66) 28.73 ▼17.2% (34.7) 200=9949
/server_side_redux_app_cached: Pro 414.72 ▲13.5% (365.25) 19.12 ▼3.7% (19.85) 29.71 ▼6.6% (31.82) 200=12533
/loadable: Pro 288.67 ▼4.8% (303.28) 19.68 ▼16.7% (23.62) 48.1 ▲31.4% (36.61) 200=8723
/apollo_graphql: Pro 136.44 ▼2.1% (139.31) 47.62 ▼5.4% (50.34) 75.32 ▼10.0% (83.71) 200=4127
/console_logs_in_async_server: Pro 3.15 ▼1.4% (3.2) 2119.81 ▼0.1% (2121.71) 2152.03 ▼10.5% (2405.03) 200=108
/stream_error_demo: Pro 363.7 ▲12.2% (324.16) 21.22 ▼0.2% (21.25) 32.4 ▼10.2% (36.09) 200=10987
/stream_async_components: Pro 315.65 ▼4.1% (329.25) 17.97 ▼18.9% (22.16) 22.01 ▼40.8% (37.18) 200=9602
/rsc_posts_page_over_http: Pro 354.8 ▲9.3% (324.51) 9.49 🟢 57.9% (22.54) 28.37 ▼23.4% (37.05) 200=10795
/rsc_echo_props: Pro 241.45 ▲7.7% (224.19) 32.99 ▼0.8% (33.25) 47.01 ▼8.6% (51.41) 200=7296
/async_on_server_sync_on_client_client_render: Pro 402.6 ▲14.7% (350.88) 19.02 ▼8.6% (20.8) 31.1 ▼8.5% (34.0) 200=12162
/server_router_client_render: Pro 399.68 ▲16.1% (344.37) 19.0 ▼7.7% (20.58) 28.42 ▼13.0% (32.68) 200=12075
/unwrapped_rsc_route_stream_render: Pro 305.61 ▼8.7% (334.65) 18.61 ▼10.5% (20.78) 41.57 ▲15.5% (36.0) 200=9235
/async_render_function_returns_component: Pro 352.38 ▲5.3% (334.53) 24.37 ▲18.1% (20.64) 34.13 ▼0.9% (34.44) 200=10647
/native_metadata: Pro 359.36 ▲6.9% (336.04) 21.77 ▼1.4% (22.08) 31.32 ▼8.6% (34.26) 200=10856
/hybrid_metadata_streaming: Pro 289.4 ▼13.2% (333.34) 19.92 ▼7.5% (21.53) 34.32 ▼5.7% (36.4) 200=8746
/cache_demo: Pro 340.06 ▲4.8% (324.62) 25.39 ▲13.3% (22.41) 35.92 ▼2.8% (36.95) 200=10276
/client_side_hello_world_shared_store: Pro 320.75 ▼4.5% (335.94) 17.93 ▼15.2% (21.14) 27.65 ▼16.9% (33.28) 200=9693
/client_side_hello_world_shared_store_defer: Pro 381.33 ▲14.4% (333.35) 19.79 ▼8.0% (21.51) 32.64 ▼6.6% (34.93) 200=11523
/server_side_hello_world_shared_store_controller: Pro 314.22 ▲12.1% (280.41) 25.09 ▼4.9% (26.38) 36.87 ▼11.1% (41.46) 200=9500
/server_side_hello_world: Pro 307.28 ▼6.9% (330.01) 18.37 ▼17.5% (22.27) 21.92 ▼36.4% (34.48) 200=9346
/client_side_log_throw: Pro 417.29 ▲12.0% (372.49) 18.23 ▼7.0% (19.6) 29.25 ▼6.2% (31.19) 200=12608
/server_side_log_throw_plain_js: Pro 429.11 ▲14.8% (373.73) 12.99 ▼33.1% (19.41) 24.76 ▼22.5% (31.93) 200=12970
/server_side_log_throw_raise_invoker: Pro 471.1 ▲15.6% (407.67) 13.35 ▼21.8% (17.07) 22.14 ▼23.0% (28.75) 200=14236
/server_side_redux_app: Pro 373.27 ▲13.2% (329.69) 21.03 ▼5.5% (22.26) 30.01 ▼16.8% (36.05) 200=11279
/server_side_redux_app_cached: Pro 345.64 ▼5.4% (365.25) 16.68 ▼16.0% (19.85) 61.82 ▲94.3% (31.82) 200=10443
/render_js: Pro 428.45 ▲15.7% (370.38) 14.51 ▼26.8% (19.81) 24.83 ▼22.3% (31.96) 200=12948
/pure_component: Pro 395.93 ▲18.3% (334.74) 19.87 ▼8.1% (21.63) 31.23 ▼12.2% (35.56) 200=11960
/turbolinks_cache_disabled: Pro 346.89 ▼4.0% (361.52) 16.35 ▼17.7% (19.88) 19.14 ▼40.2% (32.03) 200=10550
/xhr_refresh: Pro 261.67 ▼7.8% (283.88) 21.74 ▼15.6% (25.75) 37.86 ▼3.8% (39.35) 200=7906
/broken_app: Pro 390.14 ▲15.8% (336.92) 20.19 ▼6.6% (21.62) 29.78 ▼12.8% (34.13) 200=11793
/server_render_with_timeout: Pro 389.6 ▲17.0% (332.99) 20.4 ▼5.1% (21.5) 29.73 ▼12.7% (34.06) 200=11775

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Core Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
/: Core 3.44 ▲12.0% (3.07) 2379.48 ▼3.4% (2463.32) 2943.54 ▼8.9% (3232.59) 200=112
/client_side_hello_world: Core 446.23 ▼32.9% (664.96) 13.0 ▲43.0% (9.09) 25.61 ▲38.3% (18.52) 200=13484
/client_side_rescript_hello_world: Core 649.79 ▼3.0% (670.02) 9.97 ▲6.4% (9.37) 21.94 ▲21.2% (18.11) 200=19634
/client_side_hello_world_shared_store: Core 595.39 ▼7.5% (643.44) 13.37 ▲39.4% (9.59) 24.9 ▲24.0% (20.09) 200=17989
/client_side_hello_world_shared_store_controller: Core 605.36 ▼4.6% (634.68) 13.25 ▲37.2% (9.66) 24.39 ▲16.1% (21.01) 200=18287
/client_side_hello_world_shared_store_defer: Core 564.02 ▼13.2% (650.1) 13.9 ▲42.3% (9.77) 26.32 ▲28.5% (20.48) 200=17037
/server_side_hello_world_shared_store: Core 14.85 ▲11.9% (13.27) 577.63 ▼1.8% (588.38) 700.9 ▼9.0% (770.26) 200=455
/server_side_hello_world_shared_store_controller: Core 14.82 ▲10.6% (13.39) 553.03 ▼3.6% (573.82) 720.8 ▼5.8% (765.04) 200=458
/server_side_hello_world_shared_store_defer: Core 14.49 ▲8.3% (13.37) 578.86 ▼1.3% (586.45) 739.8 ▼5.9% (786.22) 200=445
/server_side_hello_world: Core 29.75 ▲12.1% (26.54) 279.18 ▼1.8% (284.19) 326.03 ▼9.0% (358.32) 200=906
/server_side_hello_world_hooks: Core 30.28 ▲12.0% (27.04) 212.52 ▼25.9% (286.86) 313.09 ▼12.5% (357.66) 200=923
/server_side_hello_world_props: Core 22.48 ▼17.2% (27.16) 266.23 ▼9.9% (295.47) 352.89 ▼1.3% (357.55) 200=685
/client_side_log_throw: Core 642.44 ▼3.0% (662.6) 12.88 ▲34.6% (9.57) 23.14 ▲24.1% (18.64) 200=19408
/server_side_log_throw: Core 24.89 ▼5.3% (26.28) 236.8 ▼21.0% (299.8) 262.86 ▼28.2% (366.04) 200=762
/server_side_log_throw_plain_js: Core 29.61 ▲10.1% (26.91) 285.57 ▲0.7% (283.7) 336.85 ▼7.0% (362.11) 200=897
/server_side_log_throw_raise: Core 25.15 ▼6.2% (26.81) 234.78 ▼19.0% (290.01) 312.14 ▼12.1% (355.23) 3xx=766
/server_side_log_throw_raise_invoker: Core 723.52 ▼7.1% (778.48) 8.58 ▲1.5% (8.45) 18.16 ▲16.0% (15.65) 200=21861
/server_side_hello_world_es5: Core 29.87 ▲15.0% (25.97) 285.44 ▼0.7% (287.4) 328.82 ▼8.6% (359.67) 200=910
/server_side_redux_app: Core 28.79 ▲9.5% (26.29) 293.23 ▼3.2% (303.02) 340.46 ▼8.6% (372.48) 200=877
/server_side_hello_world_with_options: Core 29.67 ▲10.4% (26.88) 304.89 ▲4.9% (290.59) 335.55 ▼7.0% (360.98) 200=900
/server_side_redux_app_cached: Core 563.69 ▼13.7% (652.89) 9.05 ▼11.8% (10.27) 19.56 ▲8.3% (18.07) 200=17154
/client_side_manual_render: Core 560.06 ▼19.5% (695.62) 10.34 ▲13.7% (9.1) 27.95 ▲50.5% (18.58) 200=16921
/render_js: Core 31.9 ▲12.8% (28.29) 260.12 ▼5.0% (273.77) 311.78 ▼7.7% (337.89) 200=970
/react_router: Core 28.39 ▲12.4% (25.25) 291.98 ▼7.6% (315.91) 343.59 ▼9.7% (380.67) 200=865
/pure_component: Core 30.97 ▲13.7% (27.24) 277.03 ▼3.9% (288.39) 323.9 ▼9.7% (358.56) 200=940
/css_modules_images_fonts_example: Core 29.85 ▲15.2% (25.9) 282.85 ▲1.4% (278.91) 329.98 ▼8.4% (360.25) 200=908
/turbolinks_cache_disabled: Core 616.63 ▼8.2% (671.7) 10.8 ▲14.3% (9.45) 22.83 ▲25.6% (18.18) 200=18630
/rendered_html: Core 30.51 ▲13.8% (26.81) 271.2 ▼7.1% (291.97) 328.42 ▼7.9% (356.59) 200=927
/xhr_refresh: Core 15.3 ▲10.7% (13.83) 531.84 ▼3.7% (552.16) 706.97 ▼7.0% (759.79) 200=470
/react_helmet: Core 29.5 ▲12.1% (26.31) 283.09 ▼3.7% (293.91) 339.15 ▼7.1% (365.24) 200=897
/broken_app: Core 29.86 ▲12.8% (26.48) 277.71 ▼2.5% (284.75) 327.55 ▼8.7% (358.82) 200=910
/image_example: Core 30.11 ▲14.4% (26.32) 279.16 ▼5.6% (295.59) 327.58 ▼9.3% (360.98) 200=917
/turbo_frame_tag_hello_world: Core 751.9 ▲1.1% (743.74) 8.93 ▲3.4% (8.64) 18.03 ▲7.7% (16.74) 200=22718
/manual_render_test: Core 624.21 ▼6.4% (667.08) 9.46 ▼0.5% (9.5) 24.74 ▲33.9% (18.48) 200=18861

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Pro (shard 1/2) Benchmark Summary

Benchmark RPS p50(ms) p90(ms) Status
/: Pro 187.08 ▲5.1% (177.99) 42.53 ▼1.5% (43.17) 59.65 ▼2.3% (61.07) 200=5655
/error_scenarios_hub: Pro 383.19 ▲9.7% (349.27) 19.75 ▼2.0% (20.16) 32.38 ▲1.6% (31.87) 200=11580
/ssr_async_error: Pro 308.2 ▼7.9% (334.76) 18.35 ▼12.3% (20.92) 22.74 ▼34.3% (34.6) 200=9374
/ssr_async_prop_error: Pro 300.61 ▼6.7% (322.23) 19.19 ▼10.6% (21.47) 34.53 ▼3.3% (35.7) 200=9085
/non_existing_react_component: Pro 389.55 ▲12.6% (346.07) 20.27 ▼1.2% (20.51) 30.4 ▼9.8% (33.71) 200=11771
/non_existing_rsc_payload: Pro 387.18 ▲7.8% (359.16) 19.25 ▼4.5% (20.16) 30.86 ▼10.0% (34.29) 200=11700
/cached_react_helmet: Pro 395.39 ▲7.9% (366.57) 19.45 ▲0.7% (19.31) 29.14 ▼5.7% (30.89) 200=11949
/cached_redux_component: Pro 431.1 ▲12.7% (382.6) 20.27 ▲5.0% (19.31) 27.03 ▼12.9% (31.03) 200=12943
/lazy_apollo_graphql: Pro 156.44 ▲5.3% (148.59) 46.36 ▼3.5% (48.04) 74.94 ▼2.6% (76.94) 200=4728
/redis_receiver: Pro 88.98 ▲3.1% (86.33) 58.87 ▼14.8% (69.07) 166.38 ▲11.3% (149.43) 200=2690,3xx=4
/stream_shell_error_demo: Pro 382.5 ▲16.8% (327.57) 20.24 ▼2.0% (20.66) 30.16 ▼14.3% (35.19) 200=11555
/test_incremental_rendering: Pro 379.94 ▲13.7% (334.11) 14.44 ▼32.6% (21.41) 27.8 ▼19.2% (34.39) 200=11559
/rsc_posts_page_over_redis: Pro 106.86 ▲9.1% (97.93) 72.36 ▲5.3% (68.75) 109.79 ▼2.1% (112.18) 200=3233
/async_on_server_sync_on_client: Pro 365.34 ▲14.9% (318.06) 21.01 ▼7.2% (22.65) 34.09 ▼14.6% (39.92) 200=11040
/server_router: Pro 381.73 ▲16.1% (328.76) 20.3 ▼5.9% (21.57) 32.75 ▼6.6% (35.05) 200=11533
/unwrapped_rsc_route_client_render: Pro 433.17 ▲15.6% (374.69) 17.58 ▼7.2% (18.93) 28.22 ▼5.8% (29.95) 200=13087
/async_render_function_returns_string: Pro 396.49 ▲19.1% (332.82) 19.51 ▼7.1% (20.99) 29.69 ▼11.8% (33.64) 200=11983
/async_components_demo: Pro 231.59 ▲15.8% (199.95) 34.43 ▼5.6% (36.48) 46.73 ▼7.2% (50.34) 200=7000
/stream_native_metadata: Pro 387.28 ▲15.8% (334.34) 16.26 ▼23.1% (21.15) 27.24 ▼23.3% (35.5) 200=11704
/rsc_native_metadata: Pro 314.21 ▼2.4% (321.87) 18.14 ▼19.5% (22.52) 62.64 ▲69.6% (36.93) 200=9494
/client_side_hello_world: Pro 401.92 ▲11.7% (359.75) 19.16 ▼1.5% (19.46) 30.27 ▼3.8% (31.45) 200=12143
/client_side_hello_world_shared_store_controller: Pro 370.33 ▲9.2% (339.03) 20.23 ▼3.1% (20.87) 30.5 ▼8.4% (33.3) 200=11192
/server_side_hello_world_shared_store: Pro 247.13 ▼13.9% (286.99) 23.17 ▼11.3% (26.11) 34.34 ▼11.7% (38.89) 200=7470
/server_side_hello_world_shared_store_defer: Pro 267.34 ▼6.6% (286.12) 29.52 ▲15.5% (25.55) 44.75 ▲14.9% (38.95) 200=8078
/server_side_hello_world_hooks: Pro 319.43 ▼8.0% (347.15) 23.04 ▲8.5% (21.23) 41.05 ▲17.1% (35.05) 200=9653
/server_side_log_throw: Pro 362.16 ▲6.9% (338.92) 21.5 ▼0.4% (21.59) 34.92 ▲0.5% (34.74) 200=10944
/server_side_log_throw_raise: Pro 706.01 ▲10.3% (640.3) 11.07 ▼0.2% (11.09) 19.04 ▲1.1% (18.83) 3xx=21330
/server_side_hello_world_es5: Pro 355.07 ▲5.7% (336.02) 21.45 ▲0.2% (21.4) 33.29 ▼7.6% (36.02) 200=10731
/server_side_hello_world_with_options: Pro 372.1 ▲12.1% (331.83) 21.01 ▼3.4% (21.75) 32.24 ▼7.9% (35.02) 200=11242
/client_side_manual_render: Pro 413.69 ▲13.2% (365.39) 18.38 ▼5.5% (19.45) 29.59 ▼5.2% (31.23) 200=12500
/react_router: Pro 448.36 ▲15.4% (388.65) 15.57 ▼11.8% (17.65) 29.56 ▲2.2% (28.93) 200=13548
/css_modules_images_fonts_example: Pro 311.45 ▼8.4% (340.13) 18.39 ▼14.8% (21.58) 29.76 ▼10.5% (33.24) 200=9413
/rendered_html: Pro 383.34 ▲12.9% (339.52) 20.26 ▼2.9% (20.85) 29.44 ▼11.6% (33.31) 200=11585
/react_helmet: Pro 283.87 ▼13.8% (329.41) 19.88 ▼8.9% (21.81) 62.84 ▲77.8% (35.34) 200=8577
/image_example: Pro 372.64 ▲10.6% (336.83) 20.96 ▼6.5% (22.42) 30.09 ▼13.6% (34.83) 200=11261
/posts_page: Pro 265.72 ▼17.4% (321.64) 29.19 ▼49.9% (58.24) 41.32 ▼49.7% (82.22) 200=8030

▲/▼ non-zero change vs baseline · 0.0% exact/near-zero match · 🔴 significant regression · 🟢 significant improvement (tracked measures) · (n) = baseline

@justin808 justin808 merged commit 53e0ec9 into main Jun 7, 2026
72 checks passed
@justin808 justin808 deleted the codex/issue-3413-release-gate-app-id branch June 7, 2026 22:22
justin808 added a commit that referenced this pull request Jun 7, 2026
…un-skipped-ci-retry

* origin/main:
  [codex] Harden release gate required check matching (#3763)
  Share base-ref helpers across guard scripts (#3764)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Harden release gate against app_id collisions on required-check names

1 participant