Conversation
trigger-docs.yml fired on release `published`, which GitHub sends for pre-releases too. v1.9.0-rc.1 (run #38) and v1.9.0 published as a pre-release (run #39) both dispatched update-cli-docs.yml in temporalio/documentation; run #38 opened an unwanted docs PR. Switch to `released`, which fires on a direct full-release publish and on a pre-release-to-release promotion, but never for a pre-release. Add a job-level gate that also rejects drafts and side-channel tags (v1.8.3-server-1.32.0-162.0, v1.7.4-standalone-nexus-operations, v1.6.3-serverless, and similar) built from feature branches, as defense in depth behind the event-type change. workflow_dispatch stays exempt so docs can still be backfilled on demand.
github.ref_name resolves to the selected branch on a manual dispatch, not a tag: run #37 dispatched cli_release_tag=main to temporalio/documentation. Add a required release_tag input for workflow_dispatch, and source RELEASE_TAG from the release payload's tag_name on the release event or from the new input on dispatch.
secrets.GITHUB_TOKEN and the get_user step outputs were interpolated directly into run: blocks, unlike github.actor in the same file, which already goes through env:. Route them the same way to avoid shell injection from a value GitHub Actions doesn't sanitize before substitution.
prerelease/draft/tag-shape don't rule out a hotfix to an older minor line (patching v1.7.x after v1.8.0 is already GA, for example) — that's a real full release, but generating docs from it would overwrite the newer version's command reference. The release payload has no "is latest" field (confirmed against the live API: a release object exposes prerelease/draft/tag_name/etc., but nothing about latest status), so compare the tag against GET /releases/latest instead, in a step rather than the job-level `if:` since expressions can't make API calls. workflow_dispatch stays exempt, consistent with the existing gate: backfilling an older tag on purpose is a deliberate choice, not something to second-guess.
| if: | | ||
| github.repository == 'temporalio/cli' | ||
| && (github.event_name == 'workflow_dispatch' | ||
| || (github.event.release.prerelease == false |
There was a problem hiding this comment.
Tested this live in a fork (github.com/Duncanma/cli) rather than trusting the docs. Findings:
- A fresh pre-release publish fires
created+prereleased+published— neverreleased, and withtypes: [released]the workflow doesn't even start a run for it (not skipped — no run at all). - Promoting an existing pre-release to a full release (
prerelease: true → false, the flow you described) does firereleased— confirmed viagithub.event.actionlogged directly from an Actions run, not inferred from the public events feed (which is a less reliable signal here — it showspublishedfor these edits too, so it's not a good proxy). - Doing that promotion together with
--latest(your actual flow: mark pre-release, attach assets, then flip to latest) still firesreleased. It doesn't get suppressed by the make_latest change. - The job-level
if:correctly skipped every one of these on the fork (github.repository == 'temporalio/cli'is false there), so nothing could reach the realtemporalio/documentationrepo during testing.
One caveat worth recording: of 4 promotion tests, the very first one (the first release ever created on the fresh fork) produced no event at all even after several minutes — looks like one-time webhook registration lag on a brand-new repo, not a pattern, since the next 3 all fired within under a minute. Worth keeping in mind if released is ever silent for a real release — check a bit later before assuming the gate is broken.
Also added a commit for your "check for latest" idea: make_latest isn't actually in the release payload (confirmed against the live API — a release object exposes prerelease/draft/tag_name/etc., nothing about latest status), so I added a step that compares the tag against GET /releases/latest and skips if it doesn't match. This closes a gap prerelease/draft/tag-shape alone don't cover: a hotfix to an older minor line (e.g. patching v1.7.x after v1.8.0 is GA) is a real full release and would otherwise regenerate docs from an older version, overwriting the newer one's command reference.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5039847c40
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
Comparing the release tag against GET /releases/latest trusts the make_latest marker, which is metadata someone can set independently of version order. An older-line hotfix published with --latest=true would satisfy that equality check while still being exactly the release this gate exists to reject (caught in review: #1211). List all non-draft, non-prerelease tags and version-sort them instead, so the check reflects actual version order rather than a marker that can be mis-set.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 28b801d42f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The highest-version query only filtered on draft/prerelease, not tag shape. If a side-channel tag (v2.0.0-serverless) were ever published with prerelease: false, sort -V ranks it above the real v2.0.0 (confirmed empirically), so it would win the "highest" comparison and silently skip every future GA release's docs update (caught in review: #1211). Apply the same strict vX.Y.Z tag-shape filter the job-level gate already uses to the release list before sorting.
Summary
trigger-docs.ymlfires onrelease: types: [published], which GitHub sends for pre-releases too.v1.9.0-rc.1(run #38) andv1.9.0published with the pre-release flag set (run #39) both dispatchedupdate-cli-docs.ymlintemporalio/documentation; run #38 opened an unwanted docs PR (documentation#5312, now closed unmerged).Two related defects in the same file, fixed alongside the gate:
github.ref_name, which resolves to the selected branch on a manual run — run github.com/temporalio/ui-server/v2-v2.9.0: 2 vulnerabilities (highest severity is: 6.1) - autoclosed #37 dispatchedcli_release_tag=main.steps.get_user.outputs.GIT_NAMEandsecrets.GITHUB_TOKENwere interpolated directly intorun:blocks, unlikegithub.actorin the same file, which already goes throughenv:.Why not just add
prerelease == falseto the existingpublishedtrigger?That fixes the reported symptom but opens a silent gap: promoting a release from pre-release to full release emits
released, notpublished, so the GA docs update would never fire —publishedalready fired while it was a pre-release and won't fire again.types: [released]fires on a direct full-release publish and on a pre-release → release promotion, and never for a pre-release, with no double-fire risk.Changes (3 commits, reviewable independently)
published→released, plus a job-levelif:gate that also rejects drafts and side-channel tags (v1.8.3-server-1.32.0-162.0,v1.7.4-standalone-nexus-operations,v1.6.3-serverless, and similar) built from feature branches, as defense in depth.workflow_dispatchstays exempt so docs can still be backfilled on demand.github.ref_name— adds a requiredrelease_taginput forworkflow_dispatchand sourcesRELEASE_TAGfrom the release payload'stag_nameor the new input.env:— hygiene, consistent with howgithub.actoris already handled.Verification
Checked every release in the repo's history against the new gate logic via the GitHub API: every pre-release and side-channel tag in history already carries
prerelease: true, so the gate would have blocked all of them; no full release (prerelease: false) has ever carried a hyphenated tag suffix, so the tag-shape check hasn't excluded anything unexpected historically.A gated-out release shows as a skipped (grey) run rather than a failure, so release managers aren't paged by a deliberate skip.
Companion PR in
temporalio/documentation: #5326, a receiver-side guard onupdate-cli-docs.ymlthat validates the tag independent of this fix.Test plan
-rc.tag) shows theupdatejob as skipped, not failedworkflow_dispatchrun still proceeds and reachestemporalio/documentationv1.9.0from pre-release to full release and confirm exactly one docs PR appears