Skip to content

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

Merged
PathGao merged 2 commits into
masterfrom
fix/pipeline-tells-the-truth
Aug 10, 2026
Merged

fix(release): make the recovery path usable, and stop promising updates that do not exist#566
PathGao merged 2 commits into
masterfrom
fix/pipeline-tells-the-truth

Conversation

@PathGao

@PathGao PathGao commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Two things #561 left behind. Both are the pipeline telling the truth about itself.

1. The recovery path always reported a failure that meant nothing

publish-packages.yml has a workflow_dispatch input, and its only possible use is a tag that is already published — recovering after the snap job failed, or exercising the workflow without cutting a release. That is exactly what #561's own description asks a maintainer to do against v2.7.2.

But Chocolatey accepts a version exactly once, and markpad-app 2.7.2 has been on the feed since 2026-08-07. So every use of that escape hatch would have reported:

workflow_dispatch v2.7.2
  ├─ snap        ← the thing you actually wanted to check
  └─ chocolatey  ← fails on a duplicate that is expected, not wrong

This is the same disease #561 treated, one level along: a signal nobody can act on. There it was a failure dressed as success; here it is a non-event dressed as a failure. Both train people to stop reading.

The job now asks the feed before packing:

GET /api/v2/Packages()?$filter=Id eq 'markpad-app' and Version eq '<version>'

Asked of the feed rather than inferred from the text of a push error, and a check that cannot answer — network, rate limit — deliberately 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. On the release: published path nothing changes: a duplicate there is a genuine problem and still fails.

Verified against the live feed: the query returns the version row for 2.7.2 and nothing for a version that does not exist.

2. .deb and .rpm users were told they get updates

README, before:

Snap, Chocolatey, .deb and .rpm users continue to update through their distribution channels.

There is no apt repository and no dnf repository, and tauri-plugin-updater cannot replace a package-managed install. So those users have no update path at all. RELEASING.md carried the same claim in the maintainer's own runbook, which is how it survived.

The first version of this change over-corrected, and the second commit fixes it. It said "nothing in Markpad will tell you one exists", which is not true and is not even the charitable reading: Markpad does tell them, and then fails to install it. Chasing that down produced #570__TAURI_BUNDLE_TYPE is not patched into the Linux binary, so bundle_type() is None and .deb, .rpm and snap all take the AppImage install path, which renames a downloaded file over /usr/bin/Markpad. Both documents now say that, and point at the issue.

The Chocolatey half of the original sentence was correct and is kept. The Snap half is a live question in #562 and is untouched here.

Tests

Two assertions, both mutation-checked.

The package managers we recommend must be the ones we publish to — across three files, in both directions: the README, the download table written into every release body, and publish-packages.yml. These drifted once already and nobody noticed for three months. The drift is about to be possible again with the arrow reversed: #562 asks whether to retire the snap, and retiring it means deleting that job — doing so without touching the two documents leaves a recommendation for a channel that no longer receives anything. The test makes that deletion fail until it is complete.

The Chocolatey skip must gate the push, not merely exist.

mutation result
snapcraft pushsnapcraft-DELETED first test fails, second passes
drop the if: on Pack and publish second test fails, first passes
unmodified 953 pass, npm run check clean

🤖 Generated with Claude Code

…es 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>
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>
@PathGao
PathGao merged commit e229953 into master Aug 10, 2026
4 checks passed
@PathGao
PathGao deleted the fix/pipeline-tells-the-truth branch August 10, 2026 11:59
PathGao pushed a commit that referenced this pull request Aug 11, 2026
#566 added this test alongside its fix and asserted the fix: a part literally
named `rust-deps`, and `after: [rust-deps]` on the rust part. That fix does not
work, so the test was holding the file to a spelling that fails to build.

What has to stay true is the two constraints the four runs established. Rust
must come from a phase that runs before any part is pulled -- build-packages --
because a part that installs it is too late for the part pulling alongside it.
And nothing may ask the rust plugin to validate the environment, which reported
`'rustup' not found` twice with rustup present and runnable. Plus the toolchain
choice that the plugin's pull script used to make and no longer does.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PathGao added a commit that referenced this pull request Aug 11, 2026
…ll requests (#577)

* fix(snap): keep the gnome SDK's libc off the rustup call, and pack the snap on pull requests

The v2.7.3 snap publish failed in rust-deps, the part #566 added:

  /snap/rustup/1504/bin/rustup: symbol lookup error:
  /snap/gnome-46-2404-sdk/current/usr/lib/x86_64-linux-gnu/libc.so.6:
  undefined symbol: __nptl_change_stack_perm, version GLIBC_PRIVATE

`extensions: [gnome]` puts the gnome SDK's libraries on LD_LIBRARY_PATH for
every part's build, and the rustup snap's binary resolves libc.so.6 out of the
SDK rather than out of the one it was linked against. Cleared for that one
call: node/20/stable runs under the same LD_LIBRARY_PATH and has always been
fine, and the markpad part links against the SDK deliberately.

The second half is why this could ship broken at all. publish-packages.yml
checks out a release tag, so snapcraft.yaml is first executed after a change to
it has been merged, tagged and published. test_snap.yml packs it on any pull
request that touches it and stops there -- no release upload, no store push.

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

* fix(snap): install rustup from rustup.rs instead of the snap

Run 31479631082 -- the pack job this branch adds -- got `rust-deps` past the
GLIBC_PRIVATE crash and then failed in `markpad`:

  Environment validation failed for part 'markpad': 'rustup' not found.

The short form. v2.7.3's had a second clause naming rust-deps; that clause is
satisfied now, so the part is found and the binary will not run. Prefixing my
own call with `env -u LD_LIBRARY_PATH` cannot reach the one snapcraft makes
when it validates the rust plugin's environment.

So the carrier goes rather than the call site. The rustup.rs installer produces
an ordinary ELF linked against the instance's glibc, which is the same 2.39 the
Ubuntu 24.04-based gnome SDK ships, so it does not matter which of the two is
ahead on the library path. A classic snap's binary is linked against its own
runtime and does.

`markpad` names $HOME/.cargo/bin explicitly: its override-build replaces the
rust plugin's build step, so the plugin's PATH is not a thing to rely on.

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

* fix(snap): take rustup from apt, and drop the rust-deps part

Run 31480354438 -- the rustup.rs installer in rust-deps -- failed earlier than
the run before it:

  Pulling rust-deps / Pulling markpad     both 10:06:42
  /root/parts/markpad/run/pull.sh: line 4: rustup: command not found

`after:` orders build and stage, not pull. Both parts pull in the same second,
and the rust plugin's own pull script runs `rustup update stable` before any
override-build has had a chance to install anything. No part can provide rustup
to the part that pulls alongside it.

Between the three runs the constraints are now both known and they only leave
one carrier. It has to exist before anything is pulled, which is build-packages
or build-snaps; and it has to be an ordinary ELF rather than a classic snap's,
or LD_LIBRARY_PATH from the gnome extension feeds it the SDK's libc. apt is the
intersection: noble ships rustup 1.26.0 in universe, linked against the
instance's own glibc.

rust-deps goes with it. That part existed to satisfy the rust plugin when
rustup was not on PATH; it is on PATH now. This is a smaller file than the one
v2.7.3 shipped, and smaller than v2.6.11's.

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

* fix(snap): stop using the rust plugin this part never built with

Run 31481081985, with apt's rustup installed:

  Environment validation failed for part 'markpad': 'rustup' not found and
  part 'markpad' does not depend on a part named 'rust-deps' [...]

noble's rustup package installs /usr/bin/rustup, /usr/bin/cargo and
/usr/bin/rustc. Run 31479631082 had the snap's rustup on PATH and had just
watched the plugin's own pull script run it successfully, and got the same
'rustup' not found. Twice the binary was present and the validation said it was
not, so the validation is the thing to stop trying to satisfy.

Nothing here uses the plugin. override-build runs npm, runs tauri and installs
the binary itself, and has since the snap was added -- `plugin: rust`
contributed a build step this file overrides, a pull script that runs rustup,
and the validation. `plugin: nil` keeps `source:` and drops all three.

`rustup default stable` moves into override-build, because the pull script that
used to do it is gone and apt's cargo and rustc are shims that refuse to run
without a toolchain.

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

* test(release): assert what makes rustup findable, not the shape that did

#566 added this test alongside its fix and asserted the fix: a part literally
named `rust-deps`, and `after: [rust-deps]` on the rust part. That fix does not
work, so the test was holding the file to a spelling that fails to build.

What has to stay true is the two constraints the four runs established. Rust
must come from a phase that runs before any part is pulled -- build-packages --
because a part that installs it is too late for the part pulling alongside it.
And nothing may ask the rust plugin to validate the environment, which reported
`'rustup' not found` twice with rustup present and runnable. Plus the toolchain
choice that the plugin's pull script used to make and no longer does.

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 pushed a commit that referenced this pull request Aug 11, 2026
…gain

build.yml compiles Markpad on ubuntu-24.04 and uploads a .deb. snapcraft.yaml
then compiled it a second time inside LXD, from the same source, on core24 --
which is Ubuntu 24.04. Measured on run 31481634640: 8m15s of a 14m55s pack.

The cost was never mainly the minutes. Two independent compilations produce two
different binaries, so a snap user and a .deb user of the same version did not
have the same file. And compiling here is why this file held rust and node,
which is what #566 and three runs on #577 were about. `plugin: dump` with
`source-type: deb` removes the compiler, the toolchain, and the hand-written
desktop file and icon installs -- the .deb already carries all three, including
a 256x256@2 icon this file never installed.

The version comes from `dpkg-deb -f markpad.deb Version` rather than from
package.json, so the snap cannot be labelled a version it does not contain.

Verified before writing any of it: the .deb's payload is usr/bin/Markpad, the
desktop file and three icons, and the binary names 16 libraries by DT_NEEDED
with no RPATH. core24 covers libc, libm and libgcc_s; the gnome extension
covers the GTK and glib stack; stage-packages already covers webkit, jsc and
soup. libssl.so.3 and libcrypto.so.3 are covered by none of those three on
paper, so test_snap.yml now fails on snapcraft's missing-dependency report
rather than leaving it as a warning in the log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PathGao pushed a commit that referenced this pull request Aug 11, 2026
…gain

build.yml compiles Markpad on ubuntu-24.04 and uploads a .deb. snapcraft.yaml
then compiled it a second time inside LXD, from the same source, on core24 --
which is Ubuntu 24.04. Measured on run 31481634640: 8m15s of a 14m55s pack.

The cost was never mainly the minutes. Two independent compilations produce two
different binaries, so a snap user and a .deb user of the same version did not
have the same file. And compiling here is why this file held rust and node,
which is what #566 and three runs on #577 were about. `plugin: dump` with
`source-type: deb` removes the compiler, the toolchain, and the hand-written
desktop file and icon installs -- the .deb already carries all three, including
a 256x256@2 icon this file never installed.

The version comes from `dpkg-deb -f markpad.deb Version` rather than from
package.json, so the snap cannot be labelled a version it does not contain.

Verified before writing any of it: the .deb's payload is usr/bin/Markpad, the
desktop file and three icons, and the binary names 16 libraries by DT_NEEDED
with no RPATH. core24 covers libc, libm and libgcc_s; the gnome extension
covers the GTK and glib stack; stage-packages already covers webkit, jsc and
soup. libssl.so.3 and libcrypto.so.3 are covered by none of those three on
paper, so test_snap.yml now fails on snapcraft's missing-dependency report
rather than leaving it as a warning in the log.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PathGao added a commit that referenced this pull request Aug 11, 2026
…gain (#579)

build.yml compiles Markpad on ubuntu-24.04 and uploads a .deb. snapcraft.yaml
then compiled it a second time inside LXD, from the same source, on core24 --
which is Ubuntu 24.04. Measured on run 31481634640: 8m15s of a 14m55s pack.

The cost was never mainly the minutes. Two independent compilations produce two
different binaries, so a snap user and a .deb user of the same version did not
have the same file. And compiling here is why this file held rust and node,
which is what #566 and three runs on #577 were about. `plugin: dump` with
`source-type: deb` removes the compiler, the toolchain, and the hand-written
desktop file and icon installs -- the .deb already carries all three, including
a 256x256@2 icon this file never installed.

The version comes from `dpkg-deb -f markpad.deb Version` rather than from
package.json, so the snap cannot be labelled a version it does not contain.

Verified before writing any of it: the .deb's payload is usr/bin/Markpad, the
desktop file and three icons, and the binary names 16 libraries by DT_NEEDED
with no RPATH. core24 covers libc, libm and libgcc_s; the gnome extension
covers the GTK and glib stack; stage-packages already covers webkit, jsc and
soup. libssl.so.3 and libcrypto.so.3 are covered by none of those three on
paper, so test_snap.yml now fails on snapcraft's missing-dependency report
rather than leaving it as a warning in the log.

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
…660)

* docs: bring the release runbook and the syntax reference up to 2.7.4

Four claims in the docs describe behaviour that PRs since 2.7.3 changed, and
two features shipped without reaching the file that documents what Markpad
can do.

RELEASING.md
- snapcraft.yaml no longer builds the app; it unpacks the release's own .deb
  (#579), so the `npm ci` note and the `rust-deps` troubleshooting row both
  described a file that no longer exists in that shape (#577).
- The .deb/.rpm coverage note still said *Check for Updates…* offers an
  update and then fails to install it. #573 asks `self_update_supported`
  first, so it says where updates come from instead.
- Adds the AppImage checks (#584, #658) as their own troubleshooting row, and
  notes that release builds are serialized (#612).

README, README.zh-CN
- The same pre-#573 claim, in both languages. The Chinese one also told
  .deb/.rpm users to update "through their distribution channels", which is
  the sentence #566 removed from the English one — there is no apt or dnf
  repository.

snapcraft.yaml
- The comment pointed at .github/workflows/test_snap.yml, deleted in #601.

samples/markdown-syntax{,.zh-CN}.md
- Lists: Enter continues the marker, Tab changes level, Enter on an empty
  item leaves the list (#636), and the three list chords (#652).
- Tables: cell, row and column keys, why deleting a row has no chord (#645,
  #653).
- Links: Ctrl/Cmd+K (#652).

npm test 904 pass, vitest 365 pass.

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

* docs(syntax): drop two design arguments that belong in the PRs, not here

The syntax reference carries no issue numbers, no history and no defence of
a decision anywhere in its 500 lines; where it does give a reason -- the
three deliberate incompatibilities -- the reason tells the reader what to
write instead. Two sentences I added argued for the design at the reader:
that four other editors use Mod+K, and why deleting a row has no chord while
deleting a column does. Neither changes anything the reader would type.

What is left of the second is the part that was actually useful: to delete a
row, delete its line.

* docs(readme): drop the only issue number a reader ever sees

#570 was the last issue number in either README, and it was one I added a
few days ago. It is a closed bug report, and after #573 the sentence it was
attached to describes ordinary behaviour -- so a reader asking "how does my
.deb update" now clicks through to a fault that no longer exists.

The half-sentence in front of it went for the same reason: "rather than
offering one it cannot install" describes what the app used to do. What is
left is what the reader needs -- .deb and .rpm are one-time installs, there
is no apt or dnf repository, and Check for Updates says so.

Both READMEs keep the Report a Bug link, which is the only issue link either
of them had before.

* docs(releasing): cut the post-mortems back out of the runbook

This file is what you read on the day you cut a release. Over three days in
August I turned it into a runbook with post-mortems threaded through it:
which run pushed Chocolatey 2.7.2 at 15:37, that the snap served 2.6.11 for
three months and six versions, which two of the three v2.7.2 attempts the
AppImage strip failed in, how the 2.7.2/2.7.3 lock skew was found. All true,
none of it changes what you do next, and it sits between you and step 5
while a build is running.

Test applied to each one: can it change an action taken on release day.

Gone: the "Why package managers publish after the release" section entirely
-- its one operational sentence (workflow_dispatch works against a published
tag, the only way to exercise snapcraft.yaml) moves into step 7, where you
would need it. The failure histories in three troubleshooting rows, keeping
the symptom and the fix. The placeholder-pubkey era. Two design defences.

Kept: reasons that stop you doing the wrong thing -- why not to create
anything at alecdotdev/Markpad, why the Cargo.lock bump is the one that gets
forgotten. A prohibition without its reason gets deleted as superstition by
whoever comes next.

1957 -> 1563 words. The version before I started was 883.

---------

Co-authored-by: PathGao <gaoyanbo@gaoyanbodeMacBook-Air.local>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
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