Featured image of post Your first run with skillup

Your first run with skillup

A change that ships without a version bump reaches nobody, and nothing tells you. Ten minutes in a throwaway directory to see it happen, and then catch it.

There’s a particular kind of bug I’ve grown to dislike more than the ones that crash.

I publish a handful of Claude Code plugins from a marketplace, five segments of skills and commands that other people install. Both harnesses cache them by <marketplace>/<plugin>/<version>, and neither of them resolves a git ref. So if I improve a skill, commit it, push it, and forget to move the version in its manifest… the change is sat there on main, looking for all the world like it shipped, and reaching precisely nobody.

No error. No warning. Nothing red anywhere. Just a thing I thought I’d released that nobody has.

skillup is the small tool that catches that, and this is ten minutes in a throwaway directory watching it happen and then stopping it. Nothing here touches a real repository.

You’ll need Go 1.26.5 or newer, and git.

This is the canonical walkthrough from the skillup docs, reproduced here so the two don’t drift. The docs are the source of truth: https://skillup.phpboyscout.uk/tutorials/first-run/. Every command and every output below was validated by actually running it.

1. Install it

go install gitlab.com/phpboyscout/skillup/cmd/[email protected]

Check it runs. $(go env GOPATH)/bin needs to be on your PATH:

$ skillup --help
Version the segments of a Claude Code plugin marketplace

Usage:
  skillup [command]

Available Commands:
  apply       skillup apply
  check       skillup check
  completion  Generate the autocompletion script for the specified shell
  help        Help about any command
  plan        skillup plan
  tag         skillup tag
  version     Print version, commit, and build date

No Go toolchain, or you’d rather build from a clone? See Install skillup.

2. Make a marketplace to point it at

mkdir -p /tmp/demo/plugins/alpha/.claude-plugin /tmp/demo/.claude-plugin
cd /tmp/demo
git init -q .

cat > .claude-plugin/marketplace.json <<'JSON'
{
  "name": "demo",
  "owner": { "name": "you" },
  "plugins": [{ "name": "alpha", "source": "./plugins/alpha" }]
}
JSON

cat > plugins/alpha/.claude-plugin/plugin.json <<'JSON'
{
  "name": "alpha",
  "description": "A demo segment",
  "version": "0.3.0"
}
JSON

git add -A && git commit -qm "chore: seed the marketplace"

One segment, at 0.3.0, and no tags anywhere. Hold onto that, it matters at the end.

3. Ask what should happen

$ skillup plan --path .
  every segment is at the version its commits warrant

Nothing has changed since the manifest was written, so there’s nothing to do.

4. Make a change, the way you normally would

echo "a new capability" > plugins/alpha/feature.md
git add -A && git commit -qm "feat(alpha): add a capability"

Now ask again:

$ skillup plan --path .
  alpha                    0.3.0    -> 0.4.0    (minor, 1 commit(s))

A feat: earns a minor. Had you written fix:, it’d say 0.3.1.

And this is the moment the whole tool exists for. If you pushed now, the content would be on the branch and the version would still say 0.3.0, and because both harnesses cache by version, nobody would ever receive it.

5. Watch the gate fail

check is what CI runs:

$ skillup check --path .
  alpha                    is 0.3.0 but its commits warrant 0.4.0 (minor)
      ac7f4ad9 feat(alpha): add a capability (minor)

  To fix, on your branch:

      skillup apply --path .
      git add -u && git commit --amend --no-edit   # or a new commit

  The version is what ships a segment: both harnesses cache by
  <marketplace>/<plugin>/<version> and neither resolves a git ref, so a
  version that does not move withholds your change from every consumer.

2026/08/13 06:56:44 ERRO a segment's version is behind what its commits warrant
$ echo $?
1

Worth noticing where the instruction sits: above the error, not below it. In a CI log the last line is a red ERRO, and whoever scrolls to it is looking for something to do rather than a diagnosis. Putting the fix underneath would have been tidier and less useful.

6. Fix it

$ skillup apply --path .
  alpha                    0.3.0 -> 0.4.0

Look at what changed:

$ git diff
-  "version": "0.3.0"
+  "version": "0.4.0"

One line. The rest of the manifest is untouched, byte for byte, which is the difference between a tool you let near your files and one you don’t.

Commit it with the change that earned it:

git add -u && git commit -q --amend --no-edit
$ skillup check --path . ; echo $?
  every segment is at the version its commits warrant
0

7. Nothing was ever tagged

You never created a tag, and it never asked for one. skillup worked out the previous version by reading the git history of plugins/alpha/.claude-plugin/plugin.json, finding the commit where that version last changed.

Prove it by making another change:

echo "a fix" >> plugins/alpha/feature.md
git add -A && git commit -qm "fix(alpha): correct it"
skillup plan --path .
  alpha                    0.4.0    -> 0.4.1    (patch, 1 commit(s))

It counted from 0.4.0, the version you committed a minute ago, not from the 0.3.0 you started with. The manifest’s own history is the baseline, which is why an untagged repository is a perfectly good input… and if you don’t believe me, git tag in that directory prints nothing at all.

Clean up

cd .. && rm -rf /tmp/demo

The failure it’s actually guarding

Every other release tool I’ve used protects you from shipping something broken. This one protects against shipping something that works perfectly and never arrives, which is a harder failure to notice and a much more annoying one to debug three weeks later when somebody mentions the feature they still haven’t got.

Once it’s a CI gate, the version simply cannot fall behind the commits, and I stop having to remember.

Where to go next: set up the gate in GitLab CI or in GitHub Actions, or read why the baseline is the manifest and not a tag if that decision seems odd to you.

Built with Hugo · Theme Stack designed by Jimmy