Skip to content

chore(release): version each plugin from its own commits - #481

Merged
JacobPEvans-personal merged 8 commits into
mainfrom
chore/per-plugin-release-components
Sep 12, 2026
Merged

JacobPEvans-personal merged 8 commits into
mainfrom
chore/per-plugin-release-components

Conversation

@openbao-service-broker

@openbao-service-broker openbao-service-broker Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Problem

release-please tracked a single package at the repo root and stamped that one version into the plugin.json of most plugins via extra-files. Any commit anywhere in the repo therefore moved every plugin's version.

A plugin's installed cache directory is named cache/<marketplace>/<plugin>/<version>, so a plugin whose content was byte-identical still got a brand-new directory on every release. Sessions already running resolve plugin paths at startup and re-stat ${CLAUDE_PLUGIN_ROOT} on every hook invocation, so that churn broke them:

PreToolUse:Bash hook error
Failed to run: Plugin directory does not exist: .../git-guards/5.10.1

The plugins absent from extra-files never moved — ai-cli-harness-better-practices (4.24.0), homelab-ops (1.0.0), openbao (4.21.0), slack (5.10.1) — while the repo went 5.11.0 → 5.13.0. That control group is what identified the mechanism.

Change

Each of the 21 plugin directories becomes its own release-please package with its own component, so its version tracks only its own commits.

  • Shared settings (release-type, include-v-in-tag, changelog-sections) move to root-level defaults instead of being repeated per package — the config ends up smaller than before, not 21x larger.
  • separate-pull-requests stays false, so this still produces one aggregated release PR. The org workflow's auto-merge step reads a single pr output and is unaffected.
  • The caller now passes include-component-in-tag: true, so plugins tag as <plugin>-v<version> while the root package keeps v<version>.
  • Each component stamps its own plugin.json plus its own entry in the root .claude-plugin/marketplace.json, addressed by a filtered JSONPath.

Also corrects a pre-existing inconsistency: marketplace.json listed every hand-versioned plugin at the repo-wide version rather than its real one.

Merge with main (2026-09-12)

Main released 6.2.1 while this PR was open. The manifest and marketplace.json now record each plugin at the version its own plugin.json carries on main (6.2.1, with the hand-versioned plugins at 4.25.0 / 1.0.0 / 4.21.0), and last-release-sha is re-anchored to the current main head, so the first per-component run bumps nothing that has not changed since 6.2.1.

Verification done

  • Config validates against release-please's published schema.
  • All 43 extra-file targets resolve to real files under release-please's own addPath rules — a leading / is repo-root-relative, and none trip its illegal-pathing check.
  • The filtered JSONPath was exercised against the real marketplace.json with the same jsonpath-plus version release-please bundles: it matches exactly one plugin entry and leaves the others untouched.

Verification that CI cannot do

CI does not run release-please, so the first run on main after this merges is the real test. What to check on that run:

  1. It opens exactly one release PR, not 21.
  2. That PR bumps only the plugins with commits since their manifest version.
  3. Tags come out as <plugin>-v<version>, with the root staying v<version>.
  4. Auto-merge is enabled on the release PR.

Reverting is the config + manifest + workflow input; any component tags created would need deleting separately.

🤖 Generated with Claude Code

https://claude.ai/code/session_011KxaQ6Hu73ymuCLtGFJyD7

release-please tracked one package at the repo root and stamped that one
version into 17 plugin.json files. Any commit anywhere therefore moved
every plugin's version, and a plugin's cache directory is named
cache/<marketplace>/<plugin>/<version> — so a plugin whose content was
byte-identical still got a new directory on every release. Sessions
already running resolve plugin paths at startup and re-stat
CLAUDE_PLUGIN_ROOT per hook invocation, so that churn broke them.

Three plugins were absent from extra-files and never moved
(ai-cli-harness-better-practices, homelab-ops, openbao), which is what
identified the mechanism.

Each plugin directory is now its own release-please package with its own
component, so its version tracks only its own commits. Shared settings
move to root-level defaults rather than being repeated per package.
separate-pull-requests stays false, so this still produces a single
aggregated release PR and the org workflow's auto-merge step, which reads
one PR, is unaffected. The caller now passes include-component-in-tag so
components tag as <plugin>-v<version>; the root package keeps v<version>.

