Skip to content

ci(release): stop publishing packages from inside the build matrix - #561

Merged
PathGao merged 1 commit into
masterfrom
fix/release-pipeline-p0
Aug 10, 2026
Merged

ci(release): stop publishing packages from inside the build matrix#561
PathGao merged 1 commit into
masterfrom
fix/release-pipeline-p0

Conversation

@PathGao

@PathGao PathGao commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

Four things went wrong around the v2.7.2 release. They are not four bugs — they share two causes.

An irreversible side effect placed before the decision it depends on. choco push and snapcraft push ran inside build.yml's platform matrix. Chocolatey's markpad-app 2.7.2 was published at 15:37 UTC on 2026-08-07 by run 31192564528 — a run that then failed on Linux and produced no release. The release users actually got came out of a different run three hours later. A Chocolatey version cannot be un-pushed.

A failure configured not to be one. Both pushes carried continue-on-error: true. The snap build has failed since v2.6.11:

Environment validation failed for part 'markpad': 'rustup' not found and part
'markpad' does not depend on a part named 'rust-deps' that would satisfy the
dependency.

That line is from the successful 2.7.2 run. The job reported success for three months and six versions while the Snap Store went on serving 2.6.11 (api.snapcraft.io, rev 15, 2026-06-03) and every release told people to sudo snap install markpad. Nobody reported it, because from the outside it looks like there is nothing to report — snapd checks for updates daily and tells those users they are current.

And the AppImage strip step failed in two of the three v2.7.2 release attempts (runs 31192564528, 31204367106), once on a stray -k and once on signing env vars, in a heredoc that can only be exercised by cutting a release.

Changes

Package managers publish after the release, not during the build — new publish-packages.yml, triggered by release: published, which is a human clicking Publish on a draft whose assets they have checked (RELEASING.md step 6). Two independent jobs, neither swallowing a failure. Also takes workflow_dispatch with a tag, so the snap and Chocolatey halves can be exercised against an existing release without cutting a new one — neither is reachable from a pull request otherwise.

The Chocolatey package now wraps the release's own Markpad_<version>_x64.exe instead of a second build. VERIFICATION.txt promised "copied directly" from a build; a rebuild from the same commit is not guaranteed to be the same bytes. It now describes what actually happens and carries the SHA256 and the exact URL, which is what a Chocolatey moderator can check.

The snap can find rustup — a rust-deps part, which is the one place other than PATH that snapcraft's rust plugin looks. The name comes from snapcraft, so it is asserted literally in the tests.

The AppImage strip is scripts/strip-appimage.sh — runnable against any downloaded AppImage, no signing key needed (signing stays in the workflow, which is what keeps the script runnable). It now verifies the repacked artifact rather than the AppDir: appimagetool packing the wrong directory would pass the weaker check. The excludelist has one home instead of two.

Worth being explicit about why the check is shaped that way: a CI smoke test cannot catch this defect. The runner's Mesa is the one the libraries were copied from, so nothing mismatches there and the AppImage starts fine — #498 had to be found by a user on Arch. "These libraries are absent" is the only property that can be checked before a release.

@tauri-apps/cli floored at ^2.11.4 — the lockfile resolved ^2 to 2.9.6, which predates the TAURI_SIGNING_* names, so the re-sign step had to spell the same secret the deprecated way while tauri build used the current one. 2.11.4 also fixes the AppImage bundler writing absolute symlinks for .desktop and .DirIcon — the same artifact this repacks. Both halves of the case are @PathGao's, in #497.

Verification

Measured, not assumed:

