Skip to content
Back to skills

Upgrade

DSecurity

Guide users through `pulp upgrade` — discover new CLI releases, interpret migration notes for the hop they're performing, and apply breaking-change fixes (CMake macro renames, API surface changes, config file moves). Handles "upgrade pulp", "what's new", "migrate my project", "show breaking changes", and the `/upgrade` slash command. Pairs with the embedded migration index shipped in the CLI binary (release-discovery Slice 3, #571).

  • 22 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added June 11, 2026
documentationrustgoc++shellbashexpresstestinggitapi

Works with

  • claude code
  • cli
  • api

Security analysis

D42/100
  • criticalPipes output to a shell interpreter
  • mediumUses curl or wget to download content
  • criticalDownloads and executes remote scripts — classic supply chain attack

Pro shows the line behind each finding and how to fix it

Scanned June 11, 2026

npx -y skills add danielraffel/pulp --skill upgrade --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Upgrade?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Upgrade
[![Security: D — Skills Directory](https://www.skillsdirectory.com/api/skills/danielraffel-upgrade/badge)](https://www.skillsdirectory.com/skills/danielraffel-upgrade)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: upgrade
description: |
  Guide users through `pulp upgrade` — discover new CLI releases, interpret
  migration notes for the hop they're performing, and apply breaking-change
  fixes (CMake macro renames, API surface changes, config file moves).
  Handles "upgrade pulp", "what's new", "migrate my project", "show
  breaking changes", and the `/upgrade` slash command. Pairs with the
  embedded migration index shipped in the CLI binary (release-discovery
  Slice 3, #571).
requires:
  tools:
    - pulp
---

# upgrade

## When this skill applies

- User asks to upgrade, update, or bump the Pulp CLI.
- User runs `/upgrade` in Claude Code.
- User asks "what changed", "what's new", "what breaks", or "how do I
  migrate" after bumping an SDK / CLI version.
- A `pulp doctor --versions` run surfaces CLI-vs-SDK skew the user wants
  to resolve.

This skill does NOT cover:

- Bumping the **project's** pinned SDK version — that's the
  `cli-maintenance` skill + `pulp project bump`.
- Bumping the Pulp framework/source checkout version — that's
  `pulp version bump` plus the release workflow.
- Shipping a PR from a dev branch — that's the `ci` skill via `pulp pr`.
- Updating the Shipyard pin — that's the Dependency Update Workflow in
  `CLAUDE.md` (runs through `tools/deps/audit.py`).

## Mental model

Four independently-versioned surfaces, two of which this skill acts on
directly and one of which it may hand off to `pulp project bump`:

| Surface | Source of truth | How to upgrade |
|---------|----------------|----------------|
| Pulp CLI / SDK | `~/.pulp/bin/pulp` (installed binary) | `pulp upgrade` |
| Consumer project SDK pin | project `pulp.toml` `sdk_version` + `find_package(Pulp X.Y.Z ...)` | `pulp project bump` |
| Pulp source checkout version | Pulp repo `CMakeLists.txt` / release metadata | `pulp version bump` + release workflow |
| Claude plugin | `.claude-plugin/plugin.json` | `/plugin install pulp` in Claude Code |
| Shipyard pin | `tools/install-shipyard.sh` | Dependency Update Workflow (out of scope) |

`pulp upgrade` downloads the new CLI binary and replaces the installed
one. After the Phase 8 Rust cutover, release archives are dual-binary:
Rust `pulp` is the user-facing CLI and sibling `pulp-cpp` is installed
as the C++ fallthrough delegate. A healthy install has both
`~/.pulp/bin/pulp` and `~/.pulp/bin/pulp-cpp`; use `PULP_USE_CPP=1
pulp <args>` or direct `pulp-cpp <args>` only for rollback/debug
comparisons. Do not hand-swap binaries in user/system locations.
The C++ `cmd_upgrade.cpp` path still matters for pre-cutover users
upgrading into a Rust release: it must copy the archive's sibling
payloads, including `pulp-cpp`, before replacing the running `pulp`
binary (#1673).

`pulp upgrade --notes` prints migration notes for the hop without
downloading anything. `pulp upgrade --notes --json` emits the same data
as a stable-shape JSON document for agent consumption (the `/upgrade`
slash command is the primary consumer).

Use `pulp project bump` only after deciding a consumer project should
move its SDK pin. It operates on the active project or the registry
(`--all`); it does not upgrade the global CLI and it refuses to treat
the Pulp source checkout as a consumer project.

### `pulp upgrade` updates BOTH CLI and SDK by default

Pre-#2087 `pulp upgrade` only swapped the CLI binary. Users who ran it
then built a project that still resolved its (months-old) SDK and
silently missed every framework fix in between (the Spectr incident
2026-05-15). Post-#2087:

- Default: fetch latest CLI binary + run `pulp sdk install --version
  <new>` immediately after the swap, so the matching SDK lands at
  `~/.pulp/sdk/<new>/` in the same invocation.
- `--cli-only` flag keeps the pre-#2087 behavior for the rare case
  where a user wants the new CLI paired with their current SDK.
- The SDK install is best-effort: a transient network failure logs a
  warning and tells the user to retry with `pulp sdk install`. The CLI
  swap is not rolled back — the user can always retry SDK install.
- When run from inside a project whose `pulp.toml` pins an explicit
  SDK version, the SDK install happens (so the new version is
  available globally) but the project's pin is left ALONE. A clear
  notice prints — "Project X stays on pinned SDK Y.Y.Y; latest
  available is Z.Z.Z — `pulp project unpin` to start tracking latest."
  Pinning is sacred when the user has explicitly pinned.

`pulp project pin <version>` is the new primary name for what
`pulp project bump` did. `bump` survives as a deprecated alias for
one minor release. New docs and skill examples should use `pin`.
`pulp project unpin` (new) flips a project back to floating mode
(`sdk_version = "latest"` in pulp.toml).

New projects created via `pulp create` default to floating mode
(`sdk_version = "latest"`), so they pick up framework fixes
automatically. `pulp create --pin` writes the exact version instead
for users who want reproducibility from day one.

## Discovery workflow (`pulp` on PATH is assumed)

The skill shells out to `pulp` — no hardcoded paths, no env-var
requirement. If `pulp` is not on PATH, tell the user to install first
(`curl -fsSL https://www.generouscorp.com/pulp/install.sh | sh`)
and stop.

Note: a successful `pulp upgrade` now **self-heals PATH** — after the
binary swap it appends the CLI's own directory to the user's shell
profile when it isn't already on `$PATH` (honoring `PULP_NO_MODIFY_PATH`).
This closes the gap where a CLI first installed via a source / SDK-prefix
install (`cmake --install --prefix ~/pulp-sdk` → `~/pulp-sdk/bin/pulp`)
could upgrade successfully yet still be "command not found" in a fresh
shell. So after an upgrade the user may need to restart their shell or
`source` the named profile, but no longer has to add PATH by hand. See
the `cli-maintenance` skill for the implementation
(`upgrade_install::ensure_dir_on_path`).

### Plugin ↔ CLI skew banner (Slice 6, #551)

Before running any `pulp` command, source the shared skew-check helper
so the user sees a single-line hint when the installed CLI is older
than the plugin's declared `min_cli_version`:

```bash
source "$(git rev-parse --show-toplevel 2>/dev/null || echo .)/tools/scripts/cli_version_check.sh"
pulp_cli_version_check   # no-ops silently if already checked this session
```

Behaviour:

- Banner (stderr, at most once per session):
  `[pulp] Claude plugin requires CLI >= v<MIN> but installed CLI is v<HAVE>. Run \`pulp upgrade\` or \`/upgrade\` in Claude Code.`
- Silent when CLI ≥ min, when the plugin manifest omits
  `min_cli_version` (older plugin builds), or when either version is
  non-numeric (dev builds).
- Overrides: `PULP_SKEW_CHECK_DISABLE=1` turns it off;
  `PULP_SKEW_CHECK_CACHE` overrides the session-marker directory.

The same skew logic is surfaced by `pulp doctor --versions` inline, so
users who prefer running the diagnostic directly see the finding there
too — the helper is a convenience for skill authors, not a second
source of truth.

1. **Identify the active plugin directory** (so the skill can resolve
   `docs/migrations/` for full-body lookups):

   ```bash
   pulp doctor --versions --json
   ```

   The JSON output contains `plugin_json_path` — walk up two levels
   (`dirname $(dirname $plugin_json_path)`) to reach the plugin root.

2. **Report the version skew at a glance** so the user sees where they are
   before deciding what to do. The JSON response also includes `cli`,
   `plugin`, `project_sdk`, and `findings[]` — surface those in a
   compact table.

3. **Fetch applicable migration notes** for the current hop.

   **Important:** user-invoked `pulp upgrade` and `pulp upgrade
   --install` force a synchronous release refresh instead of trusting
   the 24h cache. This is deliberate: v0.78.3 showed that a cache
   written minutes before a release can make a just-published version
   invisible. `pulp upgrade --check-only` remains the lightweight
   cache-aware probe; if you need release-fresh notes, capture the
   `Latest:` value from a refreshed/default `pulp upgrade` run and
   forward it as `--to "$LATEST"`.

   In CI / sandbox lanes, `PULP_UPDATE_CHECK_DISABLED=1` makes
   `--check-only` network-free. If the cache is empty in that mode, the
   command reports the installed CLI version plus an explicit
   disabled/not-queried latest line instead of querying GitHub Releases.

   ```bash
   pulp upgrade --notes --json                  # defaults: from = installed CLI, to = cached latest
   pulp upgrade --notes --json --to "$LATEST"   # recommended — use value captured from refreshed upgrade check
   pulp upgrade --notes --json --from X --to Y  # explicit hop
   ```

## Release verification gotcha

When verifying a newly published release, test from the previously
published CLI with a deliberately fresh cache that still names the old
release:

```bash
PULP_HOME="$(mktemp -d)" pulp upgrade --check-only --json
# seed/update-cache.json to latest_version=<previous> with a current timestamp
pulp upgrade
```

The default `pulp upgrade` path must refresh through GitHub and report
the new tag immediately. Then run `pulp upgrade --install --to X.Y.Z`
in an isolated install directory and confirm a follow-up `pulp upgrade`
reports both `Installed` and `Latest` as the new version. This catches
the stale-cache failure from #1685 before the release is announced.

   Stable-shape output (do NOT rename these keys — they are a public
   surface the skill depends on, see `tools/cli/migration_index.hpp`):

   ```json
   {
     "from": "0.27.0",
     "to":   "0.30.0",
     "entries": [
       {
         "version":   "0.28.0",
         "breaking":  false,
         "summary":   "…",
         "applies_if":"cli_version_from < 0.28.0 && cli_version_to >= 0.28.0",
         "body":      "…"
       }
     ]
   }
   ```

4. **Render the notes inline in the chat**. For each entry:
   - If `breaking == true`, prefix with a loud tag (e.g. "BREAKING").
   - Print the `summary` on the first line.
   - Include the `body` verbatim — it's already Markdown.

## Interpreting migration notes

Each note describes one release's visible behaviour shift. Read them
like a pro-developer changelog, not a marketing blurb.

### Breaking vs non-breaking

- `breaking: true` — requires user action: code change, config edit,
  habit change. Do not skip. Offer to grep the project for the affected
  symbols as a follow-up.
- `breaking: false` — informational. The upgrade works without
  intervention, but something worth knowing has changed.

### `applies_if` expressions

The filter runs in the CLI — by the time the skill receives JSON,
non-applicable entries are already stripped. You'll only see entries
whose `applies_if` matched the hop. Still: print the expression alongside
the note so a curious user can verify why it applied.

Grammar: Boolean combinations (`&&`, `||`, parentheses) over comparisons
of `cli_version_from` / `cli_version_to` against a literal semver. Six
operators: `<`, `<=`, `>`, `>=`, `==`, `!=`. See
`docs/migrations/README.md` for full details.

## Common breaking-change patterns

These are the patterns that have shown up in Pulp migration notes. When
you spot one, suggest the exact grep / replacement to the user instead
of leaving them to figure it out from a prose paragraph.

### CMake macro renames

**Pattern.** A `pulp_add_*` function in `tools/cmake/Pulp*.cmake` is
renamed, folded into `pulp_add_plugin(FORMATS ...)`, or split out of
one macro into several.

**What to do.**

```bash
# Find every call site.
grep -rn "pulp_add_ios_auv3\|pulp_add_auv3\|pulp_add_vst3_only" .
```

Propose the replacement form explicitly; do NOT rewrite the user's
CMakeLists.txt without confirmation. For unified-form migrations, show
a diff like:

```diff
- pulp_add_ios_auv3(MyPlugin …)
+ pulp_add_plugin(MyPlugin FORMATS AUv3 …)
```

### API surface changes

**Pattern.** A public C++ header under `core/*/include/pulp/**` adds,
removes, or renames a symbol that processors / view code use.

**What to do.**

```bash
# Find call sites of the old symbol.
grep -rn "OldProcessorAPI\|deprecated_function_name" .
```

Read the migration body for the replacement signature. If the change is
a deprecation (old API still works, emits a warning), flag that —
teams may want to migrate at their own pace.

### Config file / path moves

**Pattern.** A file Pulp reads (e.g. `~/.pulp/config.toml`,
`pulp.toml`, `.pulp/projects.json`) changes location or key shape.

**What to do.** Most config moves are handled by the CLI on first run
(it migrates the old location to the new one and prints a one-line
notice). The note tells you whether manual action is needed. If it is,
quote the exact `pulp config set` / `pulp config unset` commands from
the body.

### CLI flag / subcommand changes

**Pattern.** A subcommand is renamed (e.g. `pulp check` → `pulp doctor`),
a flag changes shape (`--sign-key` → `--ed25519-key`), or an exit code
semantic changes (silent-success → loud-failure as in #295 / #560).

**What to do.** If the user has CI scripts (`ci/`, `.github/workflows`,
`tools/local-ci/`, team-specific shell scripts), grep those for the
old invocation first — those are the scripts that'll silently break.

```bash
grep -rn "pulp <old-command>\|--<old-flag>" ci/ .github/ tools/ || true
```

## Bypass trailers

Slice 4 itself doesn't add new bypass trailers — it consumes Slice 3's.
But users will ask about trailers around upgrade-triggered PRs, so keep
the syntax handy (full table in `CLAUDE.md` → "Versioning & Skill-Sync
Policy"):

| Gate | Trailer (tip commit only, NEVER PR body) |
|------|-------------------------------------------|
| Version bump | `Version-Bump: <surface>=<patch\|minor\|major\|skip> reason="..."` |
| Skill update | `Skill-Update: skip skill=<name> reason="..."` |
| Auto-release | `Release: skip reason="..."` |

Trailer blocks must be **contiguous** — no blank lines between
trailers, or `git interpret-trailers --parse` stops reading. When
amending, rewrite the whole block, don't append.

## Decision tree (used by the `/upgrade` slash command)

```
pulp doctor --versions --json   → parse cli / plugin / project_sdk / findings
pulp upgrade --notes --json     → parse entries[]

present table:  CLI   vX.Y.Z → vA.B.C    (<hop>)
                plugin vP.Q.R → vP'.Q'.R' (if drift)
                project SDK vS.T.U  (skew warn from findings)

list applicable notes (inline, breaking flagged)

ask via AskUserQuestion:
  - "Upgrade CLI + bump project SDK" → `pulp upgrade` + `pulp project bump` in the active project
  - "Upgrade CLI only"            → `pulp upgrade`
  - "Bump project SDK only"       → `pulp project bump` in the active project or `--all` when explicitly requested
  - "Dismiss"                     → no action, remind user they can re-run `/upgrade`
```

## Testing the slash command

The `/upgrade` command shells out to `pulp upgrade --notes --json`.
A regression test lives in `test/test_cli_shellout.cpp` (the
`[issue-549]` tag) — it asserts that a synthetic hop produces the keys
the slash command depends on. If you add or rename a key in
`render_notes_json` (`tools/cli/migration_runtime.cpp`), update this
skill AND the slash command AND the test in the same PR.

## References

- Design: `planning/release-discovery-ux-design-2026-04-20.md` Section C + Slice 4
- CLI surface: `tools/cli/cmd_upgrade.cpp`, `tools/cli/migration_index.hpp`
- Migration doc schema: `docs/migrations/README.md`
- Slice 1 (diagnostics): #546 (`pulp doctor --versions`)
- Slice 2 (update check): #562 (`pulp upgrade --check-only`)
- Slice 3 (migration index): #571 (`pulp upgrade --notes --json`)
- Slice 4 (this skill): #549
- Slice 6 (plugin ↔ CLI skew): #551 (`min_cli_version` +
  `tools/scripts/cli_version_check.sh` + `plugin_min_cli` JSON field)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.