fix: close the extract-ssot sweep's open remainder, including a fail-closed gate that failed open - #3468
Merged
Conversation
ADR 0018 makes the plugin the encapsulation boundary for citation: a doc names a public `/plugin:skill` invocation, never a filesystem path into another plugin's private tree, because an installed plugin may not be on disk at that path. The extract-ssot sweep record declared its encapsulation roster a floor, not a total: it carried two `docs/**` sites, a later pass found three more of the identical shape on no roster, and none was fixed. This re-derives the shape across all of `docs/**` rather than trusting the roster, and closes 16 citations at 12 sites in 8 files. The five already named plus: the dynamic-context row `docs/PLUGIN-PHILOSOPHY.md`'s Convention registry gained in the same commit that recorded the floor, written in the bare plugin-relative form ADR 0018 names as its real defect class; two conformance rows in `config-cascade`; one in `pre-pr-ordering`; three in `native-references`, two of which are `V-review-13` and `V-review-14` standing open on the predecessor's 34-item L4 roster since 2026-08-26; and one adopter- row detail in `detector-findings` reaching into a private `scripts/lib/`. The convention READMEs are the sharp case. Nineteen plugin surfaces fetch `topic-docs` over raw.githubusercontent.com at run time, ten fetch `detector-findings`, five `config-cascade`, three `permission-rule-hygiene`. For that reader the repo is not on disk at all, so a `../../../plugins/...` link was dead exactly where the convention binds. `topic-docs` is the sharpest: the paragraph arguing that a `SKILL.md` "cannot defer at runtime to a document the consuming repo does not have" made its case with two links that were precisely that. Eighteen further in-shape citations are kept, with reasons, because each is evidence about this checkout rather than an address for an obligation: worked examples whose content the citing doc already states, `scripts/` entry-surface pointers the public-surface contract's own carve-out permits, generated regions and the store behind them, and dated changelog entries quoting a citation as it stood. Generated views and the dated records under `docs/specs/`, `docs/adr/` and `docs/topics/` are out for the same reason. Changelog entries follow the `loop-lane` 9.0.1 and `detector-findings` 2.7.1 precedents and each convention's own bump rule: `topic-docs` 2.5.3 and `detector-findings` 2.8.1 as patches, `permission-rule-hygiene` 1.3.1 and `loop-lane` 9.0.2 as corrective patches, `config-cascade` as an unversioned Implementers-table entry per its own no-contract-change precedent, and `native-references` gains the `CHANGELOG.md` its Versioning section promised. `pre-pr-ordering` and `docs/PLUGIN-PHILOSOPHY.md` declare no version and get no entry. `permission-rule-hygiene`'s changelog preamble also stops naming `permission-hygiene`, a skill renamed to `audit-permission-grants` in #371 and not carried along since. The sweep record's recall-limits bullet now states what closed and what was kept, so the floor reads as resolved rather than open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
… the probe
The extract-ssot sweep record left four filtered-probe injections open on purpose and
named a fifth under its recall limits. All five collapsed two meanings into one
rendering: `probe | filter | head -N || echo "none"` binds the `||` to the pipeline,
whose exit status is `head`'s, and `head` exits 0 on empty input, so the fallback never
ran and a failed `git status` rendered empty. `compress` inverted it: its fallback sat
inside the brace group behind the filter, where `grep` exits 1 both when git failed and
when nothing matched, so `none` printed for both.
Each line now heads an `&&` list with a status-only run of its own probe:
git status --porcelain >/dev/null 2>&1 && git status --porcelain 2>/dev/null \
| <filter> | head -N || echo "(git status unavailable)"
so the `||` fires on probe failure alone. Each site keeps its own filter, cap and label
noun; each label gained `empty = none`. The probe runs twice because the capture-first
alternative in `audit-derivability` needs a `$` expansion, and a `$` expansion other than
a bare `$HOME` in a pre-compute block makes a skill fail to load from a worktree-isolated
agent (`session-flow` 0.17.16). The added text introduces no `$`.
Proven by execution per site, in every state: outside a repository each prints
`(git status unavailable)`; with git absent from PATH and with a `.git` that is not a
repository, the same; inside a repository whose dirty files do not match the filter each
prints nothing; with 15 matching files and a cap of 10 exactly 10 print and the SIGPIPE
that reaches git does not leak into the `||`.
No `allowed-tools` grant changed: three of the five carry one, none of the three grants
`git status`, and the edit introduces no new binary.
Two `detect.test.sh` parity checks extract these lines out of `SKILL.md` by `sed` and run
them; their patterns were anchored on the old labels and now anchor on the label stem.
The `audit-comment-residue` porcelain extractor's capture reaches the first `|`, so it
now takes the whole `&&` list and evaluates the reachability guard as written.
docs/specs/extract-ssot-sweep-2026-08-28.md moves the cluster from open to applied,
records that the candidate shape it proposed was not taken and why, and records one
site left open: `rename-references`'s rename-pair probe has the same binding defect on
a `git diff` probe, where a filter matching nothing is the ordinary case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
…ecord An independent claims audit re-derived every count and citation in the merged record and found five more. Each was verified here against the repository before being fixed; the audit's own verifier could not run (a subagent has no Agent tool), so its verdicts arrived marked UNVERIFIED and I re-derived them myself. Two are structural defects in the resumable record, which is the file's whole job: - "Four of the factual defects ... are listed under Applied above" pointed at a section that names none of the four. That section covers the owner edits, the inline floor and the call-site normalizations; a resumer following the pointer found nothing. It now says where the detail actually is. - The paragraph on the two prompts claimed both "say in their own text that the quotation sits outside the byte-audited block". Neither does. The only sentence in either naming that block is "Two further reader-contract rules apply alongside the floor (outside the byte-audited block)", whose subject is those two rules. The conclusion held for a different reason -- section 6 binds three lane bodies and a prompt is not a lane -- but the support was a quote attached to the wrong subject, which is the identical defect this record was corrected for in round three, recurring one paragraph away. Three smaller ones. The discipline shared-method pointer was recorded at 14 sites; 15 skill bodies cite it and only 11 share one sentence form, so 14 matched neither population. The predecessor's cell was quoted as "41 instances, 34 edited" where it reads "41, 34 edited" -- faithful in meaning, not a quotation. And the check-skill.sh:414 cluster was recorded as four prose surfaces when a fifth carrier sat in a script header that a *.md-only grep could not see; all five are closed, only four were recorded as such. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
A second, independently-tooled derivation of the same shape found a file the first pass missed. Matching `<skill>/<subdir>/` and `<skill>/<file>` against the real skill-name roster, rather than only `plugins/*/skills/`, reaches `docs/MIGRATION-PLAYBOOK.md`'s dated `dometrain` security-review record, which cites `sync/scripts/update.sh` twice, `sync/SKILL.md` twice, and `grounding/SKILL.md` once. All five are keeps under the same test the rest of the roster uses: the record asserts what a named file contains at a named version, and re-triggers on the next version bump, so the path is the evidence rather than an address for an obligation. Two of the five are `scripts/` entry-surface cites the public-surface contract's carve-out permits outright. Nothing in the fix set moves. The kept count goes from 18 to 23, which is what the recall-limits bullet now says. The miss is worth recording rather than quietly correcting: the first pass keyed on the `plugins/` prefix, and a plugin-relative citation that never writes it was outside every one of its search shapes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
…ized last release
Both 0.21.23 and 0.14.12 opened by saying the prior release "normalized this plugin's
status probes", plural in one and unattributed in the other. Each plugin carried exactly
one: `rename-references` in docs-hygiene and `tidy` in code-tidying, both visible as the
single `{ git status --porcelain 2>/dev/null || echo "(git status unavailable)"; }` brace
form in the plugin. Each entry now names its one site.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
A post-merge security audit of 908902d found the defect that commit fixed for git-status probes still live in the rest of its class, at sites the sweep never scoped. The worst of them defeats a documented safety gate. plugins/knowledge/skills/video-digest/SKILL.md states: "STOP if the pre-computed context above shows MISSING for yt-dlp, ffmpeg, or ImageMagick. Cloud agents without the media toolchain fail closed." Its probes were `yt-dlp --version 2>/dev/null | head -1 || echo "MISSING ..."`. Because `||` binds to the pipeline and `head` exits 0 whatever the probe did, an absent yt-dlp rendered an empty string, never MISSING. The gate could not fire. A skill documenting itself as failing closed failed open. Verified by execution before and after: absent now renders MISSING, present renders the version. The same shape, with a fallback naming a failure the reader must act on, is fixed in course-digest (2 probes), ai-slop:audit, provenance:audit (2), firecrawl:update, and claude-ops:observability. Each uses the guard-first form firecrawl:firecrawl already shipped correctly, so the fleet gains no new idiom. No `$` expansion is introduced: the composed precompute block must stay verifiable to the worktree-isolation guard, which rules out the capture-first variant. Also fixes a grant 908902d left behind. It rewrote the two prototype skills' injections to begin with `{` while their line-6 grant remained the prefix rule `Bash(git status:*)`, whose leading token no longer matches. Narrowing, never widening, but a probe that silently stops running is the same class of silent failure as a fallback that cannot render. Sites whose fallback names an empty result rather than a failure -- `none`, `0` in session-flow:find-handoff, implementation:implement, claude-memory:audit -- are deliberately not touched here; they belong to the filtered-probe pass and are recorded with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
claude Bot
added a commit
that referenced
this pull request
Aug 28, 2026
…l-proof (#3469) #3468 fixed five filtered-probe injections so a failed probe could be told apart from a filter that matched nothing. The form it shipped is wrong under set -o pipefail, and wrong in the more damaging direction: it asserts a failure that did not happen. probe >/dev/null 2>&1 && probe | filter | head -10 || echo "(git status unavailable)" The && list takes the pipeline's exit status. Without pipefail that is head's, which is 0, and the guard works. With pipefail it is the last non-zero status in the pipeline, which two ordinary situations produce: grep matching nothing, and git taking SIGPIPE when head closes the pipe at the cap. Either fires the failure token on a healthy probe. That is worse than the defect #3468 removed: the shape it replaced only ever said none, while this one positively asserts git status was unavailable when it ran fine. Both modes reproduced against the shipped lines, then shown fixed. On a healthy repository whose dirty files do not match, and again on one with 3,000 dirty files, the 0.21.23 form prints (git status unavailable) and this one does not. Outside a repository this one still prints it, which is the case the token is for. The filter pipeline now sits in a brace group closed by :, a command that cannot fail, so the || is reachable only by the guard short-circuiting. No $ expansion is introduced; the composed pre-compute block must stay verifiable to the worktree-isolation guard. audit-comment-residue's parity test anchors its porcelain capture past '&& {' so it evaluates the data run rather than an unterminated brace group, and its capture was checked non-empty so the assertion still bites. Open and undocumented: whether Claude Code executes ! injections under pipefail. Neither the precompute convention nor the permissions docs say. The brace-group form is correct under both settings and the shipped form under only one, so this is the fail-safe direction regardless. Refs #3468, refs #3460.
kyle-sexton
pushed a commit
that referenced
this pull request
Aug 28, 2026
…date The 0.2.0 entry presents a Phase 6 measurement whose six figures are actually Phase 3 ones: 1,347 files / 525 candidates / 482 parsed / 43 declined / 0 expired / oldest 2026-04-08 all reproduce exactly at 33dccc5, the commit that introduces list-corpus.sh, on a clean tree with the scripts as they existed there. They were carried forward without re-measuring. Records the current baseline at 619199e with --as-of 2026-08-28: 1,352 tracked markdown after carve-outs (1,395 considered, 43 declined at path level), 535 candidates, 491 parsed, 44 declined at stamp level (20 month-name, 24 bare years), 0 expired at the 180-day default, oldest parsed stamp 2026-04-08, 9 findings at a 60-day window. Two of those figures expire, so the as-of date is pinned beside the commit: 0 expired holds only until 2026-10-05 on the current oldest stamp, and the 60-day finding count moves daily. A baseline recorded without one repeats the staleness this entry corrects. Also corrects the delta's attribution. It is not main moving across #3467 to #3469 — those contribute +1 in total, one file in #3468. #3467 adds 20 markdown files and contributes zero, all of them inside the excluded fixtures tree, which is why considered rises by 20 and the fixture decline goes 3 to 23 while the corpus is untouched. An earlier draft of this entry claimed the figures came from no commit at all. That replay covered only history reachable from main, which the squash-merge of #3467 had made exclude the originating build branch; the reflog held it throughout. The entry now records that a history replay bounded at a squash boundary cannot answer whether a number came from a commit. Refs #3465. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018tvKPLnWr7suxxAupU4p8A
kyle-sexton
pushed a commit
that referenced
this pull request
Aug 28, 2026
…a spaced skill dir #3469 fixed five filtered probes whose `||` fallback fires spuriously under `set -o pipefail`, and stopped there. The same shape is still live at eleven more sites across six plugins, four of them introduced by #3468 -- the commit immediately before the one that diagnosed the bug class. The mechanism is unchanged. `guard >/dev/null 2>&1 && probe | cap || echo TOKEN` takes the pipeline's exit status. Without pipefail that is the cap's, always 0. With pipefail it is the last non-zero status, which two ordinary situations produce: a filter matching nothing, and the writer taking SIGPIPE when the cap closes the pipe. Either fires the failure token on a healthy probe. ai-slop:audit is the worst and was live, not latent. detect.sh --show-config emits 10 lines against a head -8 cap, writing one at a time, so head always closes the pipe mid-run: PIPESTATUS reads 141 0. The probe rendered the full correct config and then appended "detector unavailable" beneath it. It reproduced from any directory with no configuration at all. knowledge is the one with a consequence beyond a wrong string. video-digest states "STOP if the pre-computed context above shows MISSING for yt-dlp, ffmpeg, or ImageMagick. Cloud agents without the media toolchain fail closed." #3468 fixed that gate's fail-open defect and shipped this inversion in its place, so the gate would refuse to run on a machine that HAS the toolchain. Whether the token fires is a race between the tool writing its banner and the cap closing the pipe, so it scales with output size. Measured here over 30 runs per size against a synthetic tool on PATH, installed and working: 41 lines fired 29/30, 5 lines fired 19/30, 1 line fired 0/30. So ffmpeg fails on nearly every run, magick (5 lines) is a genuine race, and yt-dlp (1 line) is latent by shape only. An intermittent false MISSING is harder to diagnose than a consistent one; all three are fixed regardless. Also fixed here, same shape: course-digest's two probes, firecrawl:firecrawl (pre-existing, and the idiom the fleet copied), provenance:audit's two detector probes, docs-hygiene:audit-derivability -- whose capture-first form protects only the git call, leaving its awk-and-head data run exposed past 20 dirty files -- and code-tidying:tidy, where a bare third "unknown" line was appended to well-formed output. Two of the eleven are latent by shape, not observed failures, and the provenance changelog says so plainly rather than claiming a live fix: list-corpus.sh --show-config emits 7 lines against a head -10 cap, and check-stamps.sh is piped into tail -3, which drains its input and cannot raise SIGPIPE. Both were verified to behave identically before and after in three states under both settings. Separately, ${CLAUDE_SKILL_DIR} is now quoted in ai-slop:audit and provenance:audit. Installed under a path containing a space, the unquoted expansion made a working detector report itself unavailable. firecrawl:update already quoted it; these now match. Pre-existing, but these commits are what made the token reachable at all -- before them the same failure rendered empty. Quoting changes the literal command string, and Bash permission rules are globs over that literal string, so the unquoted script grants no longer match the quoted invocations. Three companion rules are added, one per quoted script: Bash("${CLAUDE_SKILL_DIR}/scripts/detect.sh":*) in ai-slop, and the list-corpus and check-stamps equivalents in provenance. The unquoted rules are kept because both skill bodies still instruct unquoted invocation elsewhere. Each pair names one script under the same ${CLAUDE_SKILL_DIR} anchor with the same :* argument scope, so nothing is authorized that the plugin could not already run. No other grant changed: every other rewritten command keeps its original leading token, which is why code-tidying:tidy takes the ||-inside-the-brace-group form rather than the brace-group-first one. The remedy elsewhere is the brace group #3469 established: the data pipeline sits in { ...; :; }, closed by a command that cannot fail, so the || is reachable only when the guard short-circuits. No new idiom, and no $ expansion introduced beyond the quoted ${CLAUDE_SKILL_DIR}; audit-derivability keeps the pre-existing $s capture, which is the part that legitimately drives its ||. Every site proven by execution in three states -- tool or script absent, output under the cap, output over the cap -- with and without pipefail. The five sites #3469 fixed were re-verified at 3,000 dirty files under both settings and are correct as shipped, as is claude-ops:observability, whose sed drains its input. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
This was referenced Aug 28, 2026
Merged
claude Bot
added a commit
that referenced
this pull request
Aug 28, 2026
…a spaced skill dir (#3474) #3469 fixed five filtered probes whose || fallback fires spuriously under set -o pipefail, and stopped there. Two independent verifiers found the same shape still live at eleven more sites across six plugins, four of them introduced by #3468 -- the commit immediately before the one that diagnosed the bug class. The mechanism is unchanged. guard >/dev/null 2>&1 && probe | cap || echo TOKEN takes the pipeline's exit status. Without pipefail that is the cap's, always 0. With pipefail it is the last non-zero status, which two ordinary situations produce: a filter matching nothing, and the writer taking SIGPIPE when the cap closes the pipe. Either fires the failure token on a healthy probe. ai-slop:audit was live and unconditional. detect.sh --show-config emits 10 lines against a head -8 cap, writing them one at a time, so head closes the pipe mid-run every time; PIPESTATUS reads 141 0. The probe rendered the full correct config and then appended 'detector unavailable' beneath it, reproducible from any directory with no configuration at all. knowledge had a consequence beyond a wrong string. video-digest states 'STOP if the pre-computed context above shows MISSING for yt-dlp, ffmpeg, or ImageMagick. Cloud agents without the media toolchain fail closed.' #3468 fixed that gate's fail-open defect and shipped this inversion in its place, so the gate would refuse to run on a machine that HAS the toolchain. Measured over 30 runs each with the tool installed and working: ffmpeg 29/30 (41 lines), magick 19/30 (5 lines, a race), yt-dlp 0/30 (one line, never reaches the cap). After the fix, 0/30 at every size. Also fixed: course-digest's two probes, firecrawl:firecrawl (pre-existing, and the idiom the fleet copied), provenance:audit's two detector probes (latent by shape only, and its changelog says so rather than claiming a live fix), docs-hygiene:audit-derivability whose capture-first form protects only the git call, and code-tidying:tidy where a bare third 'unknown' was appended to well-formed output. One grant moved, and an earlier draft of this message denied it. Quoting CLAUDE_SKILL_DIR changes the literal command string, and Bash permission rules are globs over that string, so the unquoted rule stops matching the quoted command. Three companion quoted rules were added with the unquoted ones retained; same script, same anchor, same :* scope, nothing widened. The quoting itself fixes a real defect: installed under a path containing a space, the unquoted expansion made a working detector report itself unavailable. Roster completeness was re-derived by a method unrelated to the first grep, a parser tracking brace depth and quote state, which found zero of these eleven and nine survivors, all correctly-drawn declines or structurally immune. Open and undocumented: whether Claude Code runs ! injections under pipefail. The brace-group form is correct under both settings, so the exposure is avoidable regardless. Refs #3469, refs #3468, refs #3460.
claude Bot
added a commit
that referenced
this pull request
Aug 28, 2026
…five wrong keeps (#3475) #3468 closed 16 cross-plugin path citations under ADR 0018 and kept 23 on a test it never wrote down. Its own verifier reported '3 keeps refuted; 4 cannot verify' and that reasoning was unreadable, because the verifier ran as a remote session the agent could not read back. An independent verifier settled it: five are wrong, not three, and it found why this recurred. The dividing test #3468 applied -- fix a citation that is an ADDRESS for an obligation, keep one that is EVIDENCE about this tree at a moment in time -- appears nowhere in ADR 0018. Clause 2 reads unconditionally. But ADR 0018 is itself a docs/** file and path-cites plugin skill privates five times, quoting their contents inline. Either it violates itself on its own first page, or clause 2 is scoped to citations that function as an address. Only the second reading is coherent, and an unwritten rule is what let one commit rule two ways in two files. This writes it down, as an Amendment following ADR 0011's convention: the evidence/address test, scripts/lib being outside the entry-surface carve-out, and how plugin-level non-skill trees generalize. The five wrong keeps. shell-test-helpers cited a private scripts/lib/ path -- and that is a self-contradiction inside c66f26c itself, which added detector-findings 2.8.1 fixing the byte-identical shape and stating the ground, then kept it one file over. Three line and step pins are dropped as the same class 2.7.1 removed a :414 pin for; each citation already quotes its content inline, so the drop is lossless. records.json:3's keep-ground was false: the sweep kept it because generation overwrites hand-edits, but that store's own note says 'Hand-editable and human-gated'. Recorded as open remainder rather than swept: twelve citations c66f26c created itself, every fix having written a changelog entry quoting the path it removed, so the population is 16 fixed and 35 kept rather than 16 and 23; three trees excluded by fiat with no carve-out authorizing it; 51 citations into plugin-level non-skill trees; and a stale dometrain security record whose own version-bump re-trigger has fired unnoticed. A fifth review round found two more false claims, both stale statuses. '34 to 32' was wrong: re-deriving all 34 rows against the roster's own text test finds 22 already closed, twelve of them by #3380, the same commit that last wrote the roster and left its own 'all 34 re-verified' line standing over twelve citations it had just deleted. This change set republished that status in a file whose line 18 reads 'the check is the text, never the status and never the line number.' And the spec still shipped the probe shape #3469 removed, presented as verified in three states, two of which print the failure token under pipefail. Refs #3468, refs #3469, refs #3460. Sibling #3474.
claude Bot
added a commit
that referenced
this pull request
Aug 28, 2026
…test, and record the pipefail question (#3476) The last five findings from an independent security review of #3460/#3468/ #3469. #3474 and #3475 closed the probe-shape and citation halves; this closes the filter half and writes the open question into the convention. Four probes silently under-reported any filename git status --porcelain C-quotes. A plain space is enough to defeat grep '\.md$' and awk '{print $NF}'. Executed against a repo holding seven files per extension (plain, space, single quote, double quote, semicolon, pipe, embedded newline), each of the four went from catching 4 of 7 to 7 of 7. Two correct forms already shipped in the same two plugins, so this was drift rather than a missing idea: the .md sites took audit-noise's grep form, which keeps their leading token and so keeps their grant valid, and dissolve-comments took audit-comment-residue's -z NUL parse. The residual is stated in the changelogs rather than hidden. audit-comment-residue's parity test could not see an over-reporting preview. Its loop only checked one direction, so mutating the SKILL.md awk to drop its rename skip made the preview emit a phantom path while detect.sh audited five real ones, and the suite still passed 53/53. A reverse loop now asserts every previewed path is one detect.sh audited: the same mutation fails 1/54 naming the phantom, and reverted it passes 54/54. The forward loop is byte-identical. claude-ops:observability rendered 'unknown' both outside a repo and inside one whose toplevel directory is literally named unknown. The failure case now renders a distinct token. Six labels asserted 'empty = none' in two states the probe does not establish: the brace group's : makes the outer || unreachable, which also masks a filter-stage failure and a guard/data double invocation. The labels now say 'empty = none matched or the probe returned nothing'. No plumbing restructured; the ~25 unfiltered probes were left alone because their claim is already true. The durable fix is in precompute-context.md: whether Claude Code runs ! injections under pipefail is undocumented, the brace group is correct under both settings while guard && pipeline || token is correct under only one, and the brace group's own price is that an internal failure renders empty, which is why the label must carry it. The recheck trigger now covers shell options. Two derivations unrelated to the review's table both return the same six filtered probes: the true set is six, four defective, not five and three. #3474 fixed audit-derivability's SIGPIPE shape hours earlier and left its filter untouched, so both defects landed on one line; this branch was rebuilt onto current main and carries both, since committing either alone would have reverted the other. 70 pipefail cells all exit 0; affected-tests real exit 0 with 17/17 suites; changelog parity including --check-preserved across 306 headings; 3061 plugin files validated; markdownlint clean over 173 files. No grant widened or broken. Refs #3474, refs #3475, refs #3469, refs #3468, refs #3460.
kyle-sexton
pushed a commit
that referenced
this pull request
Aug 28, 2026
…, and close the dometrain staleness #3468 closed the ADR 0018 encapsulation floor by writing a convention CHANGELOG entry for each fix, and every entry quotes the path it removed. That manufactured twelve new instances of the shape the pass was sweeping. The sweep record listed them and stated they were fine; nothing ruled on them one at a time, so the arithmetic (16 fixed and 35 kept, not 16 and 23) rested on an assumption. Ruled individually against the test #3475 wrote into ADR 0018's amendment. All twelve are keep-correct and none is edited. Each sits in a dated entry whose claim is what a named file contained on that date, quoted as the string the entry removed, with the replacing invocation named in the same sentence, so no reader is sent to any of them for a rule. Three sub-rulings the class needed are recorded, because a bare "all twelve are evidence" is the assumption this was meant to replace. The three config-cascade rows quote plugin-relative forms that resolve against nothing, and the entry says so: clause 3 does not fire where the entry asserts non-resolution, since making them resolve would delete the finding. The other nine resolve on disk from their own implied base, checked one at a time. None carries a line or step pin, the part the amendment says rots first. Re-derived with a second expression over the fix commit's added lines rather than a scan of the files at rest. Same twelve, plus one the roster excludes on purpose: plugins/review/reference/topic-docs.md, a plugin-level non-skill tree that belongs to the pass the amendment routes it to, and a declared keep rather than a quoted removal. Separately, MIGRATION-PLAYBOOK's dometrain record was reviewed at 0.1.0 and the manifest reads 0.2.7, eleven releases later. Whether any of them added a trust surface is what the re-trigger clause turns on and no record answers it, which takes a review rather than a reading. The staleness is now stated in place with the re-review logged as owed. The review itself is deliberately not performed here. Refs #3468, refs #3475, refs #3460. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UmrZGdp1dgbZuPCy7tcRJo
claude Bot
added a commit
that referenced
this pull request
Aug 28, 2026
…, and flag a fired security re-trigger (#3477) Every one of #3468's ADR-0018 citation fixes wrote a CHANGELOG entry quoting the path it removed, creating twelve new instances of the shape it was closing. The population at main is 16 fixed and 35 kept, not 16 and 23. #3475 wrote the governing test into ADR 0018; this applies it to those twelve so a later re-derivation does not re-open them. All twelve are keep-correct, established row by row and anchored on text rather than line numbers. A dated changelog entry quoting a citation it removed is the evidence case exactly. No CHANGELOG was edited, so no convention bump is owed. Three sub-rulings the class needed, now written into the spec rather than left implicit. config-cascade's three rows are the only clause-3 question in the set: they are plugin-relative and resolve against nothing, and the entry says so in the same sentence. Clause 3 governs addresses a doc offers, not strings it quotes in order to report them broken, and forcing resolution would delete the finding. The other nine resolve on disk today, verified one path at a time across eleven distinct targets. No row carries a line or step pin, the part the amendment says rots first. One excluded on purpose and recorded so it is not re-opened: plugins/review/reference/topic-docs.md, a plugin-level non-skill tree the amendment routes to its own pass, which the entry declares a keep rather than quoting as removed. Separately, MIGRATION-PLAYBOOK.md records a dometrain security review performed at 0.1.0 and states that a version bump adding a new trust surface re-triggers it. The manifest now reads 0.2.7, eleven releases later. The note added states the reviewed version, the shipping version, that the ACCEPT below it describes 0.1.0 only, and that the re-review is owed and deliberately not performed here. It stops short of asserting a trust surface was added: that is the condition the clause turns on and cannot be settled by reading the page. Two checks were run to avoid claiming otherwise and neither settles it, which the note says. The spec's dometrain entry pinned MIGRATION-PLAYBOOK.md:943, a line this change's own edit would have invalidated. Re-anchored on the record's heading text. That decay rule has been violated three times across this sweep; this is the first time it was caught before landing. Recorded because two earlier claims about it were wrong in opposite directions: this worker's definition declares the Agent tool and the harness withheld it at spawn, so nesting is gated by depth realized as pre-launch tool omission. There is no refusal string because there is no call to refuse. Refs #3476, refs #3475, refs #3474, refs #3469, refs #3468, refs #3460.
claude Bot
added a commit
that referenced
this pull request
Aug 28, 2026
…reopened it three times (#3478) The L4 roster's Group 2, eight intra-plugin path-form citations, was the last block anyone believed open. All eight were already closed, by 6c7a103 (#3380) on 2026-08-26 -- the same commit that wrote the roster. Its own message names what it did: eight citations written with an implied base of the plugin root while the real base was reference/, none of which resolved for any reader. Verified one at a time against the roster's own text test. All eight now read ${CLAUDE_PLUGIN_ROOT}/skills/<skill>/<path> and all seven distinct targets exist on disk. Zero plugins/** files edited, so no bump and no plugin changelog entry: nothing needed changing. The reason four separate passes each re-derived this roster from scratch is one sentence. #3380 fixed 32 of 34 rows and left its summary asserting that all 34 path:line citations were re-verified on 2026-08-26 and every one resolves -- standing over citations it had just deleted. A fifth-round audit caught that for Group 1, where 22 of 32 were already closed and twelve of those by #3380 itself. This closes the same hole for Group 2. The generalizable rule is now in the sweep spec: a record that fixes findings and updates its own summary in the same commit must update the summary, or the summary outranks the fix for every later reader. The roster's inventory rows are left verbatim, per its own decay rule that the inventory is the part that cannot be re-derived, and per the precedent that #3474 and #3475 both closed rows without touching the file. Only an additive closure stamp is appended. On whether ADR 0018 reaches this class, the honest answer is that its encapsulation half does not. Clause 1 legalises intra-plugin citations and names this exact citing surface. Clause 2 cannot apply, because both files ship inside one plugin so the runtime absence motivating it cannot occur, and the 2026-08-28 amendment's fix-an-address / keep-evidence test divides clause 2 applications only. Only clause 3 reaches Group 2, and clause 3 is a resolvability rule rather than an encapsulation one: had these been open, the remedy would have been path form and nothing else. The clause that earned its keep is the ADR's own observation that proximity did not prevent them. A second derivation making no reference to the roster resolved every citation token in every plugin-level reference/, context/ and agents/ tree plus every plugin README against the base its own form implies: 52 tokens, 0 clause-3 failures. Group 3's two anchors were also closed by #3380. 34 closed, 0 open. No fresh-context verifier reviewed this diff. Nested spawning is unavailable at this depth: the Agent tool is withheld pre-launch, so there is no call to refuse and no refusal string. A second mechanical derivation was substituted and every line reference re-checked against the live tree, which is weaker than a fresh context and is recorded as such. Refs #3477, refs #3476, refs #3475, refs #3474, refs #3469, refs #3468.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No linked issue
Summary
#3460 merged a whole-repo
extract-ssotsweep and recorded, in its own resumable spec, three things it had deliberately left open. This closes them. Four independent agents then audited the merged commit — two remediating, two auditing with the author's account withheld — and one of them found that the sweep's own convention had a live consequence nobody had swept for.The headline is not a documentation fix.
plugins/knowledge/skills/video-digest/SKILL.md:115reads "STOP if the pre-computed context above shows MISSING for yt-dlp, ffmpeg, or ImageMagick. Cloud agents without the media toolchain fail closed." Its probes wereyt-dlp --version 2>/dev/null | head -1 || echo "MISSING …".||binds to the pipeline andheadexits 0 regardless, so an absentyt-dlprendered an empty string, neverMISSING. The gate could not fire. A skill documenting itself as failing closed failed open.Fix
Failure-token fallbacks that could never render, six plugins.
video-digest(3 probes, the gated one),course-digest(2),ai-slop:audit,provenance:audit(2),firecrawl:update,claude-ops:observability. Each now uses the guard-first shapefirecrawl:firecrawlalready shipped correctly, so the fleet gains no new idiom. Proven by execution on both branches: tool absent renders the failure token, tool present renders the real value. No$expansion is introduced — the composed precompute block must stay verifiable to the worktree-isolation guard, which rules out the capture-first variant.A grant #3460 left behind. It rewrote the two
prototypeskills' injections to begin with{while their line-6 grant stayed the prefix ruleBash(git status:*), whose leading token no longer matches. Narrowing, never widening — but a probe that silently stops running is the same class of silent failure as a fallback that cannot render. Upstream brace-group decomposition could not be established frompermission-rule-hygiene/README.md, so an exact grant was added alongside rather than assumed.The five filtered probes #3460 deferred. Their
nonefallback conflated "the filter matched nothing" with "git did not run". Fixed ataudit-noise,audit-progressive-disclosure,compress,dissolve-comments,audit-comment-residue, each keeping its own filter, cap and label noun so the two meanings stay distinguishable.The ADR 0018 encapsulation floor, closed. The spec recorded two sites and declared the roster a floor. Re-derived across all of
docs/**with five independent search shapes: 16 citations at 12 sites in 8 files. Among themV-review-13andV-review-14, standing open on the predecessor's 34-item roster since 2026-08-26 (34 → 32).The sharpest one:
docs/PLUGIN-PHILOSOPHY.md:616— the Convention registry row #3460 itself added — was written in the exact bare unresolvable form ADR 0018 names as its real defect class. The sweep introduced a fresh instance of the class it was closing, in the owner doc, in the same commit. Now/playbooks:skill-authoring.23 citations were kept, on a stated test: fix when the citation is the doc's address for an obligation, so the binding fails where it binds; keep when it is evidence about this tree at a moment in time. The kept set is recorded as a judgment set so it can be overturned.
Five more false claims in the merged record, found by a fourth review round and each re-derived before fixing. Two are structural: "listed under Applied above" pointed at a section naming none of the four clusters, and the paragraph on the two prompt files attributed to them a statement neither makes — the identical wrong-subject quote defect round three fixed, recurring one paragraph away. Three smaller: a pointer recorded at 14 sites where 15 cite it and only 11 share a sentence form; a paraphrase presented as a quotation; and a cluster recorded as four prose surfaces when a fifth sat in a script header a
*.md-only grep could not see.Verification
Execution proof for the gate, run outside any repo with the tool absent and then present:
What the security audit tried and could not break. A repo path containing spaces, quotes,
$(touch PWNED)and a backtick; files named; rm -rf victim,`touch PWNED3`, one with an embedded newline; avictim/directory to destroy;.git/HEADdeleted;gitabsent from PATH; 188 KB of porcelain throughhead -5for the SIGPIPE path. Nothing executed,victimintact, no artifact, no spurious fallback, no stderr leak. It also provedemit-findings.sh's diff is comment-only by md5-comparing the bodies fromset -euo pipefailonward.Honest limits. Both CI Claude review lanes returned HTTP 429 at every head of #3460 and never ran; this session's token gets 403 on
rerun-failed-jobs. Subagents in this environment have no Agent tool, so the nested verifiers this work was meant to use could not be spawned — two agents improvised (one hung a headless process, one spawned a session it could not read back) and returned findings marked UNVERIFIED. I re-derived every load-bearing finding myself before acting on it, which is what caught them. One remote verifier reported "3 keeps refuted; 4 cannot verify" on the encapsulation roster and its reasoning is unreadable from here; the kept set is recorded as a judgment set precisely so those can be overturned by a later pass.Related
Refs #3460, refs #3362, refs #3380. Closes the open remainder recorded in
docs/specs/extract-ssot-sweep-2026-08-28.md. Applies ADR 0018. Advances the predecessor's L4 roster indocs/specs/docs-hygiene-sweep-unapplied-remediations.mdfrom 34 to 32.Generated by Claude Code