Skip to content

ci: batch trunk-PR CI on the merge train's ci:run label - #546

Merged
kjgbot merged 4 commits into
trunkfrom
ci/merge-train-batched-ci
Oct 9, 2026
Merged

kjgbot merged 4 commits into
trunkfrom
ci/merge-train-batched-ci

Conversation

@kjgbot

@kjgbot kjgbot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Prepares relayfile for the merge-train sweeper (AgentWorkforce/cloud packages/web/lib/merge-train), porting cloud #4350/#4351. Same shape as relaycast#497 and relayauth#101. relayfile stays in dry-run.

Changes

  • ci.yml, contract.yml, relayfile-evals.yml: the trunk -> main PR now runs on opened / reopened / labeled only, never synchronize. Every job skips label events other than ci:run.
  • Concurrency: unrelated labeled events get a throwaway ignored-<run_id> group (#4351). Push runs key off the sha, so main runs no longer cancel each other.
  • New merge-train-ready.yml (Merge-train ready check): runs on feature PRs into trunk while mergeable is on. It runs go build, npm run build, npm run typecheck (TS workspaces + go vet) and the workflow contract test.
  • scripts/merge-train-ci-workflows.test.mjs: pins ci:run, the Go Test marker check, and the ready check.

Effect on manual promotion

A human trunk -> main PR gets CI when it's opened, or when ci:run is added by hand.

Verification

  • node --test scripts/merge-train-ci-workflows.test.mjs: 8/8 pass. Red-first: 6 of 8 fail against the old workflows.
  • actionlint is clean.

🤖 Generated with Claude Code


Note

Medium Risk
Changes when and how CI runs for trunk promotion and merge-train gates; misconfiguration could block merges or skip required checks, but changes are workflow-only with contract tests.

Overview
Aligns GitHub Actions with the merge-train sweeper so full CI on trunk → main promotion PRs is batched instead of re-running on every push.

Promotion workflows (ci.yml, contract.yml, relayfile-evals.yml) now listen for opened / reopened / labeled only (not synchronize), and jobs run on label events only when the label is ci:run. Concurrency is split: PR runs group per PR (cancellable), main pushes group per commit SHA (no cross-cancel), and unrelated label events use an ignored-<run_id> group so they cannot cancel an in-flight promotion CI run.

Adds merge-train-ready.yml — a lighter Merge-train ready check on PRs into trunk while the mergeable label is present (build + typecheck + contract test). Adds scripts/merge-train-ci-workflows.test.mjs to pin shared names (ci:run, Go Test marker job, ready check name) and gate/concurrency expressions against silent drift from the cloud merge-train config.

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

Port AgentWorkforce/cloud #4350/#4351 so the merge-train sweeper can manage
this repo:

- ci.yml, contract.yml and relayfile-evals.yml run the trunk -> main PR on
  opened/reopened/labeled only (never synchronize); every job skips label
  events other than `ci:run`.
- Their concurrency gives unrelated label events a throwaway group so they
  never cancel a real promotion run; push runs key off the sha.
- New `Merge-train ready check` workflow on `mergeable` feature PRs into trunk
  (go build + TS build + typecheck/go vet).
- scripts/merge-train-ci-workflows.test.mjs pins the shared names (ci:run,
  `Go Test` marker, ready check).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: cbdc4a28-9402-4cd9-a4f0-f25b7bf9a592

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment thread .github/workflows/ci.yml
jobs:
go-test:
if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main')
if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main' && (github.event.action != 'labeled' || github.event.label.name == 'ci:run'))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔴 Unrelated labels mask failed promotion checks

When any label other than ci:run is added, go-test skips and publishes a newer check on the same head. GitHub accepts skipped required checks, so an earlier failing Go Test can stop blocking promotion.

Learn more

A pull_request labeled event creates a new workflow run for the existing PR head. This gate skips Go Test when the added label is not ci:run, but the skipped job still has a check result on that head. GitHub accepts skipped jobs as successful required checks, and the newer result can mask the failed Go Test result from the actual CI run. The same gate appears on the other CI jobs and in contract and evals. The ready check already recognizes the analogous latest-check masking problem for feature PRs.

Example: Go Test fails on a trunk-to-main PR. Someone then adds a documentation label. The resulting Go Test job is skipped on the same head; the newest required Go Test result no longer reflects the failed tests.

Recommended fix: Avoid creating skipped promotion check runs for unrelated labels, or keep the check jobs running when any label event creates a run. Apply the same policy consistently to CI, Contract, and Evals, and test that adding an unrelated label after a failed run cannot turn the promotion checks green.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not changing this. The merge-train sweeper already handles it, the same way cloud does since #4351: effectiveCheckRuns (cloud packages/web/lib/merge-train/plan.ts) keeps a newer skipped run of a check name only when no non-skipped run exists. So a skipped ci:run marker or job left by an unrelated label never masks the real verdict, whether green or red. The sweeper also treats a skipped marker as no verdict. On branch protection: this repo has no required status checks, and the sweeper, not GitHub's mergeability, is the merge gate for the trunk -> main PR.

kjgbot and others added 2 commits October 9, 2026 06:49
A fork PR's ready job was skipped (same-repo condition), which the sweeper
reads as "missing" and re-kicks with `ready:check` every tick. Under
`pull_request` a fork gets no secrets and a read-only token, and the job uses
neither, so it is safe to run. Trust stays the sweeper's gate: an external PR
merges only with a maintainer's APPROVED review on its exact head.

The contract test now pins: no same-repo condition, `pull_request` as the only
trigger, no secrets, checkout without persisted credentials.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A substring check would still pass if a job appended an `|| ...` bypass that
runs promotion CI on unrelated label events. Pin the complete expression.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@cubic-dev-ai cubic-dev-ai 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.

2 issues found and verified against the latest diff

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name=".github/workflows/merge-train-ready.yml">

<violation number="1" location=".github/workflows/merge-train-ready.yml:20">
P1: `pull_request` runs the PR's workflow revision, so a feature PR can remove these steps and still produce the check the sweeper trusts. Load this gate from the base branch and safely test the PR revision, or reject workflow changes in the sweeper.</violation>
</file>

<file name=".github/workflows/ci.yml">

<violation number="1" location=".github/workflows/ci.yml:31">
P1: This skips the required `Go Test` check for unrelated labels, and GitHub treats skipped jobs as successful; a same-head label run can therefore mask failed promotion tests. Keep this check running on label events or prevent ignored-label runs from publishing the required check context.</violation>
</file>

Reply with feedback, questions, or to request a fix.

View guided diff | Re-trigger cubic

# `mergeable` is on. `edited` is deliberately not a trigger, since body edits
# would re-run it constantly.
on:
pull_request:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: pull_request runs the PR's workflow revision, so a feature PR can remove these steps and still produce the check the sweeper trusts. Load this gate from the base branch and safely test the PR revision, or reject workflow changes in the sweeper.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .github/workflows/merge-train-ready.yml, line 16:

<comment>`pull_request` runs the PR's workflow revision, so a feature PR can remove these steps and still produce the check the sweeper trusts. Load this gate from the base branch and safely test the PR revision, or reject workflow changes in the sweeper.</comment>

<file context>
@@ -0,0 +1,62 @@
+# `mergeable` is on. `edited` is deliberately not a trigger, since body edits
+# would re-run it constantly.
+on:
+  pull_request:
+    branches: [trunk]
+    types: [labeled, synchronize, reopened]
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Valid concern, and it's the same as cloud's existing ready check. Mitigation is the sweeper's trust gate: external PRs merge only with a maintainer's APPROVED review on the exact head, and that reviewer sees any workflow edit. Internal authors have write access anyway. I'm raising a follow-up for the sweeper to hold any PR that edits .github/workflows/merge-train-ready.yml unless a maintainer approved it. pull_request_target with a checkout of the PR head would be worse: it gives untrusted code a privileged token.

Comment thread .github/workflows/ci.yml
jobs:
go-test:
if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main')
if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main' && (github.event.action != 'labeled' || github.event.label.name == 'ci:run'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: This skips the required Go Test check for unrelated labels, and GitHub treats skipped jobs as successful; a same-head label run can therefore mask failed promotion tests. Keep this check running on label events or prevent ignored-label runs from publishing the required check context.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At .github/workflows/ci.yml, line 31:

<comment>This skips the required `Go Test` check for unrelated labels, and GitHub treats skipped jobs as successful; a same-head label run can therefore mask failed promotion tests. Keep this check running on label events or prevent ignored-label runs from publishing the required check context.</comment>

<file context>
@@ -19,7 +28,7 @@ env:
 jobs:
   go-test:
-    if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main')
+    if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main' && (github.event.action != 'labeled' || github.event.label.name == 'ci:run'))
     name: Go Test
     runs-on: ubuntu-latest
</file context>
Suggested change
if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main' && (github.event.action != 'labeled' || github.event.label.name == 'ci:run'))
if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not changing this. The merge-train sweeper already handles it, the same way cloud does since #4351: effectiveCheckRuns (cloud packages/web/lib/merge-train/plan.ts) keeps a newer skipped run of a check name only when no non-skipped run exists. So a skipped run left by an unrelated label never masks the real verdict, whether green or red. On branch protection: this repo has no required status checks, and the sweeper, not GitHub's mergeability, is the merge gate for the trunk -> main PR. GitHub has no label filter at the on: level, so these skip-only runs can't be avoided.

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 5 files

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.

View guided diff | Re-trigger cubic

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit fd4c6c4. Configure here.

Comment thread .github/workflows/ci.yml
jobs:
go-test:
if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main')
if: (github.event_name != 'pull_request' && github.event_name != 'pull_request_target') || (github.head_ref == 'trunk' && github.event.pull_request.head.repo.full_name == github.repository && github.base_ref == 'main' && (github.event.action != 'labeled' || github.event.label.name == 'ci:run'))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Skipped labels mask promotion checks

Medium Severity

Unrelated labeled events still start the promotion workflows and skip every job, which publishes a new skipped Go Test (and the other job names) on the same head. The sweeper uses the latest check as the CI-ran marker, so that skip can hide a real success and leave the trunk PR looking like CI never ran.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fd4c6c4. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not changing this. The merge-train sweeper already handles it, the same way cloud does since #4351: effectiveCheckRuns (cloud packages/web/lib/merge-train/plan.ts) keeps a newer skipped run of a check name only when no non-skipped run exists. So a skipped run left by an unrelated label never masks the real verdict, whether green or red. On branch protection: this repo has no required status checks, and the sweeper, not GitHub's mergeability, is the merge gate for the trunk -> main PR. GitHub has no label filter at the on: level, so these skip-only runs can't be avoided.

The ready check's per-PR concurrency group also caught label events the job
skips (no `mergeable`, e.g. the sweeper adding `external`), cancelling an
in-flight real run and leaving `cancelled` as the head's verdict. Those events
now get a throwaway group. Pinned in the contract test; ready-check timeout
raised where the build is heavy; promotion branches filter pinned where present.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@kjgbot kjgbot added the mergeable Merge train: merge this PR into trunk once its ready check is green label Oct 9, 2026
@kjgbot
kjgbot merged commit fa021b9 into trunk Oct 9, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mergeable Merge train: merge this PR into trunk once its ready check is green

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant