Conversation
Publishing was two manual workflow_dispatch runs an operator had to order by hand — build signed images at a SHA, then publish the CLI with images_ref set to that same SHA — and nothing recorded the result. The repository has no tags and no releases, so the pair a version actually denotes (an npm version and the six GHCR digests baked into cli/manifest.json) existed only inside a published tarball. release.yml is now the single entry point: Actions -> Release -> Run workflow, from main. It refuses a tag it has already published, calls the two existing workflows as reusable workflows in order, then tags v<version> and creates the GitHub release with the resolved digests attached as an asset, so the digest set stays recoverable from the tag. Calling rather than merging the two workflows keeps each dispatchable on its own and, because a reusable workflow's OIDC identity is its own ref, leaves the pinned cosign certificate identity verifying unchanged. Driving this from a tag push instead would have broken that identity, and would have left a dangling tag whenever a build failed. Publishing skips a version npm already has, so a run that died after npm and before the tag resumes instead of failing on a 403. The version is cli/package.json's, which CI now requires a pull request to bump whenever it touches what the package ships — without that the tag names nothing in particular.
Four fixes from an independent review of the release workflow. The tag is now created through the git refs API before gh release create, which then runs with --verify-tag instead of --target. --target only names a commit when gh creates the tag itself: if another actor raced the same tag in during the several minutes between preflight and tagging, gh would silently adopt it and the release would point away from the commit whose images and package were just published. Creating the ref is atomic and fails on a duplicate. A non-main dispatch failed by skipping every job, and a workflow whose jobs all skip reports success — an operator could read green and believe a release happened. Preflight now runs everywhere and exits non-zero off main. Skipping the publish because npm already has the version assumed that version came from this release. A direct publish-cli dispatch, or a re-dispatch at a different commit, could leave npm serving a tarball pinning digests the new tag claims to name. The skip now packs the published version, compares its pinned manifest to the digests just resolved, and only holds when they match. The version guard missed cli/LICENSE, cli/tsconfig*.json and cli/package-lock.json, all of which change the tarball or the emitted dist, and accepted a downgrade or a non-semver string as a bump. It now covers those paths and requires a strictly greater semver. Left as known limits: publish-cli still re-resolves the mutable SHA tag rather than carrying the exact digest release-package signed, gh release create is not atomic across its own tag/asset/publish calls, and Actions keeps only one pending run per concurrency group.
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.
Why
Publishing is two manual
workflow_dispatchruns an operator has to order by hand — build signed images at a SHA, then runpublish-cliwithimages_refset to that same SHA — and nothing records the result. The repo has no tags and no releases, so the pair a version actually denotes (an npm version plus the six GHCR digests baked intocli/manifest.json) exists only inside a published tarball, and the sidebar reads "No releases published".What
.github/workflows/release.ymlbecomes the single entry point: Actions → Release → Run workflow, frommain.main, resolvesv<cli/package.json version>, and refuses if that tag already exists.release-package.ymlas a reusable workflow.publish-cli.yml, which pins the just-built digests and publishes.gh release create --verify-tag --generate-notes, attaching the resolved digests asimages.jsonso the digest set stays recoverable from the tag.Both existing workflows stay independently dispatchable; they only gain a
workflow_calltrigger.Design notes
…/release-package.yml@refs/heads/main) verifying unchanged, because a reusable workflow's OIDC identity comes fromjob_workflow_ref— its own ref, not the caller's. A test asserts the release never triggers from a tag ref so this cannot silently regress.--targetonly names a commit whenghcreates the tag itself. Creating the ref first means a tag another actor raced in during the several minutes after preflight fails the run instead of being silently adopted.main. A job-levelifwould have skipped every job, and an all-skipped workflow reports success — green, with nothing released.npm publishbut before tagging resumes, while a re-dispatch at a different commit can't tag a version npm serves differently.contents: writeis confined to the tagging job — asserted by test.Behavior change worth a second look
The new
cli-versionCI job fails a PR that changes what the package ships (cli/bin,cli/src,cli/templates,cli/manifest.json,cli/package.json,cli/package-lock.json,cli/README.md,cli/LICENSE,cli/tsconfig*.json) without a strictly-greater semver bump incli/package.json. Without it the tag names nothing in particular.This is a real change in practice:
cli/package.jsonhas moved 3 times ever, whilecli/srcchanges in nearly every PR. Concurrent PRs will now conflict on the version line — usually self-resolving, since both edit it. The alternative is dropping the job and bumping once in a release PR; the preflight tag gate prevents a double release either way.The guard diffs against the merge base, not the base branch head, so unrelated
maincommits don't trip it.Known limits (deliberate, not oversights)
publish-clire-resolves the mutable:<SHA>tag rather than carrying forward the exact digestrelease-packagesigned. Aggregating digests out of a matrix job needs a collector job; worth doing, but a separate change.gh release createis not atomic across its own tag/asset/publish API calls. A failure mid-way leaves a tag that preflight will then refuse, so recovery is manual.NPM_TOKENto npm trusted publishing, the publisher must be configured againstrelease.yml(the caller), notpublish-cli.yml, because npm validatesGITHUB_WORKFLOW_REF.Verification
runblock passesbash -n.test/release-workflows.test.ts— 13/13, covering job ordering, both preflight gates, atomic tagging, the resume comparison,contents: writeconfinement, and the cosign-identity invariant.test/removed-features.test.tsgreen (it assertsrelease-package.ymlsurvives).npm run typecheck,eslint,oxlint,prettier --checkclean.9723930(cli/src, no bump), passesd98a914(0.1.0 → 0.1.1), ignores2c660da(non-CLI). Bump logic checked across upgrade / no-op / downgrade /0.9.0→0.10.0/ non-semver / prerelease.@yc-software/qm@0.1.1, extracted itsmanifest.json, and confirmed the comparison correctly refuses on a digest mismatch.An independent adversarial review (Codex) ran against this diff; the four findings it raised that held up are fixed in 01a9da8, and the four that were design tradeoffs are the known limits above.
Version bumped to 0.1.2 because this PR edits
cli/README.md, which ships in the package — so the guard is dogfooded here.No front-end surface changes, so no screenshots.