ci(publish): skip publish paths when no changeset pending - #369
Merged
Conversation
When the publish.yml entrypoint is triggered by a push to main that does not contain a pending changeset (e.g. infra-only changes, doc edits, or back-merges), the workflow currently proceeds to the reusable workflow, which then fails the anti-republish guard when the local version is already on npm. This produces a red CI run for what is essentially a no-op. Add a check-release job at the top of publish.yml that detects pending changesets and exposes a 'has_changeset' output. The three downstream jobs (release, hotfix, canary) are gated on this output: - release: runs only when has_changeset=true AND push to main (not a tag). - hotfix: runs only when push tag v*. Always publishes regardless of has_changeset (a tag is an explicit signal). - canary: runs only when has_changeset=true AND pull_request. When the workflow is triggered with no pending changesets, only the check-release job runs and exits 0 with a 'nothing to publish' log. The downstream jobs are skipped and the overall workflow run reports green. This keeps the CI history clean without losing the audit trail. The anti-republish guard in _publish-release.yml is preserved as defense in depth — it should never fire under normal operation, but it catches any edge case where the changeset detection lies (e.g. a malformed changeset that is still consumed by the snapshot). Tag push handling: - check-release always emits has_changeset=true for tag pushes (tags are explicit publish signals, not derived from changesets). - hotfix job's 'if' uses ref matching, not the has_changeset output, since tag push semantics are independent. Concurrency: - The check-release job runs first; release/hotfix/canary run after it (needs: check-release). Sequential, but the cost is a few seconds — negligible compared to npm publish latency. All third-party actions remain SHA-pinned.
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When
publish.ymlis triggered by a push tomainthat does not contain a pending changeset (infra-only changes, doc edits, back-merges), the workflow currently proceeds to the_publish-releasereusable workflow, which then fails the anti-republish guard when the local version is already on npm. This produces a red CI run for what is essentially a no-op.This PR adds a
check-releasejob at the top ofpublish.ymlthat detects pending changesets and exposes ahas_changesetoutput. The three downstream jobs (release,hotfix,canary) are gated on this output. When the workflow is triggered with no pending changesets, only thecheck-releasejob runs and exits 0 with a "nothing to publish" log line — the overall workflow run reports green.Why
Observed after #368 merged: the merge of
staging→main(which contained no changeset files) triggeredpublish.yml, which routed to_publish-release, which failed the anti-republish guard because@deessejs/fp@1.0.1was already on npm. The CI history showed a red run on the activation merge commit, which is misleading.The senior fix is to make the publish entrypoint decide first whether there is anything to publish, and only then dispatch. This mirrors the pattern already in place in
_publish-canary.yml(the "Detect pending changesets" step), but lifted to the entrypoint level so all three publish paths share the same decision.Changes
.github/workflows/publish.yml:check-releaseruns first:refs/tags/v1.2.3): always emitshas_changeset=true. A tag is an explicit publish signal, not derived from changesets..changeset/*.mdfiles (excludingREADME.mdandconfig.json). Emitshas_changeset=trueif any exist,falseotherwise.releasejob: gated onneeds.check-release.outputs.has_changeset == 'true' && push to main && not a tag.hotfixjob: gated onpush tag v*(independent ofhas_changeset, since tag push is the trigger).canaryjob: gated onneeds.check-release.outputs.has_changeset == 'true' && pull_request.Behavior matrix
has_changesetpushto main with changesetsreleasejob runs, publishes via Trusted Publishingpushto main without changesetscheck-releaseruns, exits 0, run is greenpushtagvX.Y.Zhotfixjob runs, publishes via Trusted Publishingpull_requestto staging with changesetcanaryjob runs, publishes snapshot tocanarydist-tagpull_requestto staging without changesetcheck-releaseruns, exits 0, run is greenDefense in depth preserved
The anti-republish guard in
_publish-release.ymlis kept. It should never fire under normal operation (the changeset detection is what gates the dispatch), but it catches any edge case where:changeset version.Removing the guard would lose the last line of defense. Keeping it costs nothing.
Test plan
pnpm turbo type-checkpasses.pnpm turbo lintpasses.mainwith no changeset files results in a green run with "nothing to publish" log.mainwith changeset files routes to_publish-releaseand publishes.main(vX.Y.Z) routes to_publish-hotfixand publishes.stagingwith a changeset routes to_publish-canaryand publishes the snapshot.Risk
Very low. No published version changes. The fix changes only the dispatch logic; the reusable workflows are unchanged. A push to
mainthat previously failed the anti-republish guard now produces a green run instead — strictly better.Rollback
Revert the merge commit. The previous behavior (red run on no-changeset pushes) returns.
🤖 Generated with Claude Code