Skip to content

ci: display the gate job as CI Status with a ci-status alias - #15397

Merged
kyle-sexton merged 6 commits into
mainfrom
ci/ci-status-display-name
Nov 12, 2026
Merged

kyle-sexton merged 6 commits into
mainfrom
ci/ci-status-display-name

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: owner decision Q26 (2026-11-11 interview). The ci-status job displays as "CI Status", rolled out by GitHub Support's adopted alias plan, step A.

Draft: held until melodic-software/ci-workflows#1072 merges and this PR repins aggregate-results with alias-jobs: ci-status; without that, contract-only runs misread their shape (see Remaining).

Summary

  • The gate job ci-status gets name: CI Status. Its id is unchanged, so needs: references and the github-iac verifiers that key on the id still match.
  • A new alias job report-ci-status, named ci-status, needs CI Status, runs if: always(), and exits with CI Status's result. The required context ci-status therefore keeps reporting the same conclusion on pull_request and merge_group until the github-iac ci-gate ruleset requires CI Status (step B). It is removed in step C. No push trigger is added: this workflow has none by design, as documented in its header.
  • Every consumer that finds the gate by its check name now looks for CI Status first and still accepts ci-status, for heads tested before this change.
  • Re-runs target the CI Status job. Re-running it re-runs its dependents, so the alias re-runs too; re-running only the alias would replay its old needs result.

Fix

  • Shared match: maintenance-reconcile-lost-runs/infra-failure.mjs exports GATE_CHECK, GATE_ALIAS, AGGREGATE_JOBS and gateEntries(), which returns CI Status's entries and never the alias's beside them.
  • Scripts using the helper: reconcile.mjs, fix-ci-dispatch.mjs, red-main.mjs, resolve-pr.mjs, probe/replay.mjs.
  • Local literals instead: clear-escalations.mjs, gate-source.mjs and review-dependency.mjs carry the two names as literals, each with a comment. Importing the helper would break the sparse hold-config checkout and the staged base-script layout, which tests already pin.
  • review-dependency.mjs: reads CI Status's annotations, since only that job writes the superseded note.
  • Release: release-publish-plugins/ci-status.sh reads check_name=CI Status, then ci-status.
  • Rerun: pr-feedback-dispatch.yml re-runs the CI Status job.
  • Gate tail query: the gate's own runner-tail query excludes both names.
  • Plugins:
    • guardrails block-unresolved-pr-merge.sh reads CI Status's annotations.
    • source-control babysit babysit_merge_policy.py, babysit_postmerge.py and babysit_verify_evidence.py read CI Status first.
    • Both plugins get patch changelog fragments.
  • Docs: docs/ci-runner-routing.md and the pr-verify prompt name CI Status and the alias.

Verification

All runs below are local and every one passes:

  • Node suites over the touched and structure-reading files:
    • reconcile and infra-failure: 202
    • fix-ci-dispatch: 79
    • dependency: 65
    • clear-escalations, recheck, gate-source and fix-ci-context: 226
    • red-main: 29
    • resolve-pr and upstream-jobs: 171
    • replay: 13
    • decide: 61
    • log-api-budget: 70
    • callers, api-budget-coverage and dispatch-limits: 295
    • the gate-reading batch: 2633
    • pr-verify: 80
  • Bash suites: ci-rerun-ci-status 285, block-unresolved-pr-merge 724, ci-lanes-pr-hold 344, ci-review-lanes 251, post-merge-demotion-alert 331, check-docs-only-gate 88, check-lane-coverage 41, plus the other scripts/ci-*.test.sh suites the gate reads.
  • pytest: 1330 tests plus postmerge 362.
  • Linters: actionlint, shellcheck and scripts/run-ruff.sh check are clean. check-lane-coverage.sh --check and check-docs-only-gate.sh --check pass.

Remaining

  • Repin aggregate-results to the ci-workflows commit that merges feat(visualization): add on-demand visualization router plugin #1072, and pass alias-jobs: ci-status. Until then the pinned composite misreads contract-only runs with the alias:
    • job_shape reads them as full runs;
    • the sibling re-run skips them;
    • carried successes from pre-rename runs are dropped.
  • Send GitHub Support's hub the merge time before and after this merges (its scripts match ci-status).

Related

Follow-ups

  • Pre-existing (advisory): human guidance text still says "re-run ci-status" (merge-holds.mjs:278, :1445, pr-feedback-dispatch.yml, reconciler note). The regexes personHoldLine and RERUN_TAIL match that text, so the wording changes together with them in step C.
  • Pre-existing (advisory): the scripts/check-lane-coverage.sh comment "the repo carries ZERO opt-outs" is no longer true while the alias exists.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe

kyle-sexton and others added 2 commits November 11, 2026 11:31
The pr-require-checks gate job keeps its id, ci-status, and now shows
as CI Status. A new alias job, report-ci-status, named ci-status, needs
it, runs on every event the gate runs on, and passes only when CI
Status succeeded, so the required context ci-status keeps reporting
until the github-iac ci-gate ruleset requires CI Status.

Consumers that find the gate by its check or job name now take CI
Status first and fall back to ci-status only when no CI Status entry
exists (heads tested before this change). Re-runs, step reads, logs
and annotations use CI Status's job, never the alias's: re-running the
alias alone replays its old needs result. Exclusion lists and the
"aggregate alone" counts treat CI Status and its alias as one check.

- pr-feedback-dispatch rerun-ci-status, the lost-run reconciler,
  resolve-pr (held and draft reads) and babysit_verify_evidence pick
  CI Status's job.
- fix-ci-dispatch, gate-source, clear-escalations, red-main and
  babysit_postmerge exclude or merge both names.
- review-dependency, dependency-dispatch, release ci-status.sh and
  babysit_merge_policy read CI Status's check runs, then ci-status.
- The guardrails merge hook reads CI Status's superseded note and
  supersedes its alias in the same Actions run with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
The verifier now sees a CI Status job beside a ci-status alias that only
repeats its conclusion; the prompt says neither is test evidence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
@kyle-sexton kyle-sexton added the do-not-merge Hard merge gate: do not merge while applied. label Nov 11, 2026
kyle-sexton and others added 2 commits November 11, 2026 12:29
The org step-name rule reads "Report CI Status's conclusion" as Title
Case ("Status" capitalized). Name it "Report the conclusion of
`CI Status`" and move the test fixtures that carry it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
kyle-sexton and others added 2 commits November 11, 2026 18:57
ci-workflows#1072 adds the alias-jobs input. Naming the ci-status
mirror job there keeps contract-only runs reading as contract-only,
re-runs failed contract-only siblings, and keeps carried successes
from before the rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
@kyle-sexton kyle-sexton removed the do-not-merge Hard merge gate: do not merge while applied. label Nov 11, 2026
@kyle-sexton
kyle-sexton marked this pull request as ready for review November 12, 2026 00:01
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-12T00:03:31.913600Z d695ffb Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

Copy link
Copy Markdown
Contributor

claude-security-review reviewed d695ffbe8ef171aaa67c2b62811ba70264964fb5: read 38 of 38 changed file(s); 0 finding(s), 0 as inline comments.

Security review: PR #15397, which shows the gate job as CI Status and adds a ci-status alias job

I found no security issues. I read every hunk of all 38 changed files in .claude-lane/pr.diff.

critical-findings: 0

What I checked

The new alias job report-ci-status (.github/workflows/pr-require-checks.yml).

  • It has permissions: {}.
  • needs.ci-status.result reaches the script only through env: RESULT, so nothing is interpolated into run:.
  • The script exits successfully only when the result is success. If the gate fails, is cancelled or is skipped, the required ci-status context fails too.
  • Re-running only the alias replays the gate's latest result, which cannot be green while CI Status is red. The alias cannot pass a red gate.

Merge holds (clear-escalations.mjs, gate-source.mjs, fix-ci-dispatch.mjs, the pr-require-checks.yml gate steps, babysit_verify_evidence.py and babysit_merge_policy.py). Every change adds the gate's new check name next to the old one. They are excluded from "no other check fails" tests in the same way ci-status was before, so this keeps parity rather than widening anything:

  • Every other failing check still keeps its hold.
  • fix-ci-dispatch.mjs gets AGGREGATE_JOBS from its existing import (line 45), so its line 302 changes the same way.
  • Reads still fail closed:
    • _ci_status returns unreadable on any non-200 response.
    • The draft-run readers find no draft step when only the alias is listed, so the fail is kept.
    • heldShape returns null when more than one job matches the gate.

Gate-run selection (gateCheckRuns, gate_entries, gateEntries, ci-status.sh, babysit_merge_policy._ci_status). These now prefer CI Status and fall back to ci-status. Spoofing works the same as before: any GitHub Actions job on the head can take either name, and the PR's own workflow already controls the gate job. The GitHub Actions app filter (app_id / app.slug) is unchanged.

block-unresolved-pr-merge.sh superseded mapping. A failed ci-status alias inherits a superseded marker only when it is in the same Actions run as a GitHub Actions CI Status run that already carries a marker. An alias run with no match stays failing.

pr-feedback-dispatch.yml re-run. The job id comes from API JSON through a constant jq program, so there is no injection path.

Instruction surface. The .github/scripts/pr-verify/prompt.md change only adds that the alias is not test evidence. Nothing is softened.

Trust controls. The diff does not touch the trusted-trigger check, the lane-token scope, the kill switch, the broker or any plugin manifest.

Notes (not findings)

  • Action pin (zizmor's lane, not re-reported): aggregate-results moves from 226dad3 to ac4d8780de793b20e60a66890e767df579c80d83 (pr-require-checks.yml#L6995). It is still a full-SHA pin to an org repository, and the step now passes alias-jobs: ci-status. The PR description says it is held until ci-workflows#1072 merges. I could not check whether that commit is on ci-workflows' default branch rather than an unmerged PR branch. zizmor's impostor-commit audit covers that.
  • Spend: one extra job (at most 3 minutes, usually a few seconds) on a standard ubuntu-24.04 runner each time the gate runs. It is not a hold.

@github-actions

Copy link
Copy Markdown
Contributor

claude-review reviewed d695ffbe8ef171aaa67c2b62811ba70264964fb5: read 38 of 38 changed file(s); 0 finding(s), 0 as inline comments.

Code review: no findings

I read every hunk of all 38 changed files. I also read the unchanged code the changed lines call, because many of them sit on trusted paths: pr-require-checks.yml, pr-feedback-dispatch.yml, infra-failure.mjs, clear-escalations.mjs, gate-source.mjs, resolve-pr.mjs and the babysit scripts. I found nothing at the blocking or advisory bar.

What I checked

  • Imports resolve:
    • fix-ci-dispatch.mjs already imports AGGREGATE_JOBS from infra-failure.mjs.
    • babysit_merge_policy.py imports quote, which turns CI Status into CI%20Status as the new test expects.
    • babysit_postmerge.py imports the module as policy and babysit_verify_evidence.py as merge_policy.
    • Nothing still uses the removed CI_STATUS_CHECK or LANE_AGGREGATE_JOB constants.
  • jq precedence: in the gate_job filter in pr-feedback-dispatch.yml and the gate-run select in block-unresolved-pr-merge.sh, the if length > 0 then . else null end guard lets // fall back to ci-status. Without it, an empty array would still count as a value and block the fallback. max_by(.id) // empty still returns nothing when no job matches.
  • ci-status.sh error handling: under set -euo pipefail, a failed gh api read inside runs=$(gate_runs …) still exits non-zero, as the header promises.
  • Preferring CI Status over the alias: gateEntries, gate_entries, gateCheckRuns and the ci-status.sh fallback all read CI Status first and use ci-status only on a head with no CI Status run. So the alias's empty annotations and replayed needs result are never read as the gate's.
  • Other readers of the old name: I searched the repository for check-name reads of ci-status outside this diff. The rest key on the job id (check-docs-only-gate.sh), name the ruleset's required context, or are guidance wording the PR lists as a follow-up.
  • Alias job: report-ci-status runs with permissions: {}. Its if: always() fails closed, so it never reports a skip, which branch protection would count as a pass.

Not verified (I had no shell or network access)

Spend (not a finding): the new report-ci-status job adds one short job on a standard hosted runner to every pr-require-checks run. Its timeout is 3 minutes and it normally takes seconds. Because of always(), it also runs on cancelled runs.

No closing issue (the PR cites owner decision Q26). No plugin manifest is changed, so there are no manifest lines. The PR context contained no embedded instructions.

@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 12, 2026
Merged via the queue into main with commit 851b550 Nov 12, 2026
123 checks passed
@kyle-sexton
kyle-sexton deleted the ci/ci-status-display-name branch November 12, 2026 01:21
@github-actions github-actions Bot added the merge-path: owner-agent Post-merge accounting record (README.md, Merge paths) label Nov 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merge-path: owner-agent Post-merge accounting record (README.md, Merge paths)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant