Skip to content

fix(claude-ops): remediate the plugins:sync audit findings (0.36.0) - #3148

Merged
kyle-sexton merged 1 commit into
mainfrom
fix/claude-ops-plugins-audit-remediation
Aug 23, 2026
Merged

fix(claude-ops): remediate the plugins:sync audit findings (0.36.0)#3148
kyle-sexton merged 1 commit into
mainfrom
fix/claude-ops-plugins-audit-remediation

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Closes #3112

Summary

Remediates the post-use plugin-quality audit of claude-ops:plugins' sync action — all 15 findings, plus a catalog-version pre-filter worth more than the rest combined. Every harness-behaviour claim added or changed here is version-stamped against Claude Code 2.1.240, the CLI it was verified on.

Three silent-failure paths closed, all of the same shape: correct-looking output from a path that did not run.

  • F1 (high) — Step 2, the self-described "primary value path", no-opped invisibly. fleet-state.sh computed currentProject as a correct tri-state, but null covers "user-scope record", "no projectPath", and "no project context resolved at all", so a run from $HOME and a run inside a repo with no in-repo installs produced an identical report. New top-level project_root carries the distinction; the report gains a fixed In-repo: row that must state a skip.
  • F4 (medium-high)sync all refreshed every marketplace, then ran install/update/enable maintenance against exactly one. Steps 2–5 are now the per-marketplace loop body, every --ids call carries --marketplace, and a new --marketplaces mode enumerates the names CR-free so the loop needs no hand-written jq.
  • F2 (high) — every actionable divergence routed to a converge command that cannot execute, because -s project has no path flag and the recorded projectPath no longer exists. Those rows now get their own report section, outside the Divergences count, and converge emits them as blocked rather than runnable.

Design decision on F2, which the brief left to me: stale project records are classified and reported, never converged and never reaped. No claude plugin verb removes an install record by path, editing installed_plugins.json is outside this skill's boundary, and claude-ops must not read another plugin's userConfig to decide whose directories these were — so the wording is deliberately generic about "a tool that owns those directories' lifecycle" rather than naming one. projectPathPresent is advisory and may never filter: an unmounted volume, an offline share, and unplugged removable media are indistinguishable from a deleted worktree to a directory test, so the report says "not present on this machine", never "dead".

Fix

Catalog-version pre-filter (the performance finding). marketplace.json entries carry no version — which is why Step 3 called claude plugin update for every user-scope install and let the CLI no-op. Each plugin's version does exist in the marketplace checkout at <installLocation>/<entry.source>/.claude-plugin/plugin.json. fleet-state.sh now exposes it as catalog_versions, and --ids update-candidates-user withholds only ids it positively proved already sit at the catalog version. On this machine's already-current fleet: 66 update calls → 0.

It fails open by construction, and that is the dominant path, not an edge case. Measured across nine registered marketplaces: resolves fully for five, partially for two (13/53, 1/15), not at all for two. Any unreadable version yields a candidate, exactly as if no pre-filter existed. The test asserts the degradation as a byte-equality against installed-user, not a spot check. The pre-filter is also disabled — falling back to the unfiltered installed-user — for any marketplace whose Step 1 refresh failed, since a stale checkout cannot prove an id current; and audit mode, which never refreshes, reports its prediction as a lower bound carrying the catalog's lastUpdated.

The manifest must sit inside the checkout, enforced physically. source is third-party content and the only unsafe direction is withholding an update. A lexical ../ refusal is insufficient — a symlink inside the checkout is reached by an ordinary ./name source no string check can see — so the resolved manifest path is required to sit under the resolved checkout root with symlinks followed.

Also fixed: pluginConfigs read-path claim (user / --settings / managed only; project and local ignored since v2.1.207, while enabledPlugins still honors them); /reload-plugins --force restated as the docs' two-step with prompt-cache invalidation as the trigger; divergence count split into run-caused vs pre-existing; self-update row when the sweep updates claude-ops itself; TOCTOU wording matched to the implementation with the inert detector replaced; versionsMatch reduced to one origin plus pointers; user_scope_orphans for the structurally-invisible single-scope orphans.