Each component stamps its own plugin.json, plus its own entry in the
root marketplace.json via a filtered JSONPath. That also fixes a
pre-existing inconsistency: marketplace.json listed all three
hand-versioned plugins at 5.11.0 while their plugin.json did not.

Verified: config validates against release-please's published schema;
all 41 extra-file targets resolve to real files under release-please's
own addPath rules (a leading slash is repo-root-relative), and the
filtered JSONPath matches exactly one plugin entry and leaves the rest
untouched.

Assisted-by: Claude:claude-opus-5
Claude-Session: https://claude.ai/code/session_011KxaQ6Hu73ymuCLtGFJyD7
release-please tracked one package at the repo root and stamped that one
version into the plugin.json of most plugins. Any commit anywhere
therefore moved every plugin's version, and a plugin's cache directory is
named cache/<marketplace>/<plugin>/<version> — so a plugin whose content
was byte-identical still got a new directory on every release. Sessions
already running resolve plugin paths at startup and re-stat
CLAUDE_PLUGIN_ROOT per hook invocation, so that churn broke them.

The plugins absent from extra-files never moved
(ai-cli-harness-better-practices, homelab-ops, openbao, slack), which is
what identified the mechanism.

Each plugin directory is now its own release-please package with its own
component, so its version tracks only its own commits. Shared settings
move to root-level defaults rather than being repeated per package.
separate-pull-requests stays false, so this still produces a single
aggregated release PR and the org workflow's auto-merge step, which reads
one PR, is unaffected. The caller now passes include-component-in-tag so
components tag as <plugin>-v<version>; the root package keeps v<version>.

Each component stamps its own plugin.json, plus its own entry in the root
marketplace.json via a filtered JSONPath. That also fixes a pre-existing
inconsistency: marketplace.json listed every hand-versioned plugin at the
repo-wide version while its plugin.json did not.

Verified: config validates against release-please's published schema; all
43 extra-file targets resolve to real files under release-please's own
addPath rules (a leading slash is repo-root-relative), and the filtered
JSONPath matches exactly one plugin entry and leaves the rest untouched.

Assisted-by: Claude:claude-opus-5
Claude-Session: https://claude.ai/code/session_011KxaQ6Hu73ymuCLtGFJyD7
The first attempt was built against 5.11.0. main has since cut 5.12.0 and
5.13.0 and gained a 21st plugin (slack), so the generated config and
manifest are regenerated from the current plugin.json versions rather
than conflict-resolved. Content is the regeneration; this merge only
records the superseded tip.
Replaces the single repo-wide release-please package with one component per
plugin directory, so a plugin's version tracks only its own commits.

- Each plugin package stamps its own plugin.json and its own entry in the
  root marketplace manifest, replacing the wildcard that stamped every entry.
- The root package keeps the repo-wide version and the manifest metadata
  version.
- Corrects the marketplace entries for the plugins that were absent from the
  old extra-files list and so carried the repo-wide version rather than their
  own.
- Tags carry the component name.

Assisted-by: Claude:claude-opus-5
Claude-Session: https://claude.ai/code/session_011KxaQ6Hu73ymuCLtGFJyD7
The previous generation was made against an older main. This regenerates it
against current main; the superseded tip is recorded as a parent so the push
is a fast-forward.
Without an anchor, each new component walks its whole history and re-reads
breaking markers from months ago. A dry run against this branch proposed
seven unintended major bumps on the first release. last-release-sha starts
every component from the tree whose versions the manifest already records,
so a plugin bumps only on commits that land after this change.

Assisted-by: Claude:claude-opus-5
Claude-Session: https://claude.ai/code/session_011KxaQ6Hu73ymuCLtGFJyD7
Manifest and marketplace entries take each plugin's current version from
its own plugin.json on main; last-release-sha moves to the current main head.

Assisted-by: Claude:claude-opus-5
Claude-Session: https://claude.ai/code/session_01U32ZwmRFZtdzUH5LZBqR3Q
@openbao-service-broker openbao-service-broker Bot added validated:pass agent-validated status is success on head SHA human:review Human review required before merge to main - AI must not merge without explicit instruction labels Sep 12, 2026
@JacobPEvans-personal
JacobPEvans-personal merged commit 0246083 into main Sep 12, 2026
31 checks passed
@JacobPEvans-personal
JacobPEvans-personal deleted the chore/per-plugin-release-components branch September 12, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

human:review Human review required before merge to main - AI must not merge without explicit instruction validated:pass agent-validated status is success on head SHA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant