Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion plugins/claude-ops/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
"name": "claude-ops",
"version": "0.42.12",
"version": "0.42.13",
"description": "Claude Code operations toolkit. Twelve skills: audit-skill-visibility (audit whether each installed skill is actually VISIBLE to the model, and diagnose why most of a fleet never gets used \u2014 a skill is invisible when its description is dropped by Claude Code's skill-listing context budget, which sheds descriptions lowest-score-first so an unused skill loses the keywords that would let it be matched, from skills genuinely not wanted, from skills the run cannot observe at all; computes whether the listing overflows from documented settings, and withholds every cold verdict the data cannot support rather than reporting absence of data as absence of use), inventory (read-only enumeration of the complete invocable surface \u2014 every built-in CLI command with aliases and hidden/gated status, every bundled skill, and every component of every installed plugin across all marketplaces; reads the shipped binary because upstream publishes no built-in command list, and carries an integrity verdict so a drifted build reports counts as floors rather than silently short totals), audit-install-state (read-only audit of the machine-scope ~/.claude installation directory and ~/.claude.json \u2014 full inventory split into an authored surface and rolled-up bulk trees, product-managed retention vs genuinely unmanaged state, filename-scheme resolution before any process-liveness check, and deliberate/mid-experiment detection; reports, never deletes), audit-performance (read-only slowness-diagnostic capture run at the moment the machine or a session feels slow: CLI version, retention-sweep health including the silent unparsable-settings pause, a timed census walk of the install tree as a sweep-cost proxy, active-session and plugin-fleet counts, a process census, and the fan-out layer, which covers a load-labelled no-op spawn baseline, every hook that will fire bucketed per-tool-call versus per-turn with its invocation shape, the configured statusline, subagent concurrency and spawn-depth ceilings against documented defaults, whether running sessions predate the settings file they are judged by, and orphan attribution by parent liveness rather than age, plus on Windows a kernel-object census (Token objects against uptime, paged pool) that names a host-level leak beneath all four suspects; read against a bundled known-performance-issues reference that also records the causes tested and cleared; separates the four documented suspects of accumulated state, version regression, component bloat, and per-spawn fan-out cost, and routes remediation out; reports, never mutates, and never executes a discovered hook or statusline command), audit-native-overlap (map native Claude Code surfaces \u2014 built-in CLI commands, bundled skills, plugin-backed built-ins, session-provided skills \u2014 against the current repo's plugin skills and agents, so a custom component never silently duplicates what Claude Code itself ships; bare invocation is a read-only overlap report carrying the extraction's integrity floors and a shared-listing-budget exposure section, verdicts are human-gated in a committed store rendered into a generated registry whose every row carries an observable recheck trigger, and only an explicit apply step bakes presence-gated native references into descriptions and Boundary sections), observability (read locally captured telemetry \u2014 OTEL store, collector, the per-session hook event log and hook-event JSONL, ccusage \u2014 with trend reports, a per-session report of what fired, what was blocked and the event timeline, and store pruning), known-issues (search known Claude product GitHub bugs, check service health, maintain a persistent tracked-issue registry), changelog (ingest Claude Code changelog entries and integrate them into the current repo), plugins (bring a machine's plugin fleet current on demand \u2014 marketplace refresh, effective-scope updates including in-repo project/local installs, new-plugin install per policy, scope-divergence detection and explicit convergence), morning-brief (read-only gh-based operator morning view \u2014 queue-label counts, merge-ready PRs, parked decisions with their RECOMMENDED lines, and loop-lane telemetry freshness), lanes (start/restart/stop/status loop lanes as named background Claude Code sessions seeded from canonical prompt files, with per-lane model/effort, a repo-pull + marketplace-refresh launch step, and a consume-restarts action \u2014 an OS-schedulable reader that relaunches stopped lanes whose telemetry carries a restart_request), and a re-runnable setup action that settles where the known-issues registry, the skill-usage log and the hook log root live, places the root's self-ignoring guard, and detects retired conventions. Plus an opt-in, default-off per-session hook event log (one JSON line per hook event on every event the generated registry marks observable, written to <root>/sessions/<session_id>.jsonl, with SessionEnd retention by session count or age and an optional detached pre-prune command), a family of eight advisory *-audit hooks (API errors, config changes, instruction loads, permission denials, pre-compaction, skill usage, tool failures, and unsurfaced hook failures \u2014 the last also warns the user via systemMessage, since a hook that fails to launch enforces nothing and Claude Code surfaces the failure to nobody) that emit the shared hook-telemetry envelope, and a reference sink that routes envelopes under the same root: per session when the envelope carries a session id, else into the shared hook-events.jsonl the observability skill reads.",
"author": {
"name": "Melodic Software",
Expand Down
61 changes: 61 additions & 0 deletions plugins/claude-ops/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,67 @@
All notable changes to the `claude-ops` plugin are documented here. Format follows
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.

## [0.42.13]

### Added

- **A standing cache-content check in the `plugins` skill, so a version-and-sha match is no
longer taken as proof that the files on disk are the build the record names (#3681).**
`claude plugin update` re-points an install record's `gitCommitSha` without rewriting the
plugin's cache directory when the manifest version number is unchanged across the two commits,
because the cache is keyed by version. The record then claims the new commit while the directory
still holds the older build, and every check the skill had passed in that state. On the reporting
machine six plugins were in it at once, twelve stale files in the worst case, including a reviewed
dispatcher and two `hooks.json` files. Any measurement or behaviour test against those caches was
a test of a different build. The new `cache-content-check.sh` byte-compares every file in a cache
directory against the recorded commit in the marketplace clone, in both directions: a changed
file, a file the commit has and the cache lacks, and a file deleted at the commit but still
sitting in the cache. It runs as Step 5b of `sync` and of `audit`, ungated, because an unchanged
version number is exactly the case in which every other step reports success. Cache-only files the
marketplace repo's own `.gitignore` covers are excluded: a cache directory is a live plugin root,
so it accumulates `__pycache__` and vendored dependencies that were never in any commit, and
without that filter three installs on the authoring machine reported stale on generated state
alone. The report gains a `Cache content:` row naming the affected ids and the remediation that
was proved to work, which is removing that version's directory and re-running the update.
- **The check reports and never repairs, and never reaches the network.** It writes no state file,
removes no cache directory, and does not `git fetch` a commit the marketplace clone lacks. A
missing commit is reported as `sha-not-local` and counted as unverifiable rather than as a pass,
because fetching it would be a mutation the audit does not perform and would erase the very
condition the verdict exists to report. A project-scope record whose `projectPath` is not on this
machine is counted as skipped rather than verdicted, on the same reasoning the skill already
applies to stale project records: absent is not dead.
- **The compare reads the plugin's source path from the RECORDED commit, not from the marketplace
clone's current checkout.** A plugin directory renamed or moved after the recorded commit was
otherwise looked up under its present-day path against an older tree, and `git ls-tree` treats a
pathspec that matches nothing as success with empty output rather than an error — so every file in
a perfectly healthy cache became an extra and the install reported `stale-content`. The manifest is
now read with `git show <sha>:.claude-plugin/marketplace.json`, with no fallback to the checkout,
so the source path and the expected tree describe the same revision. A pathspec that still matches
nothing at that sha gets its own `no-source-at-sha` verdict, counted unverifiable, so an empty
expected tree can never be reported as a content difference. The manifest read is cached per
distinct sha rather than paid per install record.
- **Pathnames travel NUL-separated end to end.** `ls-tree -z`, `find -print0` and `check-ignore -z`
replace their line-oriented forms. Without `-z`, git quotes and escapes any pathname carrying
non-ASCII, a tab, a newline or a backslash, and the compare then read that quoted spelling and the
raw path as two different files — an unchanged accented filename was reported as both
missing-from-cache and extra-in-cache. `git hash-object --stdin-paths` has no `-z` switch, so a
cache path containing a newline, and only that character, is hashed by its own process instead.
- **Tracked symlinks are compared mode-aware instead of reported missing.** A symlink is an ordinary
blob whose content is the link target text, but `find -type f` excluded it, so every tracked link
read as permanently missing from the cache. The cache walk now enumerates links as well as regular
files and hashes a link's `readlink` output, which is what git itself stores.
- **Install records are decoded with a US (0x1f) separator rather than a tab.** Bash treats tab as
IFS whitespace, so an empty middle column collapsed: a record carrying no `gitCommitSha` shifted
its `installPath` into the sha field and reported `install-path-missing` with a fabricated sha
instead of the honest `no-git-commit-sha`. This is the separator `fleet-state.sh` already uses for
its own internal records.
- **Route (b) of the three the issue offered, taken deliberately.** Route (a), a repo rule that
every plugin change bumps the version, prevents nothing already delivered and depends on every
future author remembering it; route (c), an upstream report, has no delivery date this repo
controls and leaves the machine undetected in the meantime. Route (b) is a check that runs on
every sync and audit, needs no author discipline, and holds whatever upstream does. It does not
preclude the other two.

## [0.42.12]

### Changed
Expand Down
51 changes: 51 additions & 0 deletions plugins/claude-ops/skills/plugins/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,22 @@ The third form projects that same id list from a report already on disk rather t
fleet, and is the form `sync`'s steps use: each step re-reads the full report anyway, and every
selector is derivable from it. Same script, same projection, so the `\r` protection is unchanged.

A second read-only script answers the question `fleet-state.sh` structurally cannot: whether the
files in a plugin's cache directory actually match the commit its install record claims. Run it as
Step 5b of `sync` and of `audit`:

```bash
"${CLAUDE_PLUGIN_ROOT}"/skills/plugins/scripts/cache-content-check.sh --marketplace <name> [--scope user|project|all]
"${CLAUDE_PLUGIN_ROOT}"/skills/plugins/scripts/cache-content-check.sh --marketplace <name> --ids
```

`--ids` emits the stale ids alone, one per line, CR-free, the same contract and for the same reason
as `fleet-state.sh --ids`. The script never writes anything and never runs `git fetch`; a commit
that is not in the local marketplace clone is reported as `sha-not-local`, not fetched. See
[context/sync.md](context/sync.md) Step 5b, and
[context/scope-semantics.md](context/scope-semantics.md) for the mechanism that makes a cache
directory and its recorded sha disagree in the first place.

`sync` writes its run journal under this plugin's per-machine data directory. The path is
substituted here because `${CLAUDE_PLUGIN_DATA}` resolves in skill content and **not** in a
`context/*.md` spoke, which is read raw:
Expand Down Expand Up @@ -168,6 +184,8 @@ Divergences: <N> actionable (<M> newly created by this run — <a> by the in-rep
or listed here)
Stale project records: <K> record(s) across <P> path(s) not present on this machine
(omit section entirely when K = 0; never counted in Divergences — see below for the row shape)
Cache content: <N> install(s) whose cache files disagree with their recorded gitCommitSha
(omit the row entirely when N = 0; list the ids and the remediation — see below)
Action needed: <bulleted list — missing_from_user_install, missing_from_enabled, project-scope
enable gaps, CLI failures, unknown/orphaned plugins, user_scope_orphans, plugin(s) installed this
run with unset userConfig options, user-scope enabledPlugins reorder failures, a project-scope
Expand Down Expand Up @@ -267,6 +285,39 @@ declines to pay on its own (Claude Code ≥ 2.1.163; see
monitor, call that out separately. Monitors need a full session restart, `/reload-plugins` doesn't
cover them.

## Cache content. Reported, never repaired

A version-and-sha check is not proof that the files on disk are the build the record names. When a
plugin's manifest version does not change across a commit, `claude plugin update` re-points the
record's `gitCommitSha` and leaves the existing version directory in place, so the metadata claims
the new commit while the directory still holds the old build. See
[context/scope-semantics.md](context/scope-semantics.md) for the observation this rests on.

Step 5b runs `cache-content-check.sh`, which byte-compares every file in each cache directory
against the recorded commit in the marketplace clone. Omit the `Cache content:` row when it finds
nothing. When it finds something, name the ids and give the remediation that was actually proved to
work, rather than a suggestion:

```text
Cache content: <N> install(s) whose cache files disagree with their recorded gitCommitSha
- <id>@<marketplace> <version> — <n> file(s) differ
Remediation: remove that version's directory under the plugin cache, then re-run
`claude plugin update <id>@<marketplace>`, which recreates it from the clone.
```

**The check never repairs.** It does not delete a cache directory, does not re-run an update, and
does not `git fetch` a commit the marketplace clone lacks. A commit that is not local is reported as
`sha-not-local` and left alone: fetching is a network mutation this audit does not perform, and it
would also silently erase the condition the verdict exists to report. Every verdict other than
`match` and `stale-content` is counted as `unverifiable` — the audit looked and could not decide,
which is its own number and never folded into either side.

**Expect a substantial `unverifiable` share, and never read it as a pass.** Claude Code clones a
marketplace shallow, so any install whose recorded commit predates that clone's window reports
`sha-not-local` through no fault of the fleet. On the machine this check was first run against, 11
of 74 user-scope installs were unverifiable for exactly that reason. When the unverifiable count is
material, say so alongside the match count rather than leading with the match count alone.

## userConfig: `install_new`

Controls new-catalog-plugin install policy during `sync`. Ships as a plain `string` (the manifest
Expand Down
35 changes: 35 additions & 0 deletions plugins/claude-ops/skills/plugins/context/scope-semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,41 @@ Code v2.1.193 or later." The `claude plugin prune` ≥ v2.1.121 gate is **not re
the current docs describe `prune` without naming an introducing version, so the gate stands on its
original source and nothing this pass found contradicts it.

## An unchanged version number keeps the old cache directory while `gitCommitSha` moves

`claude plugin update -y <plugin>@<marketplace>` re-points the install record's `gitCommitSha` in
`installed_plugins.json` without rewriting the plugin's cache directory when the manifest version
number is unchanged across the two commits. The cache is keyed by version, so an update that does
not move the version finds the directory already there and leaves the older build in it. The record
then names the new commit and the files on disk are the old one.

**Consequence, and it is the reason the check exists.** The version-and-sha comparison every
delivery script relies on passes in exactly this state, so it is not proof that the files loaded are
the files delivered. Any measurement or behaviour test run against that cache directory is a test of
a different build than the one the record names, and nothing in the report says so.

Observed on **Claude Code 2.1.259** (issue #3681 evidence, not re-run since). After a delivery,
six plugins reported the new sha while their cache directories still held files from an earlier
commit — twelve stale files in the worst case, including a reviewed dispatcher, three formatters, and
two `hooks.json` files. Removing those version directories and running the update again recreated
them correctly from the clone, which is both the confirmation and the remediation. **Recheck
trigger:** any minor-version bump touching plugin caching or the `plugin update` path — a date alone
is not a trigger.

`cache-content-check.sh` is the standing detection: it byte-compares every file in a cache directory
against the recorded commit in the marketplace clone, which is the only check that separates this
state from a healthy one. It reports and never repairs; see `SKILL.md`'s "Cache content" section.

**A marketplace clone is shallow, so most installs are unverifiable most of the time.** The clone
under `installLocation` carried a `.git/shallow` file and a three-commit history when this was
measured, so an install record naming any commit older than that window has no object to compare
against. Verified 2026-09-05 on **Claude Code 2.1.261**: 11 of 74 user-scope installs on the
authoring machine reported `sha-not-local` for exactly this reason, on a fleet with nothing wrong
with it. That is the steady state, not an edge case, and it caps how much any single run of the
check can establish. The check never fetches the missing commit: a fetch is a network mutation, and
it would repair the condition being reported. **Recheck trigger:** any change to how Claude Code
clones a marketplace, which would move the depth this number rests on.

## `autoUpdate` is a background complement, not a substitute

Official-Anthropic marketplaces default `autoUpdate: true`; third-party and local-dev marketplaces
Expand Down
Loading
Loading