setup — now load-bearing, not tidiness. #3115 regenerated this plugin's README with the corrected reconfiguration guidance while skills/setup/SKILL.md still prescribed the destructive uninstall/reinstall cycle — a contradiction live on main that #3115's own reviewer flagged and could not fix, because that file is in this change set's fence. This PR closes it, matching the landed fleet-wide wording verbatim apart from this plugin's option list, including the distinction that the write lands while the running session's behavior does not (${user_config.*} is injected at skill load and CLAUDE_PLUGIN_OPTION_* comes from an environment fixed at session start, so a same-session check still reports the old value and reading that as a failed write is wrong). Also corrects an inherited miscount: 15 options and 8 *_audit_enabled toggles, not 14 and 7.

Deferred, with reasons in the CHANGELOG: --run-log in fleet-state.sh (conflicts with the script's read-only contract; F12's cheapest tier landed instead), a fourth install_new value (needs durable state), F8's --selfcheck, F14 (the repo's own check-changed-skills.sh enforces trigger-keyword preservation, and the finding is cosmetic since the skill sets disable-model-invocation: true), and the upstream issue for the missing record-reaping verb.

Verification

Two independent fresh-context verifier agents reviewed the final state against the sealed audit packet. Both initially returned FAIL; both sets of defects were fixed and re-verified.

Round 1 found the change had made Step 3 catalog-dependent without extending the stale-catalog deferral rule — a stale checkout could withhold an id as "already current". Fixed at the decision point, plus three unstamped claims and an over-flattering CHANGELOG figure.

Round 2 found the symlink-containment hole above, and that audit mode never triggers the stale-catalog fallback. Both fixed.

Worth recording: the symlink regression test initially failed for the wrong reason — Git Bash's ln -s silently deep-copies instead of linking, so the fixture was asserting against a real in-checkout directory and 9.9.9 was the correct answer for what was on disk. The test now creates a genuine symlink via MSYS=winsymlinks:nativestrict, gates on [[ -L ]], and skips honestly where the platform yields no real symlink. Verified against real symlinks from both ln -s and cmd mklink /D.

Test plan

  • fleet-state.test.sh: 45 → 73 cases, 0 failed, covering every behaviour change — catalog_versions and all four fail-open inputs, the fail-open byte-equality proof, update-candidates-user (equal/behind/ahead/project-scope/partial), symlink containment plus an in-checkout control, the production installLocation branch (which bypasses the fixture override), projectPathPresent including the false-is-not-null guard and the never-filters guarantee, user_scope_orphans, project_root (including that every --all block carries it), --marketplaces (enumeration, empty, standalone-flag rejection order-independence, and a CR regression under a CRLF-emitting jq stub), and selector help-text drift across all six selectors.
  • check-changed-skills.sh — 2 skills, 0 errors. validate-plugin-contracts.mjs — 2813 files. check-shell-portability.sh, check-skill-portability.sh, check-evals-quality.sh, check-changelog-parity.sh (--check, --check-order, --check-bump, --check-preserved), shellcheck, typos, markdownlint-cli2, check-fixture-git-isolation.sh, claude plugin validate, catalog/cheatsheet/plugin-options sync — all pass.
  • Live read-only smoke independently reproduced the audit's own numbers before any test was written: user_scope_orphans returned exactly the four ids the corrected packet names, and all 45 project-scope records came back projectPathPresent: false.

Related

🤖 Generated with Claude Code

@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 2s —— View job


Security review in progress

  • Gather context (PR diff, changed files)
  • Determine security-relevant surface
  • Hunt for vulnerabilities (logic flaws, injection, trust boundaries)
  • Adversarial validation of candidate findings
  • Post final review

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown

Last security-reviewed head: 17a7ddcd17f8e3083a68aa4405d82402a8a508d3. On the next push, the relevance gate compares only the commits since this SHA; delete this comment to force a full re-review.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5fc18ad77a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/claude-ops/skills/plugins/scripts/fleet-state.sh Outdated
Comment thread plugins/claude-ops/skills/plugins/context/sync.md Outdated
@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 5m 7s —— View job


Code review complete

  • Gather context (PR diff, CLAUDE.md, REVIEW.md)
  • Check skip gate (draft/closed/trivial/already reviewed) — none apply
  • Review changed files for correctness & maintainability
  • Adversarially verify candidate findings
  • Post inline comments for anchorable findings
  • Post final summary

This repo carries a claude-security-review.yml workflow, so security findings are scoped to that lane and out of this review.

Scope reviewed: all 11 changed files — fleet-state.sh, fleet-state.test.sh, SKILL.md, converge.md, gotchas.md, scope-semantics.md, sync.md, evals.json, setup/SKILL.md, CHANGELOG.md, plugin.json.

Findings: 1 nit, posted inline.

  • 🟡 skills/setup/SKILL.md#L75-79 and CHANGELOG.md#L86 — both restate "fourteen" declared options / "seven *_audit_enabled toggles" when plugin.json's userConfig block actually declares 15 options, 8 of them *_audit_enabled (hook_failure_audit_enabled is the one omitted from both counts and from the explicit enumeration). This claim predates this PR, but the PR rewrites both paragraphs with more confidence/detail while carrying the miscount forward — flagged since it's on lines this PR touches and the PR's whole premise is precision of stamped claims.

What held up well under review (no issues found):

  • fleet-state.sh's new catalog_versions fail-open pre-filter, including the two-gate symlink-containment defense (manifest_is_contained + the lexical ../ pre-check) — traced the logic against hook::physical_path/hook::normalize_path and the string-concatenation path-join (no absolute-path-swallowing risk since it's plain shell concatenation, not an os.path.join-style function).
  • The new projectRoot, user_scope_orphans, and projectPathPresent fields and their --ids selectors — traced field derivation against the 68-case test suite; jq programs, // vs == null-handling (esp. projectPathPresent's deliberate avoidance of // to preserve false), and CR-stripping wrapper usage all check out.
  • Cross-references between sync.md, converge.md, scope-semantics.md, and gotchas.md for the F1/F2/F4 fixes (per-marketplace loop scoping, stale-project-record classification, pluginConfigs scope correction, /reload-plugins --force two-step) are internally consistent — no contradictions found between the spoke files or against SKILL.md's hub content.
  • New test cases (catalog-version fail-open equality proof, symlink escape + control, production installLocation branch, selector help-text drift) exercise the claimed behavior correctly, including the documented Git-Bash ln -s deep-copy pitfall workaround.

Not independently verified (author-claimed only, noted per this lane's rules): the live-machine numbers in the PR description/CHANGELOG (66→0 update calls, the 9-marketplace resolution breakdown, the 2.1.240 CLI probes) and the test-run/lint-tool pass counts in the Test plan — I did not have a sandboxed way to execute fleet-state.test.sh or the other listed checks in this environment, so I read the script and tests statically rather than running them.

Comment thread plugins/claude-ops/skills/setup/SKILL.md
Comment thread plugins/claude-ops/CHANGELOG.md Outdated
@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 2 times. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@kyle-sexton
kyle-sexton force-pushed the fix/claude-ops-plugins-audit-remediation branch from 5fc18ad to 94d8869 Compare August 23, 2026 04:48
@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 12s —— View job


Security review complete

  • Gather context (PR diff, custom instructions to run /review:security-review)
  • Check skip gate — PR is open; not skipped (head SHA changed since last review)
  • Determine security-relevant surface — fleet-state.sh (shell logic, path/symlink handling) and sync.md/converge.md (agent-facing command templates)
  • Hunt for vulnerabilities (logic flaws, injection, trust boundaries)
  • Adversarial validation of candidate findings
  • Post final review

Per this lane's scope (/review:security-review), this repo has a dedicated claude-security-review.yml workflow — findings are limited to security issues only (logic flaws, injection, trust-boundary gaps), not style/quality (that's /review:code-review, already run on this head).

