Skip to content

fix(release): dispatch the release PR's CI on the branch pr reports, and fail when it cannot - #41

Merged
kiro-systemf[bot] merged 1 commit into
mainfrom
prm/dispatch-release-ci
Oct 8, 2026
Merged

kiro-systemf[bot] merged 1 commit into
mainfrom
prm/dispatch-release-ci

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

stryker-js-effect Release run 37731706181 created release PR #236 at 05:29:09.7072Z. The next step, "Run the caller's CI on the release PR", ran at 05:29:09.719Z. It printed nothing, exited 0 and dispatched nothing, so #236 has no CI.

Cause

.github/workflows/release.yml:84-85 (on 0449f15b):

branch="$(gh pr list --label release --state open --json headRefName --jq '.[0].headRefName // empty')"
if [ -n "$branch" ]; then gh workflow run "$CI_WORKFLOW" --ref "$branch"; fi
  • gh pr list with a --label filter goes through the search API, not the pulls list. In cli/cli pkg/cmd/pr/list/http.go, shouldUseSearch returns true when len(filters.Labels) > 0, and the call then goes to searchPullRequests.
  • The search index is eventually consistent. 12 ms after the PR was created, the query returned an empty list. Run now, the same query returns 236 changeset-release/main, so the PR was missing from the index at that moment, not from the repo.
  • An empty branch then skipped the dispatch silently, and the step exited 0. That is a false green.

Fix

  • github-release-management pr --output <file> appends outcome=created|updated|closed|vacant, number=<n> and branch=<ref>, the same way plan --output does. branch is the branch that pr pushed or closed.
  • release.yml gives the pr step id: pr and passes it --output "$GITHUB_OUTPUT". The dispatch step reads steps.pr.outputs.outcome/branch:
    • created or updated runs gh workflow run "$CI_WORKFLOW" --ref "$PR_BRANCH". An empty branch aborts the step through ${PR_BRANCH:?}, and a failed gh call fails the job because Actions runs bash with -e.
    • closed or vacant prints that there is nothing to dispatch.
    • Any other value, including a missing output, fails with ::error::.
  • Nothing searches anymore, so the race can't happen.

I chose the step output over two alternatives: polling the search until it shows the PR, and making a pulls-list call with head=. The tool already knows the branch it pushed, so a second lookup would only be a second source of truth.

Test (e2e lane, real tools, real git, GitHub emulator)

  • e2e/harness.ts gains world.job(name). It runs a job's run: steps from release.yml as written, in order:
    • each step runs under bash --noprofile --norc -eo pipefail, like Actions does;
    • each step gets its own GITHUB_OUTPUT file;
    • ${{ steps.<id>.outputs.<k> }}, ${{ inputs.ci-workflow }} and ${{ github.token }} are resolved, and any other expression throws;
    • nix develop --command <app> maps to the built binaries.
  • gh is a stub on the step's PATH. It refuses without GH_TOKEN (with gh's own message), records gh workflow run … to a log, and rejects every other subcommand. The emulator has no workflow store, so a real dispatch would return 404.
  • The two release-PR phases now run the whole version job instead of calling the tools one by one:
    • opens: every step exits 0, pr prints created release PR #1, and the dispatch log is exactly workflow run ci.yml --ref changeset-release/main;
    • closes: every step exits 0, pr prints closed release PR #1 …, the dispatch step prints no open release PR (closed); nothing to dispatch, and the log is unchanged.

Failing before: I copied the new e2e files onto origin/main 0449f15b, keeping main's release.yml and tools. Tests 1 failed, in phase "version job opens the release PR and dispatches CI on its branch": expected [ { …(4) } ] to deeply equal []. The failing step is the dispatch step, exit 2: gh stub: only 'gh workflow run' is modelled, got: gh pr list --label release --state open --json headRefName --jq .[0].headRefName // empty. Main can't dispatch without a search lookup, and that lookup is what raced.

On the branch: Tests 1 passed (1), with all three version-job steps exiting 0 in both phases (commands.jsonl above).

Why the PR's own pull_request runs are action_required

GitHub docs ("Triggering a workflow", https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow): events triggered by GITHUB_TOKEN create no workflow run. One exception: when a workflow using GITHUB_TOKEN creates or updates a pull request, the opened/synchronize/reopened pull_request runs are created in an "approval-required" state, and a user with write access has to "Approve workflows to run". The changelog entry is https://github.blog/changelog/2026-06-11-bot-created-pull-requests-can-run-workflows-if-approved/. So the CI 37732606663, Nix 37732606712, Commitlint 37732606625 and Changeset Check 37732607247 runs on #236 are GitHub's gate working as documented, and they will never run without approval.

workflow_dispatch is not covered by that gate ("workflow_dispatch and repository_dispatch events always create workflow runs"). The dispatch is how the reusable workflow gets the release PR its checks, and nothing else in it needs to change for this. Dispatched runs don't attach to the PR's checks as pull_request events, though: they report on the head SHA. So branch protection has to require check names that a workflow_dispatch run of the caller's CI produces. That is the caller's configuration, and I changed nothing there.

Notes

  • No changeset: changeset-management check origin/main reports "no publishable package changed". apps/github-release-management is private, and the rest is the workflow, the private e2e package and the README.
  • README: the dispatch paragraph now states the approval gate and the pr --output contract.
  • Gates on ee2a595: pnpm typecheck 0, e2e and app lint 0, pnpm check:ci 0 (72/72, then 17/17), dprint check 0, e2e lane Tests 1 passed (1).
  • No file overlaps with fix(version): version a pnpm workspace whose root manifest has no version #28. I didn't run stryker.

…and fail when it cannot

The dispatch step looked up the release branch with gh pr list --label release, a search query that missed the PR pr had created 0.5s earlier, then skipped the dispatch and exited 0. pr --output now writes outcome, number and branch; release.yml dispatches on created or updated and fails on any other outcome. The e2e runs the version job's steps as release.yml writes them, with a gh stub that records dispatches
@systemfsoftware-maker systemfsoftware-maker changed the title prm/dispatch release ci fix(release): dispatch the release PR's CI on the branch pr reports, and fail when it cannot Oct 8, 2026

@kiro-systemf kiro-systemf 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.

Verified: cause (search-backed gh pr list race) at release.yml:84-85, deterministic dispatch from pr --output, failing-before e2e on 0449f15, 5/5 green on ee2a595, clean hunt grep.

@kiro-systemf
kiro-systemf Bot merged commit 2a4722c into main Oct 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant