Repository navigation
ci: name every workflow job in Title Case and run the org naming lint - #15404
kyle-sexton wants to merge 21 commits into
Conversation
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
Every job in .github/workflows now has a Title Case display name separate from its id: the id in Title Case, with the vocabulary's display words (PR, CI, API ...), so test-node shows as Test Node and fix-ci / run as Fix CI / Run. Jobs that already had a name keep their words, Title-Cased. Matrix jobs take a name with no matrix expression, and GitHub still appends the matrix values, so test-bash (3) becomes Test Bash (3). Job ids, workflow names and file names are unchanged. The sync-managed workflows from melodic-software/standards change there. Six step names are now sentence case. A check run's name is the job's display name, so every reader that matched a job or check by name moves with it: - Readers of past runs or pull request heads (merge holds, escalation clearing, refire, red-main and its fix issues, lost-run reconcile, lane-run triage, monitor signals, fix-docs and feedback dispatch, label-unblocked, address-feedback's test lanes, the review-lane steps in pr-require-checks, babysit review evidence, merge policy and post-merge baselines, the guardrails merge hook, morning-brief and review-venue) take the new name first and the old name for a head tested before the rename. Each has a test with only the old name. - Readers of their own run take the new name. handoff-evidence.sh and report-escalation.sh also accept the old name, since they run from the default branch tip and can read a run started before it. - The guardrails merge_guard_ignored_checks default names both Merge and merge. workflow_run filters and workflow_run.name comparisons key on workflow names, which this change leaves alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
A new pr-require-checks lane, check-workflow-naming (Check Workflow Naming), runs melodic-software/ci-workflows' check-workflow-naming action, enforcing, at the SHA this workflow pins its other check actions to. The action runs naming-lint and its vocabulary from melodic-software/standards main. CI Status needs the lane. Like the other lanes it skips only on a contract-only event or a draft, and its check step runs only when .github/** changed (the workflows filter group). The lane passes once the standards vocabulary that covers this repository's names (standards#984) is on main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
The check-workflow-naming lane runs melodic-software/standards' github-actions-conventions naming lint, which owns how workflows, jobs, steps and actions are named. So: - callers.test.mjs drops "every workflow and action name is its file or directory stem in Title Case" and its displayWords and displayName helpers. - docs/ci-runner-routing.md and pr-require-checks.yml's header comment drop the `<verb>-<domain>` rule and "the job id is the check name", and point at the lint instead. - babysit_review_evidence.py's workflow_display_name docstring points at the lint instead of the deleted test. The function stays: it is runtime code that reads a workflow's name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
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
…ci/job-display-names # Conflicts: # .github/scripts/pr-verify/resolve-pr.test.mjs
The workflow's own rule makes a domain a job only when that shortens the critical path by more than a job costs, so the naming lint moves from its own check-workflow-naming job into lint-repo. Its step, id workflow_naming, runs when .github/** changed (the workflows filter group), continues on error, and feeds the hygiene aggregate as workflow-naming, which reads success when that group did not match. It runs after every step that scans the tree, since the action checks out standards under the workspace. CI Status no longer needs a separate job, and the lane lists, the timeout budget, docs/ci-runner-routing.md and the replay fixture drop it. lint-repo keeps its 13-minute timeout: its measured max is 265 s against a 7-minute work budget, and the step adds a standards checkout, a cached Node setup, an npm ci of two packages and a lint pass that took 0.4 s locally. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
maintenance-file-dead-links.yml is owned here, not synced from standards, so its two jobs take Title Case display names: Read The Dead Links and Update The Dead-Link Rollup Issue. Nothing reads them by name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
…ci/job-display-names
The merge_guard_ignored_checks description grew to 400 characters when it named both forms of the merge job; the plugin-option contract caps it at 300. Say the same in 251. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
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
…ci/job-display-names # Conflicts: # .github/scripts/maintenance-monitor-signals/collect.mjs # .github/scripts/maintenance-monitor-signals/signals.mjs # .github/scripts/work-unblock/label-unblocked.test.mjs
# Conflicts: # .github/scripts/post-merge-demotion-alert/red-main.test.mjs # .github/scripts/pr-unblock/fix-ci-context.test.mjs # .github/scripts/pr-verify/probe/replay.mjs # .github/scripts/pr-verify/resolve-pr.test.mjs # docs/ci-runner-routing.md
Codex Review Summary: ✅ CompletedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1c6f2a71e1
ℹ️ 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".
didNoWork's pre-model filter matched only the job ids, so a rate-limit stop in `Resolve Issue / Broker Check` or `Resolve Issue / Run` fell to the failed-run re-fire and ignored the reset it named. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
|
claude-security-review reviewed Chunk 1 of 4 (40 files)Security review, chunk 1 of 4: no security findingsI read every hunk of all 40 files in this chunk. The prior-findings file says What changed in this chunk Jobs are getting Title Case display names, and the code that reads job and check names now accepts both forms:
Tests and changelog fragments follow. One repository test is deleted (below). Trust controls checked (merge holds)
Other changed readers
Instruction surface
Manifests and settled threads
Prior findings Spend critical-findings: 0 Chunk 2 of 4 (40 files)Security review, chunk 2 of 4: no security findingsThis lane already reviewed an earlier head ( What changed
Consumers in this chunk
Trust controls
Not settled
Manifests and instructions: this chunk changes no Prior findings and settled threads: the earlier summary finding had no line anchor and nothing here repeats it. The one settled thread ( Spend: nothing new. Embedded text: none in the PR context or thread files. critical-findings: 0 Chunk 3 of 4 (40 files)Security review, chunk 3 of 4: one manifest flag (IMPORTANT), nothing CRITICALThis lane already reviewed an earlier head ( Finding
Trust controls and holds checked
Instruction surface
Other
critical-findings: 0 Chunk 4 of 4 (15 files)Security review, chunk 4 of 4: no security findingsScope: blocking only. This lane already reviewed an earlier head (
None of these files matches a What I checked
critical-findings: 0 |
|
claude-review reviewed Chunk 1 of 4 (40 files)claude-review, chunk 1 of 4 (blocking only)This lane has already reviewed an earlier head (prior-findings says Finding
What I checked and left alone (no blocking issue found)
Notes
Chunk 2 of 4 (40 files)Chunk 2 of 4: no blocking findings (blocking-only review)This lane already reviewed an earlier head of this PR ( What I checkedWorkflow job names (21 workflows). Each change adds a Title Case Consumers that read jobs by name. These now match both the new display name and the old id, so runs started before the rename still read correctly:
Things I followed outside this chunk:
Notes
Chunk 3 of 4 (40 files)Chunk 3 of 4: blocking only, no findingsThis lane already reviewed an earlier head (prior-findings says
I ran nothing. Every pass, fail or count the PR description gives is the author's claim and unverified. What I checked and found consistent
Manifest (
|
lint-repo now feeds the org naming lint to the aggregator as workflow-naming; check-affected-gates.test.sh fails on a gate with no row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe
| "description": "Comma-separated <app slug>:<check name> pairs block-unresolved-pr-merge never judges as pending or failing. The default, github-actions:merge, is the pr-merge lane's own merge job, which runs as the merger rather than as a gate. Empty judges every check.", | ||
| "default": "github-actions:merge" | ||
| "description": "Comma-separated <app slug>:<check name> pairs block-unresolved-pr-merge never judges as pending or failing. The default names the pr-merge lane's merge job, which runs as the merger, not a gate, by display name and by job id. Empty judges every check.", | ||
| "default": "github-actions:Merge,github-actions:merge" |
There was a problem hiding this comment.
manifest userConfig.merge_guard_ignored_checks: flag, the default now also ignores every GitHub Actions check named Merge, in any repository
merge_guard_ignored_checks is a guard key. block-unresolved-pr-merge.sh L967–971 drops any check run whose <app slug>:<name> matches an entry exactly (IN($ign[]), case-sensitive). A dropped check neither blocks nor supersedes. The default used to drop only github-actions:merge. It now also drops github-actions:Merge ("default": "github-actions:Merge,github-actions:merge", and the hook fallback at L967).
Who is affected. Every installed user who never set the option gets the wider default on update, in every repository they merge from.
What gets through. Take a PR in any repository whose GitHub Actions job is displayed as Merge, for example a merge-queue or pre-merge validation gate. If that job is failing or pending, gh pr merge <n> now passes this guard without a word. Before, it was refused with "Failed checks" or "Pending checks". The guard cannot tell workflows apart (the hook header says so), so the exemption reaches far beyond the pr-merge lane's own job, which is all this change needs it for.
What still stops it. The repository's ruleset, if it requires that check, stops the merge on the server. Nothing stops it locally.
Why IMPORTANT, not CRITICAL. No token, secret, code-execution or billing path follows. The flag is reported because a guard default changed toward more unchecked action.
Options for the owner.
- Accept the wider default (the old one already exempted every
mergejob). - Or narrow the exemption another way, for example by matching on the job's details URL or workflow path instead of the name, so
Mergegates in other repositories are still judged.
severity: IMPORTANT
claude-lane: claude-security-review
There was a problem hiding this comment.
Settled without a code change: the wider default keeps current behavior across the rename. pr-merge.yml's merge job (L930) now displays as Merge. Without github-actions:Merge in the default, the guard would treat the lane's own running merge job as a pending check and refuse every lane merge, which is the only reason github-actions:merge was exempt before. The new entry is exactly as broad as the old one: one GitHub Actions job name, in any repository. github-actions:merge stays until no run reports the old name. A failing or pending Merge check that a ruleset requires is still enforced by the server. No token, secret or account path is involved. Narrowing the match to a workflow path would be a separate guard change, not part of this rename.
| job="${check%% (*}" old="${job// /-}" | ||
| old="<!-- post-merge-fix check=${old,,}${check:${#job}} -->" |
There was a problem hiding this comment.
Blocking: the pre-rename marker is wrong for hygiene-gate checks.
The old marker is rebuilt by taking everything before (, swapping every space for - and lowercasing the result. The marker written at L268 is , and `failingChecks` (red-main.mjs) produces gate checks as ${name}: ${gate} ``.
So for check="Lint Repo: typos failed":
jobis the whole string, because there is no(.oldbecomeslint-repo:-typos-failed.- The issue filed before the rename carries
check=lint-repo: typos failed.
The awk lookup never matches it. During the changeover that has two effects:
- An open fix issue for a gate check is missed, and a duplicate is filed.
- A gate muted as
not_plannedis filed again.
Matrix checks (Test Bash (3) → test-bash (3)) come out right. Only the job part before : or ( should be converted, with the rest kept as is. For example:
job="${check%%: *}" job="${job%% (*}" old="${job// /-}"
old=""No test feeds an old gate-check marker. Add one, beside the matrix case in scripts/post-merge-demotion-alert.test.sh, for Lint Repo: typos failed against a lint-repo: typos failed issue.
claude-lane: claude-review
No related issue: owner decisions Q12, Q27, Q32, Q33 and Q35 (2026-11-11 interview): every job gets a Title Case display name, step names are sentence case, the org naming lint runs in the required check, and the repo test and prose it makes redundant go.
Draft: stacked on #15397 and held with it. Its lint step fails until melodic-software/standards#983 and #984 merge.
Summary
name:, the Title Case form of the id, through standards'displayWords. A job that already had a name keeps its words, so the owner's example staysDrift (${{ matrix.skill }}). Ids do not change.artifact-tests.yml:14names the jobUploadand it reports asUpload (ubuntu-latest). Sotest-bash (3)becomesTest Bash (3).lint-repostep (check-workflow-naming) and feeds CI Status.docs/ci-runner-routing.mdand the gate header are removed, as the lint replaces them.Fix
Commits, readable one by one:
ci: name every workflow job in Title Case: workflows, consumers, tests and changelog fragments. The consumers changed:merge-holds,clear-escalations,refire-refused,superseded)red-main,handoff.bash)review_evidence,gh,merge,merge_policy,postmerge) andreview-venue.shmerge_guard_ignored_checksdefault (Mergeandmerge)morning-brief.shci: run the org naming lint in CI Status, followed byci: run the naming lint as a lint-repo step. The lint is a step inlint-repo, not its own job, per the gate header's rule that a domain gets its own job only when that shortens the critical path.docs: drop naming rules the org lint now enforces.ci: title-case the dead-link workflow job names.fix(guardrails): keep the merge-guard option description under budget.workflow_runfilters andworkflow_run.namecomparisons key on workflow names, and no workflow name changes here. Agit grepfound 6 such uses plus theworkflows:lists, all workflow names.Verification
naming-lint --mode enforcingwith the disk-hygiene: blanket 'any filesystem root' rejection has no reasoning; blocks legitimate non-OS volumes (e.g. Windows Dev Drive) #984 vocabulary:job-name2, both in the sync-managed callersmaintenance-check-links-online-claude-code-plugins.ymlandpr-check-managed-files-hosted.yml; those change in standards;step-name0;*.test.mjsin the touched directories, 129 files, 5336 pass, 0 fail. After thelint-repomove, the structure suites passed 1005 more, 0 fail.ci-review-lanes254,ci-hold-cost-decision179,post-merge-demotion-alert334,check-lane-coverage41check-docs-only-gate88,ci-lanes-pr-hold344,workflow-self-paths138,ci-rerun-ci-status285run-hygiene-gate10,maintenance-file-dead-links27, guardrails hook 726,morning-brief597run-ruff.sh checkis clean, and so is offline zizmor onpr-require-checks.yml.check-affectedhook passes.Remaining
check-links(lychee-online caller) andpr-check-managed-files(managed-files-guard caller) in their components.gate-source.mjslatestRunskeys on the raw name, so during the changeover an old-named red run beside a new-named green rerun of the same lane can still read as failing.audit-lane-runsfinding fingerprints for job-scoped findings change once, so expect one round of churn in its digest.Related
ci-statusalias), feat(github-actions-conventions): lint job, step and action display names standards#983 and disk-hygiene: blanket 'any filesystem root' rejection has no reasoning; blocks legitimate non-OS volumes (e.g. Windows Dev Drive) #984, feat(pr-require-checks): ignore gate alias jobs in run-shape reads ci-workflows#1072, ci: rename six workflows the org naming lint rejects #15394 (six workflow renames the lint exempts for now).🤖 Generated with Claude Code
https://claude.ai/code/session_01VQb9Q2ceZLXhTR4d1vkTCe