Findings: none.

Reviewed the security-relevant surface introduced/changed in this PR at 94d8869:

  • fleet-state.sh's new catalog_versions symlink-containment defense (manifest_is_contained, called at L603) — this is the highest-risk new code (resolves a path built from third-party marketplace.json source content). Traced both gates: the lexical ../ pre-check (with backslash folding) and the physical containment check that resolves symlinks via hook::physical_path/hook::normalize_path and requires a strict "$root"/* prefix match. Confirmed it fails closed on containment (any unresolved path → not contained) and the caller correctly treats that as fail-open on the update decision (null version → stays an update candidate) — the one safe direction, since withholding a real update is the only unsafe outcome this pre-filter can cause. The regression test at fleet-state.test.sh:1627-1664 exercises a real symlink escaping the checkout (with an honest platform-capability skip) plus an in-checkout control, and both assert the correct outcome.
  • String-built paths and jq argument passingcv_manifest, manifest_base, and all catalog/id values reach jq exclusively via --arg/--slurpfile/<<<, never string-interpolated into a jq program or a shell command; no injection surface there.
  • Command templates in sync.md/converge.md — every <id> driving a claude plugin … call is quoted ("$id"), sourced only from fleet-state.sh's own CR-stripped, TAB-delimited output (never a hand-rolled jq | while read the docs explicitly warn against for the same CRLF-corruption reason). converge.md's -s project/-s local mutations are gated on projectPathPresent before ever emitting a cd "<projectPath>" command, and target each row's own recorded path rather than the bare/current-directory form.
  • No GitHub Actions, permissions, secrets, or dependency-pin changes in this PR's file set — the diff is a shell script, its test suite, and skill markdown, so none of that surface applies here.

Consistent with the prior code-review pass on this head and the security scan at the prior head 5fc18ad, which reached the same conclusion on this same logic.

@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Code review in progress

  • Gather context (PR diff, CLAUDE.md, skill instructions)
  • Check skip gate (draft/closed/trivial/already reviewed) — none apply; head advanced since the last review
  • Review changed files for correctness & maintainability
  • Adversarially verify candidate findings (3 independent verifier agents, each CONFIRMED)
  • Post inline comments for anchorable findings
  • Post final summary

View job run

Comment thread plugins/claude-ops/skills/plugins/scripts/fleet-state.sh Outdated
Comment thread plugins/claude-ops/skills/plugins/context/sync.md Outdated
Comment thread plugins/claude-ops/skills/plugins/context/gotchas.md Outdated
Closes the post-use audit of `claude-ops:plugins`' `sync` action. Every
harness-behaviour claim added or changed here was verified on Claude Code
2.1.240.

Three silent-failure paths closed:

- Step 2 no-opped invisibly. `fleet-state.sh` computes `currentProject` as a
  correct tri-state, but `null` covers "user-scope record", "no projectPath",
  and "no project context resolved at all" alike, so a run from $HOME and a run
  inside a repo with no in-repo installs produced an identical report. A new
  top-level `project_root` field carries the distinction and the report gains a
  fixed `In-repo:` row that must state a skip.
- `sync all` refreshed every marketplace, then ran install/update/enable
  maintenance against exactly one. Steps 2-5 are now the per-marketplace loop
  body, every `--ids` call carries `--marketplace`, and a new `--marketplaces`
  mode enumerates the names CR-free so the loop needs no hand-written jq.
- Actionable divergences routed to a `converge` command that cannot execute,
  because `-s project` has no path flag and the recorded `projectPath` no longer
  exists. Those rows now get their own report section outside the Divergences
  count, and `converge` emits them as blocked rather than as runnable commands.