TAURI_SIGNING_PRIVATE_KEY + _PASSWORD on 2.11.4 writes the .sig
the same names on 2.9.6 Unable to find the private key (#497)
npm test 948 pass
npm run check 674 files, 0 errors
bash -n scripts/strip-appimage.sh, YAML parse of all three changed files clean

Seven assertions added to scripts/releaseWorkflow.test.ts, each holding a property rather than a step name — the build matrix has no choco push / snapcraft push; publishing triggers only on release: published; no continue-on-error in either file; rust-deps exists and is depended on; the strip is a script and the excludelist is not duplicated; the verification runs after the repack; the CLI floor is high enough for the env vars the workflow uses.

What cannot be verified from a pull request, and how to close it: snapcraft needs LXD and store credentials, and the strip needs a Linux runner with the signing secret. After merging, run Publish Packages by hand against v2.7.2 — that exercises the snap fix end to end (including the store push) without cutting a release. If it goes green, the next release is the first one in six where the snap is real.

Not in this PR

Whether Markpad should keep the snap at all, plus code signing on macOS and Windows and the Linux distribution surface — #562.

🤖 Generated with Claude Code

Four things went wrong in the v2.7.2 release, and they share two causes:
an irreversible side effect placed before the decision it depends on, and
a failure that was configured not to be one.

Chocolatey's markpad-app 2.7.2 was published at 15:37 UTC on 2026-08-07 by
run 31192564528, which then failed on Linux and produced no release. The
release users got came out of a different run three hours later. A
Chocolatey version cannot be un-pushed.

The snap build has failed since v2.6.11 -- snapcraft's rust plugin cannot
find rustup unless a part named `rust-deps` provides it -- under
`continue-on-error: true`, so the job reported success for three months and
six versions while the Snap Store served 2.6.11 and every release told
people to `sudo snap install markpad`.

So both pushes move to publish-packages.yml, triggered by `release:
published`: a human clicking Publish on a draft whose assets they have
checked. Neither job swallows a failure. The Chocolatey package now wraps
the release's own Markpad_<version>_x64.exe instead of a second build,
which is what VERIFICATION.txt promises; the file now says so and carries
the checksum.

The AppImage strip -- which failed in two of the three v2.7.2 attempts --
moves to scripts/strip-appimage.sh, runnable against any downloaded
AppImage without a signing key, and now verifies the repacked artifact
rather than the AppDir. A CI smoke test cannot catch this defect: the
runner's Mesa is the one the libraries came from, so nothing mismatches
there and #498 had to be found by a user on Arch. "These libraries are
absent" is the only property checkable before release.

Its re-sign step was spelling the same secret the deprecated way
(TAURI_PRIVATE_KEY) because the lockfile resolved @tauri-apps/cli to 2.9.6,
which predates the v2 names. Floored at ^2.11.4, which also fixes the
AppImage bundler writing absolute symlinks for .desktop and .DirIcon --
the same artifact this repacks. See #497.

Verified: `TAURI_SIGNING_PRIVATE_KEY` + `_PASSWORD` produce a .sig on
2.11.4 (they produce nothing on 2.9.6). Seven assertions added to
scripts/releaseWorkflow.test.ts hold the properties above; 948 tests pass.
Not verifiable from a pull request: snapcraft needs LXD and the store
credentials, and the strip needs a Linux runner --
`workflow_dispatch` on publish-packages.yml exists to exercise the snap
half against an existing tag before the next release depends on it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@PathGao
PathGao merged commit b0f2a2b into master Aug 10, 2026
4 checks passed
@PathGao
PathGao deleted the fix/release-pipeline-p0 branch August 10, 2026 11:03
PathGao added a commit that referenced this pull request Aug 10, 2026
…es that do not exist (#566)

* fix(release): make the recovery path usable, and stop promising updates that do not exist

Two things #561 left behind, both about the pipeline telling the truth.

`publish-packages.yml` has a `workflow_dispatch` escape hatch whose only
possible use is a tag that is already published -- recovering after the
snap job failed, or exercising the workflow without cutting a release. But
Chocolatey accepts a version exactly once, so on that path the Chocolatey
job always fails, on a duplicate that is expected rather than wrong. The
escape hatch reported a failure that meant nothing, which is the same
disease #561 treated: a signal nobody can act on. The job now asks the feed
first and skips a version already on it. A check that cannot answer falls
through to the push, which is the real gate -- the point is to drop a known
non-event, not to make publishing conditional on a lookup succeeding.

Separately, the README told `.deb` and `.rpm` users they "continue to update
through their distribution channels". There is no apt or dnf repository, and
tauri-plugin-updater supports neither format, so those users have no update
path at all: they install once, and nothing ever tells them a new version
exists. RELEASING.md carried the same claim. Both now say what is true.

The Chocolatey half of that sentence was correct and stays. The Snap half
is a live question in #562 and is untouched here.

Two assertions added. One holds the README, the release-body download table
and the publishing workflow to the same set of package managers, in both
directions -- the snap went dead for three months while both documents kept
recommending it, and retiring the snap would make the same drift possible
with the arrow reversed. The other holds the Chocolatey skip to gating the
push rather than merely existing. Both were checked by mutation: deleting
`snapcraft push` fails the first and not the second; dropping the `if:`
fails the second and not the first. 953 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: say what Check for Updates actually does on .deb and .rpm

The sentence added a moment ago said nothing in Markpad would tell a
package-managed install that a new version exists. It does tell them, and
then fails to install it -- #570 traces that to __TAURI_BUNDLE_TYPE not
being patched into the Linux binary, which sends deb, rpm and snap down
the AppImage install path. Both documents now point at it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
PathGao added a commit that referenced this pull request Aug 12, 2026
…ts own (#612)

Two things in build.yml, both about the release build being the one workflow
nothing else guards.

It had no `concurrency` at all, so dispatching twice gave two runs writing to
the same draft release. Not hypothetical: publish-packages.yml records
markpad-app 2.7.2 reaching Chocolatey at 15:37 UTC on 2026-08-07 from run
31192564528 -- a run that then failed on Linux and produced no release -- while
the release users actually got came out of a different run three hours later.
#561 moved the package pushes out of the build matrix, which fixed the
irreversible half of that story. This is the other half.

Keyed on the ref, not the version: the version is read from package.json inside
`create-release`, and a concurrency group is evaluated before any job starts. A
release is always dispatched from master, so the two are the same key in
practice.

`cancel-in-progress: false` deliberately. A second dispatch waits rather than
killing the first, because a half-cancelled run leaves a draft holding some
platforms' assets and not others -- which looks exactly like a build that has
not finished yet, and RELEASING.md step 5 is a human comparing that list against
an expected one.

And `generate-update-feed` ran on `ubuntu-latest` while every other runner in
the pipeline is pinned. It only runs gh and jq, so the version buys little on
its own; what it buys is that the image moving becomes something that happens
to us with a commit rather than without one.

Both halves checked by mutation, each failing alone: flipping
cancel-in-progress to true, and putting ubuntu-latest back on that job. The
assertion is on `cancel-in-progress: false` rather than on the group string,
because that is the part that carries the reasoning. 1006 tests pass.

Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local>
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