v1.9.1: a merged fix that no consumer loads is not a shipped fix

Released 2026-08-10. A patch whose entire content is a discovery: the marketplace serves a per-version install cache, so a fix merged without a version bump reaches nobody while looking exactly like a successful release.

What we were trying to fix

PR #5 had merged a fail-closed guard that stops implement-issue destroying GitHub issue bodies. That was not a theoretical hazard: the unguarded recipe it replaced had already blanked two live issues in a consumer repository, silently, exiting 0 both times.

Then somebody checked whether the fix was actually running anywhere. It was not (#6). Measured on the same day as the merge:

Location Commit scripts/tick-plan.sh The destructive recipe
main ba4724d present removed
the marketplace clone e8418d9 absent present
the cache agents actually load e8418d9 absent present

The installed plugin had not moved in 16 days, and the commit it sat on predated the fix by three commits. So every agent invoking the skill at that moment, the unattended fleet above all, was still running the code path that blanks an issue body.

The issue was careful to state what was not yet known. The obvious inference was “bump the version or the update will not take”, and the install record also carried a gitCommitSha, which pointed at commit-level tracking instead. Guessing would have produced the same failure shape as the bug itself: a change that looks like a fix and ships nothing. So the first task was to determine the propagation rule, not to assume it.

What we decided

Measure it, twice, each measurement ruling out a different escape route (PR #7).

First, advancing the marketplace clone forward to a commit that contains the fix, with .claude-plugin/plugin.json untouched, left the loaded cache completely unchanged. The cache is an install-time copy, not a live view onto the clone.

Second, running the plugin update against that refreshed clone answered “already at the latest version”. The updater compares the declared version field, so a content change at the same version is invisible to it. That killed the gitCommitSha theory outright: the recorded sha is a note of what was installed, not a live pointer.

The rule, stated plainly, is the whole release: the marketplace serves an install cache keyed by version, so a fix merged without a .claude-plugin/plugin.json bump reaches zero consumers, while green CI, a merged commit on main and a clone that visibly contains the fix all say otherwise.

Two things followed from it. The version went to 1.9.1, which is why this release exists at all. And a CI gate went in: a pull request touching skills/** without changing the declared version now fails. The measurement is what upgrades that gate from prudent to required, because such a PR is not merely at risk of not shipping, it is guaranteed not to ship.

What got cut

The issue was deliberately not auto-closed by the PR that fixed it. Four of its steps could only be finished after the merge, and a Closes keyword would have marked the propagation verified while it was still unverified. It was linked with a plain reference instead and closed by hand once the cache was confirmed to have moved. On a release whose subject is the difference between “merged” and “shipped”, that restraint is the point rather than a detail.

The gate was kept narrow on purpose. Changes confined to docs/, tests/, samples/ or .github/ are releasable without a bump, and the pattern is anchored so that a documentation file whose path merely contains the word “skills” does not trip it. An unreadable changed-file list fails closed rather than reading as “nothing changed”, which is the same fail-closed instinct the guarded write in PR #5 was built on.

What bit us

The version had already been bumped in .claude-plugin/plugin.json without a tag or a release ever being created, so git’s history did not record what consumers were by then running. The tag and the changelog entry for this release were therefore both placed retroactively, on the commit that carried the fix, at the same time release-please was installed. release-please needs a real anchor to compute the next version from, and it had none.

That is a small piece of bookkeeping with a large moral, and it is why this article exists. For most of a day, the repository’s state and the world’s state disagreed, and every signal available (a green build, a merged commit, a clone containing the fix) reported the world’s state as the repository’s. Nothing in the toolchain was lying. Nobody had checked which question the toolchain was answering.