Adds a catalog-version pre-filter for the Step 3 sweep. Each plugin's version
lives in the marketplace checkout even though the catalog entry carries none, so
`--ids update-candidates-user` can withhold ids it proved are already current --
0 update calls instead of 66 on an already-current fleet. It fails open by
construction: an unreadable catalog version yields a candidate, and the test
suite proves the degradation as a byte-equality against `installed-user`.

The manifest it reads must sit inside the marketplace checkout, enforced
physically rather than lexically: `source` is third-party content, withholding
an update is the only unsafe direction, and a symlink inside the checkout is
reached by an ordinary `./name` source no string check can see.

Also corrects the `pluginConfigs` scope claim (user/--settings/managed only;
project and local ignored since v2.1.207, while `enabledPlugins` still honors
them), restates the `/reload-plugins --force` trigger as prompt-cache
invalidation with the docs' two-step recovery, splits the divergence count into
run-caused vs pre-existing, and reduces the `versionsMatch` rule to one origin.

`setup`: the headless `--config` route no longer prescribes an
uninstall/reinstall cycle, and separates the write from the running session's
behavior (a same-session `check` still reports the old value). Wording matches
the fleet-wide correction landed in #3115 verbatim apart from this plugin's own
option list, closing a live contradiction on main where the regenerated README
carried the corrected guidance while this skill still prescribed the
destructive cycle.

fleet-state.test.sh: 45 -> 73 cases, covering each behaviour change.

Two mechanisms adopted from the concurrent duplicate implementation in #3145,
which an autonomous work-items lane opened against the same issue: the
`--marketplaces` enumeration mode, which closes a real gap here (this branch
told the model to loop per marketplace without giving it a CR-safe way to list
them), and the snake_case `project_root` spelling, which matches the existing
top-level key convention where this branch had used camelCase. Its `stale-user`
selector name and `plugins/<name>` layout fallback were deliberately not taken:
the former reads as an authoritative stale list rather than a candidate
superset, and the latter guesses a layout the catalog did not declare, which can
withhold on an unverified assumption.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kyle-sexton
kyle-sexton force-pushed the fix/claude-ops-plugins-audit-remediation branch from 94d8869 to 17a7ddc Compare August 23, 2026 05:45
@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 2s —— View job


Security review complete

  • Gather context (PR diff via gh pr diff, custom instructions to run /review:security-review)
  • Check skip gate — PR is open; head advanced to 17a7ddc since the last completed security pass (the most recent review attempt at a later SHA hit an infrastructure failure and never completed) — not skipped
  • Determine security-relevant surface — fleet-state.sh (shell logic, path/symlink handling) and sync.md/converge.md (agent-facing command templates)
  • Hunt for vulnerabilities (logic flaws, injection, trust boundaries)
  • Adversarial validation of candidate findings
  • Post final review

Per this lane's scope (/review:security-review), this repo has a dedicated claude-security-review.yml workflow — findings are limited to security issues only (logic flaws, injection, trust-boundary gaps), not style/quality (that's /review:code-review, already run separately on this head).

Findings: none.

Reviewed the full PR diff (origin/main17a7ddc) with focus on the security-relevant surface:

  • fleet-state.sh's catalog_versions symlink-containment defense (manifest_is_contained, called at L606) — this remains the highest-risk new code, since it resolves a filesystem path built from third-party marketplace.json source content and uses the result to decide whether to withhold a plugin update. Confirmed:
    • The lexical ../ pre-check folds backslashes before matching, so both ../foo and ..\foo spellings are caught.
    • The physical gate resolves the candidate manifest path with symlinks followed (hook::physical_path) and requires a strict "$root"/* prefix match against the resolved, normalized checkout root — closing the gap a lexical-only check would miss (a symlink inside the checkout pointing outside it).
    • manifest_is_contained fails closed on containment (any unresolved path → not contained), and the caller correctly treats that as fail-open on the update decision (unresolved/null version → id stays an update candidate). The only unsafe direction for this pre-filter is wrongly withholding a real update, and both gates land on the safe side of that.
    • The regression suite (fleet-state.test.sh) exercises this with a real symlink escaping the checkout (created via MSYS=winsymlinks:nativestrict / gated on [[ -L ]] to avoid Git Bash's silent copy-instead-of-link behavior), a Windows-backslash traversal spelling, and an in-checkout control — all assert the correct outcome.
  • jq argument passing — all values that flow from marketplace/catalog content into jq (cv_manifest, manifest_base, ids, etc.) go through --arg/--slurpfile/<<<, never interpolated into a jq program string or a shell command — no injection surface.
  • Command templates in sync.md/converge.md — every id driving a claude plugin … invocation is quoted and sourced only from fleet-state.sh's own CR-stripped, tab-delimited --ids output (the docs explicitly warn against a hand-rolled jq | while read, which would corrupt ids with embedded \r on Windows). converge.md's new blocked-row handling for absent projectPath rows means a (cd "<projectPath>" && …) command is never emitted for a path that can't be cd'd into.
  • No GitHub Actions, permissions, secrets, or dependency-pin changes in this PR's file set — the diff is a shell script, its test suite, and skill/changelog markdown, so that surface doesn't apply here.

This is consistent with the prior security passes on earlier heads of this PR, which reached the same conclusion on this same logic; the code in the security-relevant sections (manifest_is_contained and its call site) is unchanged in substance since then.

@kyle-sexton
kyle-sexton merged commit 6e8c97f into main Aug 23, 2026
50 checks passed
@kyle-sexton
kyle-sexton deleted the fix/claude-ops-plugins-audit-remediation branch August 23, 2026 06:20
@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 37s —— View job


Code review skipped

  • Gather context (PR diff, CLAUDE.md, /review:code-review skill instructions)
  • Check skip gate — PR is merged (closed, not open); skip gate condition 1 applies
  • Post final summary

/review:code-review's skip gate says to stop early — without posting further review content — when "PR is closed or not open." PR #3148 was already merged into main (as 6e8c97f, matching this PR's title) before this run started, so there's no open PR state left to review against, and no further findings are being posted for this trigger.

kyle-sexton added a commit that referenced this pull request Aug 23, 2026
Closes #3182

## Summary

Post-merge verification of #3139 (merged as `ef4d53959`) found three
defects in the prose it shipped, each the same class that PR existed to
correct: **a claim or rule reaching past what backs it.** They were
filed rather than quietly patched because the content was already on
`main`.

This fixes those three plus the smaller items #3182 lists, in 46 added
lines across three files.

## Fix

**1. The fetch prohibition's stated cause did not entail its stated
rule.** `PLUGIN-PHILOSOPHY.md` sanctioned "a documentation URL", then
condemned any skill instructed to fetch it as having "made the publisher
a runtime dependency". Fetching `code.claude.com` creates no such
dependency, and a practice already shipping in the tree was condemned by
it.

The prohibition now turns on the target's owner and reaches
publisher-owned targets only. For those targets, distinguishing an
instruction to fetch from a citation offered for a reader is genuinely
hard, and the statement says so rather than implying it has been settled
— `plugins/architecture/reference/topic-docs.md` is named as the open
case, and no ticket owns it (#3136 is enforcement-site consolidation,
not this).

**2. The `evidence-bearing` bullet was unsatisfiable as worded.** It
required setup to report "the effective value it observed", while the
same section pins *effective value* to running-session behaviour and
directs verification to a fresh session. A same-session run can only
observe the **stored** value. One word: `effective` → `stored`.

**3. A narrowing presented as a faithful clarification.** The
hook-plugin eval skip stated its rationale as "no model-facing skill at
all" where the prior text said "no model-**invoked** skill". Neither
works: a `setup` skill sets `disable-model-invocation: true`, so either
phrasing is satisfied by a plugin that ships one — admitting as skips
exactly the plugins the rest of the rule excludes. The defect was
stating the condition in terms of invocation mode at all. It now reads
**"no skill carrying a judgment-bearing contract"**, the test the
warrant rule two sentences above already uses. Outcome unchanged: 19
hook plugins ship a setup skill, all 19 carry setup evals.

**Smaller items.** Both paired reconfiguration sites in
`MIGRATION-PLAYBOOK.md` now name the readback location and agree in
substance, including the sensitive-value limit — an asymmetry between
them would have sent a reader reconfiguring a sensitive option at
project scope to look in user settings, find nothing, and report a
failed write, which is the false failure `PLUGIN-PHILOSOPHY.md` exists
to prevent. Their provenance cites seam 1, which documents both halves,
rather than smoke-test C, which explicitly disclaims covering a
sensitive option. The "step 3 above" cross-reference — which pointed
from inside Reintegration's step 1 at Reintegration's own step 3, about
verify-before-retiring — is replaced by a direct citation of seam 1. The
`github.test.sh` sweep's wider/narrower axes are named, and "both steps
of the same job" is corrected to "each running in its own step". The
workflow header's gate description is corrected: the pinned reusable
requires four sections, not a closing keyword plus `## Related`.

## Verification

Local gates at the final commit: `markdownlint-cli2` 0 issues;
`check-contract-clause-coverage.py` exit 0; `lychee --offline` 0 errors
across 103 unique links; `zizmor` no findings. The workflow change is
comment-only, confirmed by diff.

Six fresh-context verification passes, each given the bounded criteria
plus an unbounded criterion instructing it to hunt for claims reaching
past their evidence anywhere in the touched paragraphs. **All six
returned FAIL**, and each round's fixes introduced at least one new
instance of the defect being repaired.

The sixth pass found two, both in the single paragraph this PR had to
*write* rather than cut, and both repairs were deletions: a tracker-wide
"no ticket owns that question" that the tracker contradicts (#432
carries an accepted ruling on it, and
`scripts/skill-portability-tokens.txt` stages a lint class blocked on
that ruling), and a hedge that denied the statement its own preceding
clause had just made. Every deletion from the prior round verified clean
against the tree.

Reviewing where the findings came from settled the approach. Items 2 and
3 were clean from round three onward; essentially every finding from
round two on landed in material added *beyond* what #3182 asked for — an
enumeration of nonconforming instances, a paragraph grounding the
prohibition against the tree, a rewritten security rationale, a
sensitive-value carve-out. Each was written to close the previous
round's finding and opened one or two of its own. The final revision
deletes those elaborations rather than repairing them again, which is
why the diff is 46 lines rather than the 2,926 it peaked at.

Twenty-five instances of the defect class were found across the five
rounds. One was caught by the author re-reading their own writing, four
by the review bots, and the rest by fresh-context verification. **None
by self-review.**

Two things generalise. A verifier is bounded by its criteria, so a
defect nobody names survives any number of green passes — every round's
findings came from the unbounded criterion, not the checklist. And
under-claiming is not the safe direction: round four's findings were
mostly repairs to what round three's *removals* broke. Both directions
are the same failure to say exactly what the evidence supports.

One pre-existing defect is deliberately left alone and filed as #3184:
the workflow header's security rationale ("reads PR body metadata from
the event payload only") is false against the pinned reusable, which
live-refetches. It is outside #3182's scope, the `zizmor` suppression it
backs is independently sound, and three separate rewrites of that
comment block each introduced a new inaccuracy.

## Related

- #3182 — the issue this closes; its line references were verified
against `origin/main` at `ef4d53959`.
- #3139 — introduced this prose; its own post-merge verification found
these defects and filed them rather than patching silently.
- #3184 — the workflow-comment defects this PR deliberately did not
rewrite.
- #3173 — shipped part 1 into the same files; item 3's prior wording is
its text.
- #3136 — enforcement-site consolidation; it does not own the
cite-versus-fetch question, and this PR no longer claims it does.
- #3115 / #3116 / #3148 — the rest of the campaign whose doc half #3139
was.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(claude-ops): remediate the plugins:sync audit findings

1 participant