Skip to content

fix(docs): establish a verbatim fetch route so a truncated env-vars read stops reading as drift - #2185

Merged
kyle-sexton merged 4 commits into
mainfrom
gap/envvars
Aug 11, 2026
Merged

fix(docs): establish a verbatim fetch route so a truncated env-vars read stops reading as drift#2185
kyle-sexton merged 4 commits into
mainfrom
gap/envvars

Conversation

@kyle-sexton

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

Copy link
Copy Markdown
Contributor

No linked issue

Summary

https://code.claude.com/docs/en/env-vars could not be read whole. Three independent fetches
truncated before the CLAUDE_CODE_MAX_* range, and the summarizer then reported those rows
absent — a false negative indistinguishable from real drift (#2182).
This repo cites env-vars in ~13 places, so that defect was standing, silent, and reproducible on
demand: every future claim about the page had the same failure waiting in it.

This is a capability gap, not a correctness one. The fix is a route that reads the page verbatim,
documented where the concern already lives, plus a re-derivation of every env-vars claim in the
repo from that read.

Fix

The route, verified empirically

curl the raw-markdown channel (https://code.claude.com/docs/en/<slug>.md) to a file and search
the file. Against env-vars on 2026-08-10:

Evidence Value
Status / type 200, text/markdown; charset=utf-8
Size 361,797 bytes, 458 lines, 315 variable rows
The range that truncated away 3× present — CLAUDE_CODE_MAX_* at lines 278–286
Determinism two fetches seconds apart, identical SHA-256 43a805b4cfffd9aae5e36cec42f3a271dc92ddead26db76cd401d61ff4048584
Per-page content date none — Last-Modified: Mon, 10 Aug 2026 23:53:10 GMT equals Date, independently re-confirming the header finding already recorded at docs/conventions/upstream-drift/README.md §Drift signal

The route is hoisted, not invented — from two surfaces that derived it independently.
plugins/claude-ops/skills/changelog/context/read-actions.md carried it page-scoped: "curl the
.md and slice locally … Never report a version 'absent from the changelog' on a truncated fetch".
/knowledge:docpage-digest's Anthropic publisher profile carried it claim-scoped, binding any
absence-establishing fetch to curl on the raw .md channel with a recorded length, on the
asymmetry that "a truncated fetch cannot fabricate a PRESENCE, only an ABSENCE" — written after two
of its own runs asserted a false absence exactly this way. Two independent derivations of one rule
is the signal it wants an owner. Per the convention registry's one-owner-per-concern rule the
general form belongs in upstream-drift; both surfaces keep their scope-specific detail, and
nothing is copied in either direction. Same move #2182 made with the mirror, one level up.

The convention (upstream-drift 1.1.0 → 1.2.0)

New §Reading the basis — the fetch route. Additive guidance only: no required part, canonical
name, or enforceability verdict changed.

  • Two rules bind every read, every rung. No verbatim quote, no claim. And a truncated read
    supports no absence claim, ever
    — "not in the response" is never "not on the page", because the
    reader cannot tell those apart. That is the whole failure being closed: a summarizing fetch does
    not merely fail, it manufactures an absence that reads exactly like a real one.
  • Three rungs. 1 — curl the .md channel (default). 2 — a summarizing fetch, admissible only
    when the read shows the page arrived whole. 3 — a verbatim mirror, one rung below primary, and
    the record says so
    , carrying fix(discipline): close the open concurrency-variable currency question in sweep-all #2182's freshness-corroboration step generalized: corroborate
    against a fact the page's own content can only carry after a known upstream change, never
    against the mirror's self-reported sync time.
  • The .md channel is per-page. docpage-digest's existing warning — a raw-markdown channel
    that works for one doc can 404 for another — is carried across as the reason a run verifies the
    channel before trusting the rung.
  • Currency of a rung-1 read is capped at the fetch date, because upstream publishes no per-page
    content date. Anything stronger is the overclaim the doc's own first rule forbids.

The claims — 2 drifted, 1 citation rotted, 1 basis retired

claude-config 0.29.0 → 0.29.1

  1. audit's MANDATORY env-var check instructed auditors to do the exact thing that fabricates
    findings.
    Category F said fetch the page and search it, calling it "the authoritative source",
    with no word on how to read it. A summarizing fetch truncates 315 rows and reports the rest
    absent — so an auditor following the row as written could flag a valid variable as unrecognized
    and never know. Now routes through the .md route and states that a truncated read supports no
    finding at all. This is the capability gap made concrete, and it was live guidance.

  2. The same row invited the inverse error. "Authoritative source" + "do not flag as unrecognized
    without checking this page" reads as absent here means not real. It is not:
    CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_ENHANCED_TELEMETRY_BETA, and
    CLAUDE_CODE_EXPERIMENTAL_OBSERVER_AGENTS are each cited as real in this repo and each absent
    from the full verbatim read. The row now caps the verdict at "not documented on env-vars" and
    names the sibling pages to check.

  3. audit-pass carried DISABLE_DOCTOR_COMMAND as undocumented. It is documented — and
    describes that very skill:

    DISABLE_DOCTOR_COMMAND | Set to 1 to hide the /doctor
    setup checkup skill and its /checkup alias. Useful for managed deployments where users
    shouldn't run setup diagnostics from a session. Doesn't affect the claude doctor terminal
    command. Before v2.1.205, this variable hid the /doctor diagnostics screen command

    It moves into the verified list with the scope the row actually draws (session skill, not the
    terminal command), and independently corroborates the v2.1.205 cutover the same section already
    states. The skillOverrides half is untouched and still says UNVERIFIED — this run re-derived
    the env-vars basis only, and the trigger now names the settings fetch that would retire it.

  4. audit-instructions' effort-audit reading list promised a release the page does not state. It
    sent auditors to env-vars for CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING "with the models and
    release
    it reaches". The row states the models — "Has no effect on Fable 5, Sonnet 5, or Opus
    4.7 and later" — and no release at all. Telling a reader to go find something that is not there
    invites them to invent it. Corrected, and routed through the fetch route for the same reason.

discipline 0.12.2 → 0.12.3 — a trigger written yesterday fired today. #2182 sourced
CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY from a mirror and wrote its own retirement condition: "any
env-vars fetch that reaches the CLAUDE_CODE_MAX_* range, which retires the mirror basis for a
primary one."
The rung-1 fetch reached it. The row is unchanged

CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY | Maximum number of read-only tools and subagents that can
execute in parallel (default: 10). Higher values increase parallelism but consume more resources

— so no value moves; what changes is the citation's standing, mirror → primary. Four more rows the
preflight leans on were read in the same fetch and each matches how it cites them:
CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION ("Removed in v2.1.224 and now a no-op … Previously capped …
(default: 200)"), CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS ("default: 20"),
CLAUDE_CODE_DISABLE_BACKGROUND_TASKS ("the run_in_background parameter on Bash and subagent
tools"), CLAUDE_CODE_FORK_SUBAGENT ("overriding any server-side rollout").

knowledge 0.11.0 → 0.11.1 — a line-number citation into a live page had rotted. (Its second
change is a one-sentence provenance pointer: the profile's absence-fetch rule is named as rung 1 of
the route it was generalized from. The rule and its asymmetry are unchanged and stay where they
are.) The Anthropic
profile recorded its sole attested api-only near-miss sub-shape (3) as env-vars.md:394. On the
verbatim read, line 394 is DISABLE_UPGRADE_COMMAND. The row the instance actually describes is
FALLBACK_FOR_ALL_PRIMARY_MODELS — the only row on the page that both describes Claude Code's own
retry behavior and names a model subject ("models Claude Code recognizes as Opus, Fable 5, or
Mythos models stop retrying this way"); the sibling CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK
names none, so it is excluded. The attestation stands; only its address moved. Cited by variable
name now, with a standing rule: cite a live docs page by anchor or row key, never by line number.
Line numbers into an archived snapshot stay citable — that file is immutable, which is what makes
them citable.

playbooks 0.8.2 → 0.8.3 — verified current, with one qualifier honestly downgraded.
sonnet-5.md's harness-side thinking facts re-verify: MAX_THINKING_TOKENS still carries the
Anthropic-API-vs-third-party split, the Fable 5 exception, and the nonzero-ignored rule, and
CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING now states the file's central claim outright — "Has no
effect on Fable 5, Sonnet 5, or Opus 4.7 and later". The "from Claude Code v2.1.111" attribution did
not re-verify: the page states no release for that variable. Flagged in place rather than
deleted or quietly kept — uncontradicted and immaterial, but a reader is owed the difference between
a claim re-read today and one carried forward.

docs/OFFICIAL-DOCS.md — the env-vars row's stamp moves 2026-08-06 → 2026-08-10, and the
staleness warning now points at the fetch route, because this file's whole job is carrying per-page
verification dates. Other no-drift stamps were deliberately not refreshed (the two
statusline-shim.sh CLAUDE_CONFIG_DIR quotes, verified current below): a date refresh with no
verdict change would cost two plugin bumps for zero information, and the convention's
read-time-validation rule says a lookup finding no drift obliges no edit.

Verification

Every env-vars claim in the repo, re-derived from the 2026-08-10 verbatim read. 13 citation
sites and 50 distinct variable names swept.

Verdict Count Sites
Drifted, fixed 3 audit-checklist.md Category F (method defect, both directions); doctor-handoff.md DISABLE_DOCTOR_COMMAND; criteria.md's "and release it reaches"
Citation form rotted, fixed 1 anthropic-docs-profile.md env-vars.md:394
Basis upgraded (mirror → primary) 1 sweep-all CLAUDE_CODE_MAX_TOOL_USE_CONCURRENCY
Verified current, quoted 11 sweep-all ×4 more rows; stateless CLAUDE_CODE_DISABLE_AUTO_MEMORY (SKILL.md + official-guidance.md + README); continue-in-background CLAUDE_CODE_FORCE_SESSION_PERSISTENCE / CLAUDE_CODE_CHILD_SESSION; both statusline-shim.sh CLAUDE_CONFIG_DIR; official-corroboration.md's four owned names
Verified current, one sub-claim marked unverified 1 sonnet-5.md "from v2.1.111" — page states no release for that variable
Left unverified, reason stated 1 doctor-handoff.md skillOverrides — a settings claim, out of this page's scope; not re-read, and the record says so
Not a claim (pointer/instruction) OFFICIAL-DOCS.md row, criteria.md reading list, CHANGELOG history (never rewritten)

Non-Claude-Code names swept up by the variable grep — BASH_SOURCE/BASH_REMATCH (shell builtins),
PLAYWRIGHT_MCP_VIEWPORT_SIZE, and this repo's own MCP_GATE_ENABLED/BASH_LINT_ENABLED — are out
of scope: absence from env-vars is expected and means nothing.

Two representative verbatim quotes, both matching the repo as cited:

CLAUDE_CODE_DISABLE_AUTO_MEMORY | Set to 1 to disable auto memory.
Set to 0 to force auto memory on even when --bare mode or autoMemoryEnabled: false would
otherwise disable it. When disabled, Claude does not create or load auto memory files

CLAUDE_CONFIG_DIR | Override the configuration directory (default: ~/.claude). All settings,
session history, and plugins are stored under this path, as are credentials on Linux and Windows…

Gates (committed tree, CI form):

  • check-contract-slice-prune.sh --check-diff origin/main — pass, no path under docs/topics/
  • check-changelog-parity.sh --check-bump origin/main — pass, every bumped plugin has its entry
  • check-skill-portability.sh origin/main — pass, 4 skill files
  • check-shell-portability.sh origin/main — pass, no shell files in scope
  • check-changed-skills.sh origin/main — pass
  • markdownlint-cli2 over all 12 changed markdown files — 0 errors
  • validate-plugin-contracts.mjs (2241 files), generate-catalog.mjs --check,
    generate-cheatsheet.mjs --check — pass
  • Remaining gates: CI is the authority

Review round 1 (f2f05d8) — both findings real, both accepted:

  • claude: a 315-vs-318 row-count split across files citing the same read. Correct, and mine.
    grep -c '^| \'` returns 318 because the page has a second, three-row settings-file table; the
    variables table is 315. I caught it pre-PR but the corrective sweep matched the phrase literally
    and skipped two line-wrapped occurrences. On a PR arguing no-verbatim-quote-no-claim, an
    unreconciled count is the defect it argues against.
  • chatgpt-codex-connector (P2): relative ../../docs/ links die on plugin install. True — the
    marketplace cache holds plugins/<name>, not the repo-level docs tree. All in-plugin references
    are now absolute GitHub URLs, matching the form plugins/planning/reference/topic-docs.md
    already uses. Vendoring the guidance instead was declined: five copies of the rule would violate
    pointer-not-copy, which the upstream-drift doc's own Boundary defers to. The operational rule is
    stated inline at every site regardless, so the link is provenance rather than a dependency. Two
    pre-existing CHANGELOG links the rewrite caught were restored — history is never rewritten.

Reviewing that file surfaced prior art this PR had understated, which is why the hoist framing above
is stronger than it was at open: the profile already bound absence fetches to curl-the-.md. The
duplicate paragraph was withdrawn, and the profile's own earlier read of this page became the
evidence for the line-numbers-rot rule — stated as line numbers, not row counts: it records
CLAUDE_CODE_MAX_OUTPUT_TOKENS at line 277 of a 451-line page, and that row is at line 280 of 458
today. A row-count comparison was drafted and dropped: the earlier read's "316 rows" has no recorded
counting rule, and this PR just spent a round establishing that two defensible rules on this page
differ by three. Two line numbers from two records need no such assumption.

Related

  • #2182 — solved this once for
    one variable via a mirror; this generalizes its protocol and retires the mirror basis it wrote,
    by the terms of the trigger it wrote. A convention firing correctly within a day is the argument
    for writing retirement conditions down instead of leaving a rung permanent.
  • env-vars — the primary page, now readable in full
  • plugins/claude-ops/skills/changelog/context/read-actions.md — the page-scoped prior art hoisted

… reading as drift

`env-vars` cannot be read whole through a summarizing fetch. Three independent
attempts truncated before the `CLAUDE_CODE_MAX_*` range, and the summarizer then
reported those rows absent — a false negative indistinguishable from real drift.
This repo cites `env-vars` in ~13 places, so the defect was standing, silent, and
reproducible on demand.

The route: `curl` the raw-markdown channel (`<page>.md`) to a file and search the
file. Verified on `env-vars` — 361,797 bytes, 458 lines, 318 variable rows
including the range that had truncated away three times, two fetches hashing
identically. It is hoisted, not invented: `claude-ops`'s `changelog` skill already
carried it page-scoped and `knowledge`'s publisher profile already preferred the
`.md` channel, so per one-owner-per-concern the general form lands in the
upstream-drift convention and their page-specific detail stays with them. The
mirror rung from #2182 is kept as the fallback with its freshness-corroboration
step and its one-rung-below label.

Then every `env-vars` claim in the repo was re-derived from that read. Two were
wrong. `audit`'s MANDATORY env-var check instructed auditors to do precisely the
thing that fabricates findings, with no truncation guard and a framing that also
invited the inverse error — three variables this repo cites as real are absent
from the page. `audit-pass` carried `DISABLE_DOCTOR_COMMAND` as undocumented; it
is documented, and describes that skill exactly. `docpage-digest` cited the page
by line number, which had moved. `sweep-all`'s mirror basis retired to a primary
one, by the terms of the trigger #2182 wrote a day earlier — the convention
working as designed, which is the argument for writing retirement conditions down.
@cursor

cursor Bot commented Aug 11, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 4m 10s —— View job


Review complete

  • Gather context (PR diff, CLAUDE.md, REVIEW.md)
  • Review docs/OFFICIAL-DOCS.md and upstream-drift convention changes
  • Review claude-config skill changes (audit, audit-pass, audit-instructions)
  • Review discipline sweep-all changes
  • Review knowledge docpage-digest changes
  • Review playbooks sonnet-5.md changes
  • Check CHANGELOG/plugin.json version bumps for consistency
  • Post findings

Summary

This is a docs-only PR (17 files, all markdown/JSON) adding a .md-channel fetch route to the upstream-drift convention and re-deriving every env-vars citation in the repo from a verbatim read. Scope, semver bumps (all patch except the convention's own 1.1.0→1.2.0 minor, correctly justified as additive), and changelog/plugin.json pairing all line up. Relative markdown links added in criteria.md, audit-checklist.md, doctor-handoff.md, sweep-all/SKILL.md, anthropic-docs-profile.md, and sonnet-5.md all resolve correctly to docs/conventions/upstream-drift/README.md at their respective directory depths, and the #reading-the-basis--the-fetch-route anchor matches GitHub's slug for the new ## Reading the basis — the fetch route heading (double hyphen from the em dash, correctly reproduced).

One correctness finding, posted inline:

  • Self-contradicting row count for the same verbatim read. plugins/knowledge/CHANGELOG.md and plugins/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md both state the 2026-08-10 verbatim read of env-vars.md found 318 variable rows, while docs/conventions/upstream-drift/README.md, docs/conventions/upstream-drift/CHANGELOG.md, and plugins/claude-config/skills/audit/reference/audit-checklist.md all state 315 for the same page, same date, same claimed-identical-SHA-256 read. Given the PR's central thesis is "no verbatim quote, no claim," this specific pair of claims is currently unreconciled. Flagged inline on both knowledge locations with links to the 315 sources.

Note on verification: I don't have network egress in this environment (git fetch, curl, and WebFetch against code.claude.com were all blocked or — in WebFetch's case — truncated the page itself, ironically reproducing the exact failure mode this PR is about). I could not independently confirm which count (315 or 318) is correct, the claimed byte size/line count, the SHA-256 determinism claim, or the specific DISABLE_UPGRADE_COMMAND/FALLBACK_FOR_ALL_PRIMARY_MODELS line-394/400 claims — these are author-claimed and unverified by me. I also did not independently run markdownlint-cli2 or the other gates listed in the Verification section; those results are author-claimed.

No other correctness, architecture, or maintainability issues found in the diff.
· branch: gap/envvars

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

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


Security review of PR #2185

  • Gather context (diff via gh pr diff 2185, full file list read)
  • Review each changed file for security issues
  • Post findings

Scope reviewed: all 17 changed files (git diff origin/main...HEAD / gh pr diff 2185) — docs/OFFICIAL-DOCS.md, docs/conventions/upstream-drift/{README,CHANGELOG}.md, and matching plugin.json + CHANGELOG.md + skill-reference .md files for claude-config, discipline, knowledge, and playbooks.

Findings

None. This PR is documentation-only:

  • No GitHub Actions workflow files are touched (nothing under .github/workflows), so there's no pull_request_target/workflow_run trigger risk, no permissions: change, and no github.*-context script-injection surface in scope.
  • No shell scripts, hooks, or executable code are added or modified — every changed file is Markdown (.md) or a plugin.json version/description bump. The new curl <url>.md guidance added across several files (e.g. docs/conventions/upstream-drift/README.md, plugins/discipline/skills/sweep-all/SKILL.md) is prose instructing a human/agent to fetch a fixed, hardcoded Anthropic docs URL — not a script, template, or interpolated command, so there's no injection surface (the URL is a static literal, never built from untrusted input).
  • No secrets, tokens, or credentials are introduced, referenced, or handled anywhere in the diff.
  • No authorization, access-control, or trust-boundary logic exists in any of the changed files — they're plugin skill reference docs and changelogs.
  • No dependency or action pins are touched, so no supply-chain surface applies here (correctly out of scope for this lane per zizmor anyway).

No security concerns to flag on this PR.

Comment thread plugins/knowledge/CHANGELOG.md Outdated
Comment thread plugins/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md Outdated
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

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

@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: 1256cd90bc

ℹ️ 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/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md Outdated
…links installable

Review round 1. Two findings, both real.

The 315-vs-318 split was mine: the sed that corrected the count matched only
unwrapped occurrences, so two line-wrapped ones in `knowledge` kept the wrong
figure. 315 is the variables table; 318 counted three settings-file rows in a
different table on the same page. On a PR whose thesis is no-verbatim-quote-no-claim,
an unreconciled count is the defect it argues against.

The relative `../../docs/` links do not survive plugin installation — the cache
holds `plugins/<name>`, not the repo-level docs tree — so every link added inside
a plugin is now an absolute GitHub URL. Vendoring the guidance instead would
violate pointer-not-copy; the operational rule is stated inline at each site
regardless, so an installed reader is never dependent on following the link. Two
pre-existing CHANGELOG links the rewrite caught are restored: history is never
rewritten.

Reviewing that file also surfaced prior art the PR had understated. The Anthropic
publisher profile ALREADY bound absence-establishing fetches to curl-the-.md, on
the asymmetry that a truncated fetch cannot fabricate a presence, only an absence.
The duplicate paragraph is withdrawn, the rule is left where it is and named as
the fleet rung, and the convention now says the route was hoisted from two
surfaces that derived it independently — which is a stronger claim than one
surface plus an invention. Its recorded earlier read of the same page (451 lines,
316 rows, versus 458 and 315 today) is now the evidence for the line-numbers-rot
rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdBsorqGjCU4Mg5nrmgz4m
@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 4s —— View job


Security review of PR #2185 (re-run after new push, commit f2f05d8)

  • Gather context (gh pr diff 2185 against origin/main, full 17-file diff)
  • Review each changed file for security issues
  • Post findings

Scope reviewed: all 17 changed files, confirmed via git diff --stat equivalent (gh pr diff) — same file set as the PR's changed-files list, no files added or removed by the new commit f2f05d8 ("reconcile the env-vars row count and make the fetch-route links installable"), which only edits prose (the 315-row-count fix and swapping relative docs/... links for absolute https://github.com/melodic-software/claude-code-plugins/blob/main/... links per the earlier Codex comment).

Findings

None. This PR remains documentation-only:

  • No workflow files. Nothing under .github/workflows is touched — no pull_request_target/workflow_run risk, no permissions: change, no github.*-context script-injection surface.
  • No executable code. All 17 changed files are Markdown (.md) or plugin.json version/description bumps. Confirmed via the full file list — every path ends in .md or .claude-plugin/plugin.json.
  • New/changed links are static literals, not an injection surface. The new commit's link rewrite (relative → absolute https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/upstream-drift/README.md#reading-the-basis--the-fetch-route) and the curl https://code.claude.com/docs/en/env-vars.md guidance in audit-checklist.md are all fixed, hardcoded URLs in prose instructing a human/agent — never built from untrusted input, never interpolated into a shell command in this diff.
  • No secrets, tokens, or credentials introduced or referenced anywhere in the diff.
  • No authorization/access-control/trust-boundary logic in any changed file — these are plugin skill reference docs and changelogs.
  • No dependency or action pins touched — no supply-chain surface in scope.

Consistent with the security review already posted on the prior commit (1256cd9) — the intervening fixes were correctness/consistency edits (row-count reconciliation, link portability) with no new security-relevant surface.

No security concerns to flag on this PR.
· branch: gap/envvars

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 59s —— View job


Code review complete

  • Gather context (PR diff, CLAUDE.md, prior review comments)
  • Check whether the new commit (f2f05d8) resolves the 315/318 row-count discrepancy flagged in the prior review
  • Check whether the new commit resolves Codex's cross-plugin relative-link finding
  • Review remaining changed files for correctness/maintainability issues
  • Post findings

Summary

This synchronize pushed one commit, f2f05d8, in direct response to the two findings from the prior review round. I checked both fixes against the actual repo state (not just the author's commit message):

  • 315-vs-318 row count reconciled. Grepped the whole repo for "31[5-9] variable" — every remaining occurrence (docs/conventions/upstream-drift/{README,CHANGELOG}.md, plugins/claude-config/{CHANGELOG.md,skills/audit/reference/audit-checklist.md}) now says 315. No stray 318 remains anywhere in the tree.
  • Cross-plugin relative links fixed. Grepped every file this PR touches inside plugins/ for the fetch-route reference: all now point to the absolute blob URL https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/upstream-drift/README.md#reading-the-basis--the-fetch-route, which survives plugin-only installation (unlike the earlier ../../docs/... relative form Codex flagged). The two pre-existing relative CHANGELOG links this touched in a prior version (plugins/discipline/CHANGELOG.md:48, plugins/playbooks/CHANGELOG.md:797,925) are untouched historical entries, consistent with the "history is never rewritten" rule stated in the commit message.
  • Anchor correctness spot-checked. #reading-the-basis--the-fetch-route (double hyphen from the em dash) matches GitHub's slug for ## Reading the basis — the fetch route; #drift-signal--content-hashing-deferred and #convention-registry (in PLUGIN-PHILOSOPHY.md) both resolve to real headings.
  • Version/changelog pairing. All four plugin.json bumps (claude-config 0.29.1, discipline 0.12.3, knowledge 0.11.1, playbooks 0.8.3) have a matching ## [x.y.z] CHANGELOG entry.
  • doctor-handoff.md, criteria.md, sonnet-5.md prose reads coherently after the edits — no dangling clauses or broken numbered-list renumbering introduced by the fixup.

No new correctness issues found in this round. Both findings from the prior review are resolved; I have no other findings to add.

Not independently verified (no network egress in this environment, same limitation as the prior review round): the underlying env-vars page content itself — actual row count, byte size, SHA-256 determinism claim, and the FALLBACK_FOR_ALL_PRIMARY_MODELS/DISABLE_UPGRADE_COMMAND line-394/400 claims in plugins/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md. These remain author-claimed. I also did not re-run markdownlint-cli2 or the other CI gates listed in the PR's Verification section (blocked by the same sandboxing); those results are author-claimed.
· branch: gap/envvars

The line-numbers-rot rule was argued from a row-count delta -- 316 rows in the
profile's earlier read of env-vars.md against 315 today -- and concluded that
both a growth and a removal landed between the reads. That conclusion is not
available: the earlier 316 has no recorded counting rule, and this branch just
established that two defensible rules on this page differ by three (315 variable
rows, 318 including a second table's settings-file rows). If the earlier author
counted the way the first draft here did, the delta runs the other direction and
needs no removal at all.

The line numbers are unambiguous and both are already on the record: that read
puts CLAUDE_CODE_MAX_OUTPUT_TOKENS at line 277 of 451, and the 2026-08-10 read
puts the same row at line 280 of 458. That proves line numbers rot, which is the
only thing the rule needs, and it assumes nothing about anyone's counting rule.

Same defect class the review caught one commit ago -- a number reused across
surfaces without its derivation -- and it does not belong in the PR that
establishes no-verbatim-quote-no-claim.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdBsorqGjCU4Mg5nrmgz4m
@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

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


Security review of PR #2185 (commit 1ec8da4)

  • Gather context (gh pr diff 2185 --name-only confirms the same 17 files as prior reviews; git diff f2f05d8 1ec8da4 isolates what changed since the last security pass)
  • Review new/changed content for security issues
  • Post findings

Scope reviewed: the full 17-file diff against origin/main (same file set already reviewed on 1256cd9 and f2f05d8), with focused attention on the delta introduced by the new commit 1ec8da4 ("drop a row-count inference the record cannot support").

What changed since the last review: exactly one file, plugins/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md, 4 lines changed. It replaces a 451 lines and 316 rows / 458 lines and 315 rows framing with a citation anchored to a specific variable name (CLAUDE_CODE_MAX_OUTPUT_TOKENS at line 277→280) instead of a row-count comparison the record can't fully support. Pure prose — no code, no links, no structural change.

Findings

None. This remains a documentation-only PR:

  • No workflow files touched — nothing under .github/workflows, so no pull_request_target/workflow_run risk, no permissions: change, no github.*-context injection surface.
  • No executable code anywhere in the diff — every changed file is Markdown or a plugin.json version bump.
  • The new commit's edit is a citation-precision fix in prose, not a link or command change — no new injection surface, no new external reference introduced.
  • No secrets, tokens, or credentials anywhere in the diff.
  • No authorization/access-control/trust-boundary logic in any changed file.
  • No dependency or action pins touched.

Consistent with the two prior security reviews on this PR (1256cd9 and f2f05d8) — no security-relevant surface has been introduced at any point in this PR's history.

No security concerns to flag on this PR.
· branch: gap/envvars

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

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


Code review complete

  • Gather context (PR diff, CLAUDE.md, prior review threads)
  • Review latest commit 1ec8da4 ("drop a row-count inference the record cannot support") against prior review findings
  • Re-check the 315/318 row-count reconciliation and cross-plugin link fixes for regressions
  • Review remaining changed files for correctness/maintainability issues
  • Post findings

Summary

This review covers the state as of 1ec8da4, the third commit on this branch, pushed in response to prior review rounds. I re-verified (via grep/git diff in this checkout, not by re-fetching the upstream env-vars page — no network egress here, same limitation noted in the earlier security-review runs) that the two previously-flagged issues are resolved and stay resolved:

  • 315-vs-318 row count: no stray 318 remains anywhere in the tree (grep -rn "318" across the changed plugins/docs is clean); every file states 315 for the variables table.
  • Cross-plugin relative links (Codex's P2): every fetch-route reference inside a plugin now uses the absolute https://github.com/.../blob/main/docs/conventions/upstream-drift/README.md#reading-the-basis--the-fetch-route form, which survives plugin-only install.

New finding (posted inline)

The latest commit (1ec8da4) removed an unsupported "316-row" inference about the earlier env-vars.md read from plugins/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md (lines 19–22) — per its own commit message, "the earlier 316 has no recorded counting rule." The identical "316-row" claim survives in the same file, three lines below the edited section:

plugins/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md:53:

CLAUDE_CODE_MAX_OUTPUT_TOKENS sits at line 277 of a 451-line, 316-row page whose rendered fetch surfaced only roughly its first fifth.

grep -rn "316" across every changed plugin and docs/ in this checkout turns up only this one hit — the same defect class the commit's own message names ("a number reused across surfaces without its derivation"), missed in a second spot in the same file rather than caught by the sweep that fixed the first. Detail and a fix link are on the inline comment.

Other checks

  • Version/changelog pairing: all four plugin.json bumps (claude-config 0.29.1, discipline 0.12.3, knowledge 0.11.1, playbooks 0.8.3) have a matching ## [x.y.z] entry in that plugin's CHANGELOG.md.
  • docs/conventions/upstream-drift/{README,CHANGELOG}.md minor bump (1.1.0 → 1.2.0) is additive-only, consistent with SemVer rule stated in that convention's own CHANGELOG header.
  • Prose in criteria.md, doctor-handoff.md, audit-checklist.md, sweep-all/SKILL.md, and sonnet-5.md reads coherently after the fixup commits — no dangling clauses or broken list structure.
  • Did not independently run markdownlint-cli2 or the other CI gates listed in the PR's Verification section; those results remain author-claimed.
    · branch: gap/envvars

…f deleting it

Review round 2: the retracted row-count inference had a sibling three lines
below -- the profile's own record of an earlier read, 'a 451-line, 316-row
page'. Same weakness: no counting rule, on a page that admits two differing by
three.

Qualified rather than removed. That figure is this pipeline's recorded
observation of a fetch it performed, and the profile's own archive-reading rule
says a source artifact is noted at the row and never silently repaired --
deleting the number would repair the record instead of annotating it. What the
line now says is what is true: the count supports nothing by subtraction, and
the rule it appears in rests on the 277-of-451 position and the first-fifth
cutoff, which are unambiguous. That is also why the sibling rule above argues
from line numbers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FdBsorqGjCU4Mg5nrmgz4m
@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

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


Security review of PR #2185 (commit 0b3b56d)

  • Gather context (diff since last security review at 1ec8da4, and full PR diff against merge-base b01dace)
  • Review new/changed content for security issues
  • Post findings

Scope reviewed: the full 17-file diff against origin/main (unchanged file set from all prior reviews on this PR — confirmed via git diff b01dace 0b3b56d --stat). The new commit (0b3b56d, "qualify the profile's unruled 316-row count instead of deleting it") touches exactly 2 files versus the last-reviewed commit (1ec8da4):

  • plugins/knowledge/CHANGELOG.md
  • plugins/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md

Both changes add a qualifying clause to the existing "316-row" figure (noting it lacks a recorded counting rule, per the 315-vs-318 reconciliation from earlier review rounds) rather than deleting it, plus a cross-reference to an in-file anchor (#archive-reading-conventions). Pure prose edits — no code, no commands, no new external links or references.

Findings

None. This PR remains documentation-only, and this commit introduces no new surface of any kind:

  • No workflow files touched — nothing under .github/workflows, so no pull_request_target/workflow_run risk, no permissions: change, no github.*-context injection surface.
  • No executable code anywhere in the diff — every changed file across the whole PR is Markdown or a plugin.json version bump.
  • The new commit is a prose qualification, not a link or command change — the added #archive-reading-conventions reference is an internal same-file anchor, not an external URL.
  • No secrets, tokens, or credentials anywhere in the diff.
  • No authorization/access-control/trust-boundary logic in any changed file.
  • No dependency or action pins touched.

Consistent with all four prior security reviews on this PR (1256cd9, f2f05d8, 1ec8da4) — no security-relevant surface has been introduced at any point in this PR's history.

No security concerns to flag on this PR.
· branch: gap/envvars

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

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


Code review complete

  • Gather context (gh pr diff 2185, full 17-file diff, CLAUDE.md)
  • Check whether latest commit (0b3b56d) resolves the outstanding "316-row" finding on anthropic-docs-profile.md:53
  • Re-verify no regressions in the 315/318 reconciliation and cross-plugin link fixes
  • Review remaining changed files for correctness/maintainability issues
  • Post findings

Summary

Reviewed the PR at HEAD 0b3b56d (branch gap/envvars) against origin/main via gh pr diff 2185 — same 17 files as all prior review rounds.

The outstanding finding from the last round is resolved. Commit 0b3b56d addresses the sibling "316-row" claim at plugins/knowledge/skills/docpage-digest/context/anthropic-docs-profile.md:53 (flagged inline in the previous round) by qualifying it in place rather than deleting it — consistent with the file's own archive-reading rule ("a source artifact is noted at the row and never silently repaired"). I re-checked this rather than taking the commit message at face value:

  • grep -rn "318" across plugins/knowledge, plugins/claude-config, plugins/discipline, plugins/playbooks, and docs/ turns up exactly one hit, in the new qualifying sentence itself (anthropic-docs-profile.md:55), which correctly explains the 315-vs-318 split (second table's settings-file rows) rather than asserting either as the row count.
  • grep -rn "316" turns up only the two now-qualified occurrences (anthropic-docs-profile.md:53-54, plugins/knowledge/CHANGELOG.md:30) plus unrelated PR-number matches (#1316, #1318) in other docs — no stray unqualified "316" remains.
  • The new [noted source artifact, not a repaired one](#archive-reading-conventions) link resolves — ## Archive-reading conventions is a real heading in the same file, and GitHub's slug matches.

Re-verified, no regressions from earlier rounds:

  • Cross-plugin fetch-route references (Codex's P2 finding) all use the absolute https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/upstream-drift/README.md#reading-the-basis--the-fetch-route form in every file this PR touches — confirmed by grep, not just the commit message. The plain-text (non-link) mentions of docs/conventions/upstream-drift/README.md remaining in criteria.md (lines 26, 1198, 1349) and discipline/CHANGELOG.md:48 are outside this PR's diff and pre-existing, not a regression.
  • All new/changed anchors resolve to real headings: #reading-the-basis--the-fetch-route, #drift-signal--content-hashing-deferred, #a-date-is-never-authority (all in upstream-drift/README.md), #convention-registry (in PLUGIN-PHILOSOPHY.md).
  • The audit-checklist.md table row (a single long cell) still parses as a valid 3-column row — 4 pipe characters, no unescaped internal pipes.
  • Version/changelog pairing holds for all four bumps: claude-config 0.29.1, discipline 0.12.3, knowledge 0.11.1, playbooks 0.8.3 — each has a matching ## [x.y.z] entry.
  • doctor-handoff.md's renumbered list (items 1–3 plus the "Suppression channels" prose) reads coherently; no broken list structure from the insertion.

No new correctness, architecture, or maintainability issues found in this round's diff.

Not independently verified (no network egress in this environment): the actual env-vars page content — the 315-row count, byte size, SHA-256 determinism claim, and the specific DISABLE_UPGRADE_COMMAND / FALLBACK_FOR_ALL_PRIMARY_MODELS line claims. These remain author-claimed, as in every prior round. I also did not re-run markdownlint-cli2 or the other CI gates listed in the PR's Verification section.
· branch: gap/envvars

@kyle-sexton
kyle-sexton merged commit 72b199e into main Aug 11, 2026
33 checks passed
@kyle-sexton
kyle-sexton deleted the gap/envvars branch August 11, 2026 00:54
kyle-sexton added a commit that referenced this pull request Aug 11, 2026
…scope (#2191)

No linked issue

## Summary

`upstream-drift` 1.2.0 shipped the fetch route yesterday
([#2185](#2185)).

[#2187](#2187)
applied it across 327
stamps today and surfaced **two holes in it**. Both are the route's own
failure mode — a false
absence carrying every outward sign of a good read — one scope up from
where 1.2.0 drew the line.

This is the convention's recheck discipline finding gaps in the
convention, one release after it
shipped. `upstream-drift` 1.2.0 → 1.3.0, additive; no required part,
canonical name, or
enforceability verdict changed. Pure `docs/`, so no plugin bump.

## Fix

### Hole 1 — a `200` does not mean you got the page you asked for

1.2.0's rung 1 guarded against truncation and against a channel that
404s. It did not guard against
a channel that **succeeds with the wrong page**. A retired slug is
silently aliased to its
successor — no redirect, no `Location`, no notice in the body:

| Probe | Result |
|---|---|
| `slash-commands.md` | `200`, `text/markdown`, 82,668 bytes, first
heading `# Extend Claude with skills` |
| `skills.md` | `200`, `text/markdown`, 82,668 bytes, same heading |
| SHA-256 of both |
`a833dd5c96b9b111de0daec5fc6436e210c8cdc009e51306d32438746db0b5a5` —
**byte-identical** |
| Rendered `slash-commands` | `200`, `num_redirects: 0` |
| `nonexistent-page-xyz.md` | `404` — so this is **not** a catch-all;
aliasing is specific to slugs that once existed |

**This outranks truncation as a failure.** Truncation at least yields
text you can see is short.
Here a search for a term the *requested* page owns comes back empty
against a full, healthy-looking
body. Identity is therefore now part of rung 1, with two cheap checks:

- **Confirm the slug against `llms.txt`.** Verified across ten slugs:
the nine live ones each
appear as `docs/en/<slug>.md`; `slash-commands` appears in no such entry
(only an unrelated
`agent-sdk/slash-commands`) — exactly the one that aliased. A mechanical
detector, not a judgment
  call.
- **Read the body's first heading before quoting it.** A heading that
does not match the page you
asked for ends the read. A title merely *worded* differently from the
slug does not —
`sub-agents.md` is titled "Create custom subagents", `costs.md` "Manage
costs effectively"; both
  are correct pages.

A missing slug is not a dead end — it is a prompt to find the successor
in the index and cite
**that** slug. Left unchecked, a citation of a retired slug keeps
working indefinitely while
pointing somewhere its author never read, then becomes a `404` on a
claim nobody re-derived the day
the alias is dropped.

### Hole 2 — an absence claim now carries its scope

1.2.0 said a truncated read supports no absence claim. It never said a
**complete** read of one page
supports no claim about the product. Two moves break it:

- **Widening the subject.** Searching `hooks` and concluding "Claude
Code has no X" asserts
something about every page not searched. The honest form names the
corpus: "not documented on
`hooks`" — or, if the sweep genuinely covered the index, "not documented
on any page listed in
  `llms.txt` as of `<date>`", a far larger and more expensive claim.
- **Searching the phrase instead of the capability.** Verified on
`hooks.md`: the phrase
  "verbose hooks" appears **zero** times, while the same page documents

> Async hook completion notifications are suppressed by default. To see
them, enable verbose mode
  > with `Ctrl+O` or start Claude Code with `--verbose`.

  and separately

> set `CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose` to see additional log lines
such as hook matcher
  > counts and query matching

A phrase search returns nothing here and licenses "no verbose hooks
toggle exists" — false, from a
  complete, untruncated read of the *right* page.

Stated as its own rule because it is the reason to care: **a sound
conclusion resting on a false
premise is fragile, not safe.** The instance above kept its conclusion
on a corrected premise

([#2190](#2190));
the next reader who
checks a false premise discards the conclusion with it. Fix the premise
and keep the conclusion —
never keep a premise because the conclusion it props up is convenient.

## Verification

Every claim above was re-derived here directly rather than taken on
report, per the rule this
section states — a report of an absence is exactly the thing the
convention says not to accept
second-hand:

- Both `.md` bodies fetched and hashed locally;
`slash-commands`/`skills` identity confirmed by
  matching SHA-256, byte count, and first heading
- Redirect behavior probed with `curl -L -w '%{num_redirects}'` on the
rendered URL
- `404` control run against an invented slug, establishing the alias is
not a catch-all
- `llms.txt` fetched (187 `docs/en/` entries) and checked slug-by-slug
across all ten
- `hooks.md` fetched in full; `grep -ic "verbose hooks"` → `0`, and all
four `verbose` mentions read
  verbatim
- Nine live pages fetched to confirm the title-vs-slug check does not
produce false positives

**Gates (committed tree, CI form):** `check-contract-slice-prune.sh
--check-diff origin/main`,
`check-changelog-parity.sh --check-bump origin/main`,
`check-skill-portability.sh`,
`check-shell-portability.sh` — all pass; `markdownlint-cli2` over both
changed files — 0 errors.
Remaining gates: CI is the authority.

## Related

-
[#2185](#2185)
— shipped the fetch
  route this patches
-
[#2187](#2187)
— applied it at scale
  and found both holes
-
[#2190](#2190)
— withdrew the
  over-scoped nonexistence claim that hole 2 generalizes

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Aug 11, 2026
…searched (#2190)

No linked issue

## Summary

PR #2187 claimed, as a headline result, that reading complete raw pages
made two negative claims assertable "for the first time". **One of them
was wrong**, and wrong in the way negative claims usually are: I
searched one page and stated the result about the product.

This corrects it. The rule it supports does not change; its
justification does.

## Fix

### The claim that was wrong

`docs/conventions/hook-observability/README.md` read:

> **Not a UI feature.** No native "verbose hooks" toggle exists in
Claude Code as of 2026-08-10 (confirmed against the same fresh fetch
this doc cites)

The literal phrase "verbose hooks" does appear on no page. But `verbose`
appears across **at least 13 Claude Code docs pages**, four of those
mentions on `hooks` itself — and one is squarely on point:

> Async hook completion notifications are suppressed by default. To see
them, enable verbose mode with `Ctrl+O` or start Claude Code with
`--verbose`.

A verbose mode that reveals hook output is precisely what the bullet
denied. Also present, none of it acknowledged:

| Surface | What it is |
| :-- | :-- |
| `verbose` setting | "Show full tool output instead of truncated
summaries" |
| `viewMode` setting | `"default"` / `"verbose"` / `"focus"` |
| `--verbose` flag | Full turn-by-turn output; overrides `viewMode` |
| `CLAUDE_CODE_DEBUG_LOG_LEVEL=verbose` | Hook matcher counts and query
matching |
| `--include-hook-events` | Hook lifecycle events in the stream-json
feed |

### Why the rule still stands

None of those is a **consumer-facing toggle that makes an ordinary
hook's routine work visible**. Each is operator-driven debugging, a
transcript view the consumer must already have switched on, or a machine
feed for a `-p` harness. A plugin cannot depend on an operator's debug
posture, and none of them changes where a hook must *put* its message.

So `statusMessage` and `systemMessage` remain the surfaces a fleet hook
writes to, and no hook in the fleet changes. What changes is that the
rule now rests on "a plugin cannot assume an operator's debug posture"
instead of on a nonexistence claim that was false. A correct conclusion
resting on a false premise is fragile — the next person to check the
premise has reason to discard the conclusion with it.

### Second fix, same class

The same file attributed this to the hooks page:

> The harness's own signal for it is a generic "PostToolUse hook
modified `<file>` after your edit (likely a formatter)" line

That string appears on **no** Claude Code docs page. It is an observed
harness string, and the sentence read as though it had been verified
against the page cited beside it. It is now labelled as observed, and
the documented negative it sits next to — that the three output channels
carry no file-change or diff surface — is kept and separately
attributed, since that is the part the rule actually needs and it does
hold.

## Verification

- `verbose` occurrence counts taken across the full raw-markdown corpus
fetched via the rung-1 route in [`upstream-drift`, "Reading the
basis"](https://github.com/melodic-software/claude-code-plugins/blob/main/docs/conventions/upstream-drift/README.md#reading-the-basis--the-fetch-route):
13 pages carry it, `hooks.md` four times.
- The `Ctrl+O` / `--verbose` sentence quoted verbatim from `hooks.md`.
- "PostToolUse hook modified" and "likely a formatter" both return zero
matches across every page in the corpus.

Gates: `check-contract-slice-prune` pass · `check-changelog-parity
--check-bump` pass · `check-skill-portability` pass (no skill files in
scope) · `markdownlint-cli2` 0 errors. Pure `docs/` change — no plugin
version bump required.

## Related

- PR #2187 — introduced the claim this corrects, as one of its two
advertised negatives
- PR #2185 — established the rung-1 fetch route; its gotcha "absence
from one page is not absence" is what prompted re-checking my own merged
work, and is what caught this

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Aug 12, 2026
…ill a real hook inventory (#2301)

## Summary

Two clusters in `claude-config:audit`, both of the same shape: the skill
claiming coverage it did not have.

**Category G was unexecutable (#2274).** It is the one Phase 2 category
a consumer could not run.

- Its only overflow detector was `/doctor`, which needs an interactive
TTY, so the category yielded nothing in the harness's own headless mode.
It now names the documented headless route — *"When the listing exceeds
its budget, Claude Code also writes a warning to the debug log, visible
with `--debug`"* — and reports which route it took. `/context`'s Skills
row is labelled as what it is: a second *interactive* reading, not a
headless substitute.
- It stated no budget constant, so "overflowed, and by how much?" was
unanswerable by hand. It now carries `skillListingBudgetFraction`
default `0.01`, `SLASH_COMMAND_TOOL_CHAR_BUDGET`'s documented
8,000-character fallback, `skillListingMaxDescChars` default `1536`, and
the `200,000 × 4 × 0.01 = 8,000` arithmetic reconciling the first two.
- Its "cheapest first" lever list put `skillOverrides` second while
carrying the caveat that it does not reach plugin skills, and never
named the substitute upstream prescribes — so on a plugin-heavy roster
the ordered list degenerated to the entry it labels "last resort".
Levers are now split by roster origin, `/plugin` is named, and a
roster-composition count is required before any lever is recommended. No
per-skill `name-only` state is promised for `/plugin`; no page documents
one.
- It was the only letter with no checklist table (headings ran A–F,
**H**, I). It has one now, and the sentence at `audit-checklist.md:5-7`
that said it had none is gone rather than left contradicting the same
commit.

**Corrected remedy for the table row.** The originating item asked for a
new `check-skill-listing-budget.sh`. That measurement already exists as
`plugins/skill-quality/scripts/check-listing-budget.sh` (#1404), so the
fix is wiring — and the wiring says out loud what the item did not:
**the two tools measure different populations.** That script walks a
*repository's* skills roots; Category G asks about the listing a
*consumer's session* assembled. It is pointed at as an explicit in-repo
proxy, never as the consumer's number. Its measured cost is stated too
(below), because a marketplace-wide invocation cannot finish inside a
default Bash tool timeout (#2216).

**`audit` asserted hook coverage it could not enumerate (#2275).**
Category D writes rules for `${CLAUDE_PLUGIN_ROOT}` /
`${CLAUDE_PLUGIN_DATA}` placeholders that appear only in a
plugin-provided hook, and Category B's third baseline narrowing turns on
whether such a hook is live — while three of the skill's own surfaces
disclosed in prose that the enumeration was impossible
(`context/procedures.md`, `reference/required-permissions.md` twice).
Gap disclosed in three places, closed in none.

New `scripts/check-hook-coverage.sh` (+ `.test.sh`) closes it. It
resolves each enabled plugin through the installed-plugin registry —
every record carries a version-pinned `installPath`, so no
version-directory ordering is inferred — and reads its hook config in
all four documented shapes (`hooks/hooks.json`; a `hooks` path, array of
paths, or inline object in `plugin.json`; plugins-reference: *"Location:
`hooks/hooks.json` in plugin root, or inline in plugin.json"*, key typed
`string|array|object`). It also reports `disableAllHooks` /
`allowManagedHooksOnly` / `strictPluginOnlyCustomization`, because a
hook a setting has switched off is not coverage.

The **exit code is the contract**: `0` complete, `1` partial with the
unenumerated sources named, `2` no inventory. The fail-open posture is
kept and narrowed — it applies to what a partial run could not read, not
to every run by default. This also removes the ordering problem the
narrowing-3 precondition used to carry: Phase 1.0 runs before Category
B, so the lever reading is available even on a scope-filtered `/audit
permissions`. All three "no enumeration path exists" surfaces were
rewritten in the same commit, and the eval that asserted "having no way
to enumerate a plugin's hooks/hooks.json" was rewritten with them.

**A live defect found while writing it, with the mechanism stated
correctly.** On Git for Windows `jq` writes stdout in **text mode** and
appends a CR to every line — a property of jq's own output stream,
**not** of the input file's line endings. Untreated, a plugin key read
out of `jq` is `name@marketplace\r`, every registry lookup misses, and
the plugin is reported as not installed on a machine where it is
installed. My first comment blamed CRLF input files; the red run refuted
that (LF-only fixtures failed too) and the comment was corrected to
match. Pinned by a regression case.

**Also in this PR:**

- **`destructive-bash-deny` ships with its fragility.** Eight
argument-constraining globs, rated `error` when absent, with no caveat —
in a file that quotes the permissions page's *"Bash permission patterns
that try to constrain command arguments are fragile"* one section
earlier, against a different table. The concrete hole is now stated:
matching is prefix-anchored, so `Bash(git push --force *)` does not
match `git push origin main --force`. The patterns stay — they raise the
cost of an accidental force push — but a finding no longer implies they
bound a determined one. Deliberately **not** claimed: that upstream
ranks the hook above the glob for destructive commands. That page's
remedy recommendation is scoped to URL filtering; only the fragility
half is doc-supported, and the PR says so.
- **The "speed bump, not a boundary" hook ranking is scoped, not
deleted.** Correct for secret exfiltration, where an OS boundary exists.
Stated unscoped, it also governed destructive git — where the sandbox's
vocabulary is `filesystem.*` paths and `network.*` hosts, with no
expression for a command's arguments, so it cannot separate `git push`
from `git push --force` to the same remote.
- **The read-it-verbatim guard covers all of Phase 3**, not the single
Category F row #2185 landed it on — and `settings`, fetched in Phase
3.1, is the page a summarizing fetch already reported three present keys
as absent on.

Every doc quote above was re-fetched as raw markdown (`curl
…/docs/en/<page>.md`) on 2026-08-12 and grepped, not recalled.

## Test plan

New suite, red-then-green. Red proof: deleting the CR strip from
`jqs()`:

```
$ bash plugins/claude-config/skills/audit/scripts/check-hook-coverage.test.sh
FAIL: case 1: complete inventory exits 0
FAIL: case 1: plugin hook enumerated
FAIL: case 4: CRLF settings still complete
FAIL: case 4: CRLF plugin resolved
FAIL: case 5: inline hook enumerated
FAIL: case 6: custom-path hook enumerated
FAIL: case 9: inventory verdict present
17/34 checks failed.
```

Green, with the fix restored:

```
$ bash plugins/claude-config/skills/audit/scripts/check-hook-coverage.test.sh
PASS: case 1: plugin hook enumerated
PASS: case 3: partial inventory exits 1
PASS: case 3: never claims completeness
PASS: case 4: CRLF plugin resolved
PASS: case 5: inline hook enumerated
PASS: case 6: custom-path hook enumerated
PASS: case 7: missing declared path is partial
PASS: case 8: lever reported
PASS: case 9: --json output is valid JSON
PASS: case 10: exit 2 with no readable scope
PASS: case 11: exit 2 when jq missing

All 34 checks passed.
```

Against the real machine (73 enabled plugins), the inventory the skill
previously could not take:

```
$ bash plugins/claude-config/skills/audit/scripts/check-hook-coverage.sh
Hooks (44):
  SOURCE                       EVENT        MATCHER         COMMAND
  settings:user                PreToolUse   Bash|PowerShell bash ~/.claude/hooks/block-destructive-removal.sh
  plugin:guardrails@melodic-software PreToolUse Bash|PowerShell "${CLAUDE_PLUGIN_ROOT}"/hooks/block-dangerous-git.sh
  …
INVENTORY: complete — every enabled plugin resolved and every hook source parsed.
```

The Category G proxy-cost figure quoted in the checklist, measured here:

```
$ time bash plugins/skill-quality/scripts/check-listing-budget.sh plugins/claude-config/skills
Shared listing-budget estimate over 8 listing-eligible skill(s) across 1 root(s):
  aggregate: 5892 chars
  budget:    8000 chars (documented default (SLASH_COMMAND_TOOL_CHAR_BUDGET fallback))
CHECK-LISTING-BUDGET: OK — aggregate 5892/8000 chars within budget.

real    0m5.983s
```

Repo gates:

```
$ shellcheck --rcfile=.shellcheckrc -x …/check-hook-coverage.sh …/check-hook-coverage.test.sh
SHELLCHECK CLEAN

$ bash scripts/check-shell-portability.sh --paths …/check-hook-coverage.sh …/check-hook-coverage.test.sh
No unexcused GNU-only constructs in 2 shell file(s).

$ CHECK_SKILL_SKILLS_ROOT=plugins/claude-config/skills bash plugins/skill-quality/scripts/check-skill.sh audit
INFO: markdownlint clean
INFO: script test passed: scripts/check-hook-coverage.test.sh
INFO: script test passed: scripts/check-plugin-drift.test.sh
INFO: script test passed: scripts/check-structure.test.sh
INFO: script test passed: scripts/fix-plugin-drift.test.sh
CHECK-SKILL audit: PASS — 0 errors, 2 warning(s)

$ bash plugins/skill-quality/scripts/check-evals-quality.sh plugins/claude-config/skills/audit/evals/evals.json
check-evals-quality: PASS (0 warning(s) across 1 file(s))

$ npx markdownlint-cli2 plugins/claude-config/CHANGELOG.md "plugins/claude-config/skills/audit/**/*.md"
Summary: 0 issues in 0 files

$ bash scripts/check-changelog-parity.sh --check && --check-order && --check-bump origin/main
Every versioned plugin has a CHANGELOG.md …
All 75 changelog(s) read newest-first with no duplicate versions.
Every plugin whose version changed vs origin/main has a '## [<version>]' CHANGELOG.md entry.
```

The two `check-skill.sh` warnings (SKILL.md over the 200-line soft
target; no Gotchas surface) are pre-existing on this skill, not
introduced here.

## Security review note

`check-hook-coverage.sh` is a new **read-only** surface. It widens what
the skill reads — the user-scope `settings.json`, the installed-plugin
registry, and each enabled plugin's hook config — and adds no write, no
network call, and no execution. It **never runs a hook command**; hook
commands are printed as strings only. It grants nothing: no permission
rule, no `allowed-tools` entry, no new hook. The one judgment it
deliberately does not make is whether an enumerated hook *covers* a
permission family; that stays with Category B against the three
preconditions in `required-permissions.md`, so the new data cannot by
itself downgrade a security finding.

`plugin.json` bumped 0.33.0 → **0.34.0** (new surface + behavior change)
with a matching CHANGELOG entry.

## Related

Closes #2274
Closes #2275

Inbox items: `20260810-225906-claude-config-category-g-unmeasurable`
(#2274), `20260810-225905-claude-config-audit-category-b-hook-blindness`
(#2275).
Ledgers:
`.work/handoff-inbox-batch-4/ledgers/I3-claude-config-category-g.md` §
B1–B5 · `.../I2-claude-config-category-b.md` § A4, A2, MN1.

Adjacent, not duplicated: #1404 (closed) shipped the measurement engine
Category G now points at; #2216 owns its Windows runtime; #2023 / #2034
/ #1271 own the marketplace-wide budget program, and no repo-wide
aggregate is quoted here on purpose. #1598 is A2's precedent on the Read
deny table in the same file. #2185 landed MN1's partial fix (Category F
only).

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.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.

1 participant