Skip to content

CI does not require a version bump when versioned content changes, so a merged change can reach no install #84

Description

@twistedmelonman

The gap

CLAUDE.md line 28 requires a version bump on every content change, in both
SKILL.md frontmatter and .claude-plugin/plugin.json, kept in lockstep.
Nothing enforces it. CLAUDE.md line 10 says so directly: validate.yml "also
doesn't enforce a bump on every SKILL.md change, but one is needed anyway."

So the rule is written down, known, and skippable.

It already happened

#82 retired four pattern groups from rules/taxonomy.md and added three
rules to rules/learned.md, and left the version at 1.1.0. It merged clean:
every check passed, both automated reviewers passed, and the pre-merge analysis
passed.

The merge changed nothing for any install. /plugin update treats an unchanged
version as nothing to pull, so the cache kept serving the pre-consolidation
rules. Measured after that merge, on the machine that did it:

  • Live cache was ~/.claude/plugins/cache/personify/personify/1.1.0/, marked
    .in_use.
  • Its rules/taxonomy.md still had all four retired groups. Expected 0, found
    4.
  • Its rules/learned.md had none of the three new rules. Expected 3, found 0.

#83 was then needed to bump 1.1.0 to 1.2.0 and ship what #82 had already
merged. That is the whole failure: a content change that merges green and
reaches nobody, with no signal anywhere that it did not land.

Worth noting what did not catch it. Two blocking AI reviewers read #82 and
passed it. The failure is invisible in a diff, because the defect is the
absence of a line in a file the diff does not touch.

Proposed check

In .github/workflows/validate.yml, which already runs on pull_request and
already parses plugin.json. Roughly:

  1. If the PR changes no versioned content path, pass.
  2. Otherwise require that version in SKILL.md frontmatter differs from the
    value on the base branch.
  3. Require that SKILL.md's version and .claude-plugin/plugin.json's version
    are equal. This half is worth running unconditionally, since a lockstep
    break is wrong whether or not content changed in the same PR.

Needs fetch-depth: 0 or an explicit base fetch to read the base version;
validate.yml currently does a default shallow checkout.

Which paths count

The set is the point. Too wide and every typo fix demands a bump, the rule gets
resented, and someone adds a skip label that becomes the new default.

Content that installs reach, and that a stale cache would serve wrongly:

  • SKILL.md
  • rules/ (taxonomy.md, hard.md, learned.md)
  • VOICE.example.md
  • commands/
  • reviewer/

Explicitly out:

  • mcp-server/ is versioned independently. CLAUDE.md line 12 says the
    lockstep rule "applies only to the skill content, not to this bridge."
  • tests/, scripts/, .github/, docs/
  • README.md, CLAUDE.md, AGENTS.md, LICENSE
  • VOICE.md is gitignored and cannot appear in a diff.

commands/ and reviewer/ deserve a second opinion. They ship with the
plugin, so a stale cache serves stale copies of both, but a bump for an edit to
either is arguably noise. I would rather include them and be wrong toward
safety than discover the same silent-staleness failure from a different
direction.

What "differs" should mean

Weakest useful rule: the version string is not equal to the base branch's. Not
"is greater" and not semver-aware.

Comparing semver correctly means parsing it, which means deciding what a
prerelease or a build-metadata suffix does, and that is more machinery than
this needs. A wrong-direction bump is a mistake a human notices in review; an
absent bump is the thing that already shipped and nobody noticed. Catch the
second and let review handle the first. Revisit only if a backwards bump
actually happens.

Why not a hook instead

A local pre-commit hook would catch it earlier, but only on a machine with the
hook installed, and it cannot see the base branch's version on a first commit
to a new branch without extra work. CI sees both sides by construction and
applies to every PR regardless of who opened it. Local hooks are a reasonable
addition later, not the primary enforcement.

Success criteria

Reconstruct #82 as it was, with no version change, and CI fails with a
message naming both the file to bump and the current value. Re-run with the
bump applied and it passes. A PR touching only README.md passes untouched.

This is the case to test against, not a synthetic one: it is the exact shape
that got through.

Also worth doing

CLAUDE.md line 10 currently documents the absence of this check. Update it
when the check lands, or it becomes a stale claim about CI behavior.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions