From 1c1f516b5c4786eb4adae2ddddc31f1c866626f8 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 10:26:03 -0400 Subject: [PATCH 1/4] docs(claude-ops): record the --all + install_new:all mass install On 2026-09-14 `/claude-ops:plugins sync --all` run with the `install_new` userConfig set to `all` installed 2,231 plugins across every known marketplace in 22 minutes, ~49 GB of cache, and left 22 third-party MCP servers running for 8.5 hours. Against the one curated marketplace the policy was written for, `all` means "install the handful I am missing"; `--all` multiplies it across every catalog the machine knows about, and the wording does not change between the two cases. gotchas.md: new section recording the incident, why the downgrade guard and `audit` both miss this combination, the `claude --bare` requirement for the revert (a plain `plugin uninstall` spawns a session that loads every still- enabled plugin and boots its MCP servers), and the revert recipe. SKILL.md: the marketplace-resolution list now warns that `all` multiplies the install policy across catalogs, and the `install_new` section requires a counted human confirmation before that combination runs. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XD4Hr2S7RA2G8PR2CadLP1 --- plugins/claude-ops/skills/plugins/SKILL.md | 17 +++++++- .../skills/plugins/context/gotchas.md | 40 +++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/plugins/claude-ops/skills/plugins/SKILL.md b/plugins/claude-ops/skills/plugins/SKILL.md index 073b27c03..64f2d5280 100644 --- a/plugins/claude-ops/skills/plugins/SKILL.md +++ b/plugins/claude-ops/skills/plugins/SKILL.md @@ -150,7 +150,10 @@ No hardcoded marketplace name anywhere in this skill. Every action resolves its `installed_plugins.json`'s install records, never a hardcoded name). - `` argument → that marketplace only. - `all` argument → every marketplace in `known_marketplaces.json`; per-marketplace failures are - reported inline and never abort the sweep (see [context/sync.md](context/sync.md)). + reported inline and never abort the sweep (see [context/sync.md](context/sync.md)). **`all` also + multiplies the `install_new` policy across every catalog**: under a rendered policy of `all` this + target installs every plugin published in every known marketplace. Confirm the resolved install + gap with a human first, per "userConfig: `install_new`" below. ## State inspection @@ -270,6 +273,18 @@ schema has no `enum` type. Verified against the published schema), default `"ask Any explicitly-set value other than these three is invalid; treat it as `ask` and note the invalid value in the report. +**`all` is scoped to the marketplace target, and the target is what makes it safe or catastrophic.** +Against the default marketplace, the one whose fleet the operator curates, the gap is normally zero +or a handful and `all` is the intended convenience. Against a multi-marketplace target (`all`, or a +named third-party catalog) the same word means "install every plugin published in every catalog this +machine knows about." One observed run installed 2,231 plugins before it was killed; see +[context/gotchas.md](context/gotchas.md)'s "`--all` with `install_new: all` is a mass install of +every catalog". **When the marketplace target is `all` and the rendered policy is `all`, do not +proceed unattended:** resolve the total install gap first (one `audit all`, or `fleet-state.sh +--ids install-gap` per marketplace), state the number, and get an explicit human yes. Treat the +configured value as written with the operator's own marketplace in mind, and downgrade to `ask` when +no human is present to receive the count. + **Configured value: `${user_config.install_new}`**. Claude Code text-substitutes a `userConfig` value into this skill's content before the model sees the rendered skill, but **only when the key is explicitly set** in user settings (`~/.claude/settings.json`), `--settings`, or managed settings: precedence managed → `--settings` → user. It is **not** "some `pluginConfigs` scope": a project's diff --git a/plugins/claude-ops/skills/plugins/context/gotchas.md b/plugins/claude-ops/skills/plugins/context/gotchas.md index c6ab3ea4f..dd6d7801f 100644 --- a/plugins/claude-ops/skills/plugins/context/gotchas.md +++ b/plugins/claude-ops/skills/plugins/context/gotchas.md @@ -15,6 +15,7 @@ - [`marketplace remove` is a bulk uninstall, not a declaration removal, and it deletes this skill's own run journal](#marketplace-remove-is-a-bulk-uninstall-not-a-declaration-removal-and-it-deletes-this-skills-own-run-journal) - [Internal-schema drift: fail loud, never guess](#internal-schema-drift-fail-loud-never-guess) - [Captured values on Windows carry `\r`: strip it before embedding in any command or JSON](#captured-values-on-windows-carry-r-strip-it-before-embedding-in-any-command-or-json) +- [`--all` with `install_new: all` is a mass install of every catalog, and nothing warns you](#--all-with-install_new-all-is-a-mass-install-of-every-catalog-and-nothing-warns-you) Failure modes this skill is specifically built to avoid, and what breaks if the safeguard is bypassed. Underlying facts are in [scope-semantics.md](scope-semantics.md). This file is the @@ -339,3 +340,42 @@ with a parameter expansion and no `tr` process, **and** strip `\r` the same way captured from any other source before embedding it in a `claude plugin` command or a JSON argument. A `jq() { command jq "$@" | tr -d '\r'; }` wrapper gives the same guarantee at the price of a second process per call. Don't rediscover this the hard way in a second script. + +## `--all` with `install_new: all` is a mass install of every catalog, and nothing warns you + +Observed 2026-09-14 on **Claude Code 2.1.270**, claude-ops 0.56.2. A `sync --all` run under +`install_new: all` installed **2,231** plugins at user scope in about 20 minutes, 1,953 of them +from `claude-community`, before the operator killed it partway through the alphabet. Nothing in the +run asked, and nothing in the report predicted the volume before Step 4 began. + +**The two settings are individually reasonable and catastrophic together.** `install_new: all` +means "install every not-yet-installed catalog plugin." Against the default marketplace, the one +whose fleet the operator already curates, that is the intended convenience: the gap is normally +zero or a handful. Against `--all` it quantifies over *every marketplace in +`known_marketplaces.json`*, so the same word now means "install every plugin published in every +catalog this machine knows about," including large third-party ones nobody curated. The policy is +read once and applied per marketplace; it carries no notion that its blast radius just grew by +three orders of magnitude. + +**Why the existing safeguards do not catch it.** The downgrade guard governs direction, not volume. +The `ask` branch is the only one that enumerates before acting, and `all` exists precisely to skip +it. `audit` would have predicted it, but `audit` is a separate invocation an operator reaching for +`--all` has no particular reason to run first. Step 4's report names what it installed only +*after* installing it. + +**The rule, until the skill enforces a volume gate itself:** treat `--all` combined with a rendered +`install_new` of `all` as requiring an explicit human confirmation that names the number. Resolve +the per-marketplace install gap first (`fleet-state.sh --ids install-gap` per marketplace, or one +`audit --all`), present the total, and proceed only on a yes. An agent running this unattended +should downgrade its own effective policy to `ask` rather than assume the configured `all` was +written with the cross-marketplace case in mind: an operator sets that option while thinking about +the marketplace they maintain. + +**Reverting is exact but slow.** The run journal records one `Successfully installed plugin: ` +line per install, so the revert set is `grep -oE "Successfully installed plugin: [^ ]+"` over +`journal.log`, deduplicated, and it is disjoint from the pre-existing fleet by construction. Verify +that disjointness against the run's own `pre-refresh.*.json` snapshots before uninstalling anything. +`claude plugin uninstall -s user -y` removes both the `installed_plugins.json` record and the +user-scope `enabledPlugins` entry, but it costs ~6s per plugin, so a four-figure revert runs for +hours. There is no bulk uninstall verb; `marketplace remove` is not one (see its own section above, +it also deletes this skill's run journal, which is the only record of what to revert). From b4d369e78f9c40c96f41bf068e8b3bb80458fb77 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:27:27 -0400 Subject: [PATCH 2/4] chore(claude-ops): bump to 0.56.14 for the plugins-skill doc change check-changelog-parity --check-bump refuses to reuse a published version when a change set touches shipped plugin files, documentation included. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XD4Hr2S7RA2G8PR2CadLP1 --- plugins/claude-ops/.claude-plugin/plugin.json | 2 +- plugins/claude-ops/CHANGELOG.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/claude-ops/.claude-plugin/plugin.json b/plugins/claude-ops/.claude-plugin/plugin.json index 85fbc7078..699277718 100644 --- a/plugins/claude-ops/.claude-plugin/plugin.json +++ b/plugins/claude-ops/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "claude-ops", - "version": "0.56.13", + "version": "0.56.14", "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: 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: 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: 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, namely built-in CLI commands, bundled skills, plugin-backed built-ins, and session-provided skills, 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 from the OTEL store, the collector, the per-session hook event log and hook-event JSONL, and ccusage, 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: 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: 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, 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 /sessions/.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. 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", diff --git a/plugins/claude-ops/CHANGELOG.md b/plugins/claude-ops/CHANGELOG.md index e6445863b..1e2d6bb97 100644 --- a/plugins/claude-ops/CHANGELOG.md +++ b/plugins/claude-ops/CHANGELOG.md @@ -3,6 +3,12 @@ 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.56.14] + +### Changed + +- The plugins skill records the `--all` + `install_new: all` mass install. gotchas.md gains a section on why that combination installs every plugin in every known catalog, why the downgrade guard and `audit` both miss it, the `claude --bare` requirement for the revert, and the revert recipe. SKILL.md's marketplace-resolution list now warns that `all` multiplies the install policy across catalogs, and its `install_new` section requires a counted human confirmation before that combination runs. Documentation only; no script, output, or exit code changes. + ## [0.56.13] ### Changed From b0e45a5930b14280b24a29c75484dd08c81b4438 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 18:32:19 -0400 Subject: [PATCH 3/4] fix(claude-ops): correct the preflight commands and add the CLI record MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three review findings, all correct. The counted-confirmation recipe named `fleet-state.sh --ids install-gap`, which is not a selector the script accepts — ids_selector_valid() rejects it and the command exits 2 before producing the count the new rule requires. The selector is missing-user-install, the one sync-run.sh itself projects for Step 4. Fixed in both SKILL.md and gotchas.md, with --marketplace added so the per-marketplace form actually resolves. gotchas.md also wrote `audit --all`; the Action Router takes the marketplace target as the second bare token, so it is `audit all`. SKILL.md already had this right. The revert guidance restated volatile CLI behaviour — uninstall side effects, the absence of a bulk verb, per-plugin timing — with no verification record, which .claude/rules/skill-bodies-state-current-rules.md requires precisely so a recovery procedure cannot silently rot as the CLI moves. Added the four-part record, and with it the --bare requirement that was the actual load-bearing finding of that incident: a plain uninstall spawns a session that loads every still-enabled plugin, so a large revert loop re-executes the set it is removing on every iteration. Basis separates what is from --help, what is direct observation, and which numbers are machine-dependent. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XD4Hr2S7RA2G8PR2CadLP1 --- plugins/claude-ops/skills/plugins/SKILL.md | 3 +- .../skills/plugins/context/gotchas.md | 28 +++++++++++++++++-- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/plugins/claude-ops/skills/plugins/SKILL.md b/plugins/claude-ops/skills/plugins/SKILL.md index 64f2d5280..5591d7b6c 100644 --- a/plugins/claude-ops/skills/plugins/SKILL.md +++ b/plugins/claude-ops/skills/plugins/SKILL.md @@ -281,7 +281,8 @@ machine knows about." One observed run installed 2,231 plugins before it was kil [context/gotchas.md](context/gotchas.md)'s "`--all` with `install_new: all` is a mass install of every catalog". **When the marketplace target is `all` and the rendered policy is `all`, do not proceed unattended:** resolve the total install gap first (one `audit all`, or `fleet-state.sh ---ids install-gap` per marketplace), state the number, and get an explicit human yes. Treat the +--marketplace --ids missing-user-install` per marketplace — that is the selector +`sync-run.sh` itself projects for Step 4), state the number, and get an explicit human yes. Treat the configured value as written with the operator's own marketplace in mind, and downgrade to `ask` when no human is present to receive the count. diff --git a/plugins/claude-ops/skills/plugins/context/gotchas.md b/plugins/claude-ops/skills/plugins/context/gotchas.md index dd6d7801f..e4a3cb301 100644 --- a/plugins/claude-ops/skills/plugins/context/gotchas.md +++ b/plugins/claude-ops/skills/plugins/context/gotchas.md @@ -365,8 +365,9 @@ it. `audit` would have predicted it, but `audit` is a separate invocation an ope **The rule, until the skill enforces a volume gate itself:** treat `--all` combined with a rendered `install_new` of `all` as requiring an explicit human confirmation that names the number. Resolve -the per-marketplace install gap first (`fleet-state.sh --ids install-gap` per marketplace, or one -`audit --all`), present the total, and proceed only on a yes. An agent running this unattended +the per-marketplace install gap first (`fleet-state.sh --marketplace --ids +missing-user-install` per marketplace — the selector `sync-run.sh` itself projects for Step 4 — or +one `audit all`), present the total, and proceed only on a yes. An agent running this unattended should downgrade its own effective policy to `ask` rather than assume the configured `all` was written with the cross-marketplace case in mind: an operator sets that option while thinking about the marketplace they maintain. @@ -379,3 +380,26 @@ that disjointness against the run's own `pre-refresh.*.json` snapshots before un user-scope `enabledPlugins` entry, but it costs ~6s per plugin, so a four-figure revert runs for hours. There is no bulk uninstall verb; `marketplace remove` is not one (see its own section above, it also deletes this skill's run journal, which is the only record of what to revert). + +**A plain `claude plugin uninstall` spawns a session that loads every still-enabled plugin**, so a +revert loop over a large set re-executes the very plugin set it is removing, once per iteration — +including their MCP servers. Run every uninstall as `claude --bare plugin uninstall -s user -y` +instead; `--bare` skips hooks, LSP, plugin sync and the MCP boot, at roughly 15s per uninstall +rather than 6s. Until the revert drains, use `--bare` for unrelated shell work too, and do not run +`/reload-plugins`. + +*Basis:* the absence of a bulk verb is `claude plugin uninstall --help`, whose complete option list +is `-h/--help`, `--json`, `--keep-data`, `--prune`, `-s/--scope` and nothing that takes more than +one ``; the spawn-and-load behaviour and the `--bare` mitigation are direct observation +during the 2026-09-14 revert on this fleet — the host's node/bun process count climbed on every +plain uninstall and stayed flat at 73 across six consecutive `--bare` uninstalls; the ~6s and ~15s +figures are wall-clock from that same run and are machine- and plugin-count-dependent, so treat +them as orders of magnitude rather than constants. Neither the spawn behaviour nor `--bare` is +documented on +[plugins-reference](https://code.claude.com/docs/en/plugins-reference) or +[plugin-marketplaces](https://code.claude.com/docs/en/plugin-marketplaces), which is why this +record exists. *As of* 2026-09-15 on **Claude Code 2.1.272** (win32); the revert itself ran on +2.1.263–2.1.272 with no observed change in the behaviour. ***Recheck trigger:*** a bulk or +glob-accepting form appearing on `claude plugin uninstall --help`; any release note or +`plugins-reference` change touching what a non-interactive `plugin` subcommand loads at startup, or +documenting `--bare`; or an observed uninstall that does not spawn a plugin-loading session. From 2acf3cbca9693d14bca9a30b4660ae4e85e14097 Mon Sep 17 00:00:00 2001 From: Kyle Sexton <153232337+kyle-sexton@users.noreply.github.com> Date: Tue, 15 Sep 2026 20:01:14 -0400 Subject: [PATCH 4/4] style(claude-ops): drop the em dashes from the plugins skill The claude-ops plugin is on the em-dash purge list (scripts/em-dash-purged-paths.txt covers plugins/claude-ops/skills/*/SKILL.md and skills/*/context/*.md), and the preflight and CLI-record edits reintroduced four. Replaced with commas, a parenthetical and a colon; no wording otherwise changed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01XD4Hr2S7RA2G8PR2CadLP1 --- plugins/claude-ops/skills/plugins/SKILL.md | 2 +- plugins/claude-ops/skills/plugins/context/gotchas.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/claude-ops/skills/plugins/SKILL.md b/plugins/claude-ops/skills/plugins/SKILL.md index 5591d7b6c..04ec9ae7e 100644 --- a/plugins/claude-ops/skills/plugins/SKILL.md +++ b/plugins/claude-ops/skills/plugins/SKILL.md @@ -281,7 +281,7 @@ machine knows about." One observed run installed 2,231 plugins before it was kil [context/gotchas.md](context/gotchas.md)'s "`--all` with `install_new: all` is a mass install of every catalog". **When the marketplace target is `all` and the rendered policy is `all`, do not proceed unattended:** resolve the total install gap first (one `audit all`, or `fleet-state.sh ---marketplace --ids missing-user-install` per marketplace — that is the selector +--marketplace --ids missing-user-install` per marketplace, which is the selector `sync-run.sh` itself projects for Step 4), state the number, and get an explicit human yes. Treat the configured value as written with the operator's own marketplace in mind, and downgrade to `ask` when no human is present to receive the count. diff --git a/plugins/claude-ops/skills/plugins/context/gotchas.md b/plugins/claude-ops/skills/plugins/context/gotchas.md index e4a3cb301..eacec92a9 100644 --- a/plugins/claude-ops/skills/plugins/context/gotchas.md +++ b/plugins/claude-ops/skills/plugins/context/gotchas.md @@ -366,7 +366,7 @@ it. `audit` would have predicted it, but `audit` is a separate invocation an ope **The rule, until the skill enforces a volume gate itself:** treat `--all` combined with a rendered `install_new` of `all` as requiring an explicit human confirmation that names the number. Resolve the per-marketplace install gap first (`fleet-state.sh --marketplace --ids -missing-user-install` per marketplace — the selector `sync-run.sh` itself projects for Step 4 — or +missing-user-install` per marketplace (the selector `sync-run.sh` itself projects for Step 4), or one `audit all`), present the total, and proceed only on a yes. An agent running this unattended should downgrade its own effective policy to `ask` rather than assume the configured `all` was written with the cross-marketplace case in mind: an operator sets that option while thinking about @@ -382,7 +382,7 @@ hours. There is no bulk uninstall verb; `marketplace remove` is not one (see its it also deletes this skill's run journal, which is the only record of what to revert). **A plain `claude plugin uninstall` spawns a session that loads every still-enabled plugin**, so a -revert loop over a large set re-executes the very plugin set it is removing, once per iteration — +revert loop over a large set re-executes the very plugin set it is removing, once per iteration, including their MCP servers. Run every uninstall as `claude --bare plugin uninstall -s user -y` instead; `--bare` skips hooks, LSP, plugin sync and the MCP boot, at roughly 15s per uninstall rather than 6s. Until the revert drains, use `--bare` for unrelated shell work too, and do not run @@ -391,7 +391,7 @@ rather than 6s. Until the revert drains, use `--bare` for unrelated shell work t *Basis:* the absence of a bulk verb is `claude plugin uninstall --help`, whose complete option list is `-h/--help`, `--json`, `--keep-data`, `--prune`, `-s/--scope` and nothing that takes more than one ``; the spawn-and-load behaviour and the `--bare` mitigation are direct observation -during the 2026-09-14 revert on this fleet — the host's node/bun process count climbed on every +during the 2026-09-14 revert on this fleet: the host's node/bun process count climbed on every plain uninstall and stayed flat at 73 across six consecutive `--bare` uninstalls; the ~6s and ~15s figures are wall-clock from that same run and are machine- and plugin-count-dependent, so treat them as orders of magnitude rather than constants. Neither the spawn behaviour nor `--bare` is