Skip to content

Commit b293df8

Browse files
authored
fix: catch C-quoted filenames in four probes, close a one-way parity 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.
1 parent 02e1d8b commit b293df8

18 files changed

Lines changed: 215 additions & 22 deletions

File tree

plugins/claude-ops/.claude-plugin/plugin.json

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

plugins/claude-ops/CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,20 @@
33
All notable changes to the `claude-ops` plugin are documented here. Format follows
44
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.
55

6+
## [0.38.18]
7+
8+
### Fixed
9+
10+
- **`observability`: the repo-slug failure token collided with real data.** 0.38.17 made the
11+
`unknown` fallback reachable. It stayed ambiguous: a repository whose toplevel directory is
12+
literally named `unknown` rendered the same string as a `git rev-parse` that failed, so the
13+
reader could not tell a working probe from a broken one. Verified by execution, both states
14+
rendered `unknown` byte for byte. The failure case now renders `(git toplevel unavailable)`,
15+
matching the parenthesized convention the rest of the fleet's probes use, which no directory name
16+
in practice collides with. A repository named `unknown` still renders `unknown`, now
17+
unambiguously. Nothing in the plugin reads the injected value, so no consumer changes. The skill
18+
declares no `allowed-tools`, so no grant changed. LIVE and PRE-EXISTING. Minor.
19+
620
## [0.38.17]
721

822
### Fixed

plugins/claude-ops/skills/observability/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ metadata:
1313
## Pre-computed context
1414

1515
Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"`
16-
Repo slug: !`git rev-parse --show-toplevel >/dev/null 2>&1 && git rev-parse --show-toplevel 2>/dev/null | sed 's|.*/||' || echo "unknown"`
16+
Repo slug: !`git rev-parse --show-toplevel >/dev/null 2>&1 && git rev-parse --show-toplevel 2>/dev/null | sed 's|.*/||' || echo "(git toplevel unavailable)"`
1717
ccusage availability: !`command -v npx >/dev/null 2>&1 && echo "npx present" || echo "npx MISSING"`
1818
Hook event log: !`bash "${CLAUDE_PLUGIN_ROOT}/skills/observability/scripts/probe-observability-state.sh" --hook-events 2>/dev/null || echo "unknown"`
1919
OTEL collector :4318: !`bash -c 'source "${CLAUDE_PLUGIN_ROOT}/skills/observability/otel/net-probe.sh" && port_status 4318' 2>/dev/null || echo unknown`

plugins/code-tidying/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
33
"name": "code-tidying",
4-
"version": "0.14.14",
4+
"version": "0.14.15",
55
"description": "Code tidying and comment hygiene: /code-tidying:tidy proactively hunts a rotated, glob-scoped lane for Beck-style tidyings under a research-backed scope budget and ships one tight PR; /code-tidying:batch-simplify sweeps a time window, a branch, or an entire repository through grouped, dependency-ordered simplification waves with a never-drop deferred-items contract; /code-tidying:dissolve-comments enforces self-describing expressive code over a diff or target, widening to the branch diff and then the whole repository when the tree is clean — deletes zero-information comments, dissolves code-expressible ones into names and structure behind a tests gate (safe mode restricts applied edits to removals), and keeps only terse load-bearing comments code cannot express; /code-tidying:audit-comment-residue is a read-only classifier that flags history, plan, conversational, and ticket/PR residue in code comments for author-applied deletion; /code-tidying:audit-dead-code is a read-only whole-repo dead-code hunter running four labelled lanes of unequal confidence (knip for TS/JS, vulture for Python, gopls for Go, and a portable grep lane for shell and other symbol languages), adjudicating every candidate against dynamic-usage evidence into a dead, uncertain, or alive verdict. Project-specific tidy lanes are scaffolded into a tracked .claude/tidy-lanes/ config folder by a re-runnable setup skill.",
66
"author": {
77
"name": "Melodic Software",

plugins/code-tidying/CHANGELOG.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,58 @@
33
All notable changes to the `code-tidying` plugin are documented here. Format follows
44
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this plugin uses semantic versioning.
55

6+
## [0.14.15]
7+
8+
### Fixed
9+
10+
- **`dissolve-comments`'s preview filter dropped every C-quoted filename.** It parsed the porcelain
11+
record with `awk '{print $NF}'`, which splits on whitespace and hands the extension grep a
12+
trailing `"` on any path `git` chose to quote. Proven by execution against a repository holding
13+
seven `.py` files whose names carry, respectively, nothing unusual, a space, a single quote, a
14+
double quote, a semicolon, a pipe, and a newline: the old parse showed 4 of 7, the `-z` NUL parse
15+
it now uses shows 7 of 7. That parse is `audit-comment-residue`'s, adopted verbatim rather than
16+
re-derived, because this site already parses fields and the NUL form is correct for every
17+
filename instead of one class. IMPACT IS BOUNDED and this entry should not overstate it: the line
18+
is a PREVIEW capped at 10 whose own body already instructs the model to re-enumerate the full set
19+
with `git status --porcelain -z` at scope time, so the defect misled the model's first look at
20+
the tree rather than the scope it actually triages. LIVE and PRE-EXISTING: the parse predates
21+
0.14.12 and 0.14.13, which reworked the shape around it and not the filter. The skill declares no
22+
`allowed-tools`, so no grant changed. One residual, noted rather than fixed: under `-z` a filename
23+
containing a newline reaches the grep as two lines, so it is detected but rendered as its tail.
24+
`head -10` and the re-enumeration instruction already tell the reader the render is partial.
25+
- **The brace group 0.14.13 installed is intact and still correct.** Re-ran the state matrix over
26+
both probes in this plugin (healthy, at cap, no match, clean tree, outside a repo, each under
27+
`set -o pipefail` and `set +o pipefail`, 20 cells): all exit 0, the at-cap cells render exactly
28+
the cap with no failure token, and only the outside-a-repo cells render one. The SIGPIPE
29+
inversion 0.14.13 removed has not come back.
30+
31+
### Added
32+
33+
- **`audit-comment-residue`'s SKILL.md parity check is now two-directional.** It compared
34+
`detect.sh`'s audited set against the preview in one direction only: a file `detect.sh` audits
35+
that the preview misses. It could not see the opposite failure, a preview emitting a path
36+
`detect.sh` never audited. Demonstrated by mutation against the shipping tree: dropping the
37+
rename skip from the SKILL.md `awk` program makes the preview print the rename's source record
38+
offset by the status prefix, a path naming no file, and the suite still passed 53 of 53. The
39+
reverse loop now asserts that every path the preview emits is one `detect.sh` audited. Re-running
40+
that exact mutation with the new loop in place fails 1 of 54, naming the phantom path; reverting
41+
the mutation passes 54 of 54. No existing assertion was weakened or removed. PRE-EXISTING blind
42+
spot, test-only change, no shipped behavior touched.
43+
44+
### Changed
45+
46+
- **Both probe labels no longer assert `empty = none`.** `audit-comment-residue` and
47+
`dissolve-comments` rendered their filtered preview under a label positively claiming that an
48+
empty render meant no matching files. The probe never established that. The brace group's closing
49+
`:` makes the outer `||` unreachable, so a failure INSIDE the group also renders empty: a filter
50+
binary off PATH, or a second `git` invocation that fails after the guard's copy succeeded, which
51+
models `index.lock` contention. Not a regression, the pre-0.14.13 shape was equally silent in
52+
both states, but the label was making a claim the plumbing cannot back. The labels now read
53+
`empty = none matched or the probe returned nothing`. Fixing the label rather than the plumbing
54+
is deliberate: closing those states needs a temp file or a second capture in a block that must
55+
stay free of `$`, which costs more than the honesty is worth. The parity test extracts the label
56+
by stem, so the wider parenthetical does not disturb it.
57+
658
## [0.14.14]
759

860
### Fixed

plugins/code-tidying/skills/audit-comment-residue/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ metadata:
1313
## Pre-computed context
1414

1515
Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"`
16-
Uncommitted code files (empty = none): !`git status --porcelain -z >/dev/null 2>&1 && { git status --porcelain -z 2>/dev/null | awk 'BEGIN { RS = "\0" } skip { skip = 0; next } { if (substr($0, 1, 2) ~ /[RC]/) skip = 1; print substr($0, 4) }' | grep -Ei '\.(cs|ts|tsx|js|jsx|py|sh|ps1|go|rs|java|rb|lua|sql|c|h|cpp|hpp|yaml|yml|toml)$' | head -10; :; } || echo "(git status unavailable)"`
16+
Uncommitted code files (empty = none matched or the probe returned nothing): !`git status --porcelain -z >/dev/null 2>&1 && { git status --porcelain -z 2>/dev/null | awk 'BEGIN { RS = "\0" } skip { skip = 0; next } { if (substr($0, 1, 2) ~ /[RC]/) skip = 1; print substr($0, 4) }' | grep -Ei '\.(cs|ts|tsx|js|jsx|py|sh|ps1|go|rs|java|rb|lua|sql|c|h|cpp|hpp|yaml|yml|toml)$' | head -10; :; } || echo "(git status unavailable)"`
1717
Residue findings (sample): !`${CLAUDE_SKILL_DIR}/scripts/detect.sh 2>/dev/null | grep -E '^(Summary total:|Finding shape:)' | head -20 || echo "none"`
1818

1919
## Purpose

plugins/code-tidying/skills/audit-comment-residue/scripts/detect.test.sh

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ assert_not_contains "worktree-column rename is not reported as files=0" "$worktr
245245
assert_contains "worktree-column rename resolves to the new path" "$worktree_rename_out" "Summary file: new.py"
246246

247247
# --- 10. SKILL.md pre-computed-context parser stays at parity with detect.sh (#3126) ----
248-
# SKILL.md's `Uncommitted code files (empty = none):` line re-implements the porcelain parse to
248+
# SKILL.md's `Uncommitted code files ():` line re-implements the porcelain parse to
249249
# preview targets to the model. A divergence there is a false negative on the same surface, so the
250250
# program is EXTRACTED from SKILL.md and executed rather than being restated here — a copy
251251
# would pass while the real line rotted. Fixture names force C-quoting through an embedded
@@ -256,8 +256,8 @@ if [[ ! -f "$SKILL_MD" ]]; then
256256
fail "SKILL.md located for parity check" "file at $SKILL_MD" "missing"
257257
else
258258
# Pull the awk program out of: ... | awk '<program>' | grep ...
259-
# Anchor on the label stem, not the whole label: the label carries a parenthetical
260-
# (`(empty = none)`) so the fallback can be read as a failed probe rather than an empty filter.
259+
# Anchor on the label stem, not the whole label: the label carries a parenthetical naming
260+
# what an empty render can mean, so the fallback reads as a failed probe, not an empty filter.
261261
skill_awk="$(sed -n "s/^Uncommitted code files[^:]*:.*| awk '\(.*\)' | grep .*$/\1/p" "$SKILL_MD")"
262262
if [[ -z "$skill_awk" ]]; then
263263
fail "SKILL.md awk program extracted" "non-empty program" "no match — line shape changed"
@@ -306,9 +306,17 @@ else
306306
assert_not_contains "SKILL.md parser leaves no rename arrow" "$skill_out" ' -> '
307307
assert_not_contains "SKILL.md parser leaves no escaped quote" "$skill_out" '\"'
308308

309-
# Parity with detect.sh over the same tree: every code file detect.sh audits must also
310-
# appear in the preview, or the model is shown a tree the audit does not agree with.
309+
# Parity with detect.sh over the same tree, checked in BOTH directions. Forward: every code
310+
# file detect.sh audits must also appear in the preview, or the model is shown a tree the
311+
# audit does not agree with. Reverse: every path the preview emits must be one detect.sh
312+
# audited, or the model is shown a target that does not exist. A forward-only check cannot
313+
# see an over-reporting preview: drop the awk rename skip and the src record is printed
314+
# offset by the status prefix ("...amed-src.py"), a path naming no file, while every forward
315+
# assertion still passes. REPO13 holds only code files, so the two sets must match exactly;
316+
# a non-code fixture added here would need the preview's extension filter extracted
317+
# alongside the awk program before the reverse direction stays true.
311318
detect_out="$(cd "$REPO13" && bash "$DETECT")"
319+
mapfile -t audited_paths < <(printf '%s\n' "$detect_out" | sed -n 's/^Summary file: \(.*\) | T1=.*$/\1/p')
312320
parity_ok=1
313321
while IFS= read -r audited; do
314322
[[ -z "$audited" ]] && continue
@@ -325,6 +333,27 @@ else
325333
else
326334
fail "SKILL.md preview covers every file detect.sh audits" "full coverage" "see above"
327335
fi
336+
337+
reverse_ok=1
338+
while IFS= read -r previewed; do
339+
[[ -z "$previewed" ]] && continue
340+
matched=0
341+
for audited in ${audited_paths[@]+"${audited_paths[@]}"}; do
342+
if [[ "$previewed" == "$audited" ]]; then
343+
matched=1
344+
break
345+
fi
346+
done
347+
if [[ "$matched" -eq 0 ]]; then
348+
reverse_ok=0
349+
printf ' preview emitted a path detect.sh did not audit: %s\n' "$previewed" >&2
350+
fi
351+
done < <(printf '%s\n' "$skill_out")
352+
if [[ "$reverse_ok" -eq 1 ]]; then
353+
pass "SKILL.md preview emits no path detect.sh did not audit"
354+
else
355+
fail "SKILL.md preview emits no path detect.sh did not audit" "no extra paths" "see above"
356+
fi
328357
fi
329358
fi
330359

plugins/code-tidying/skills/dissolve-comments/SKILL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ metadata:
1212
## Pre-computed context
1313

1414
Current branch: !`git branch --show-current 2>/dev/null || echo "unknown"`
15-
Uncommitted code files (preview, first 10; empty = none): !`git status --porcelain >/dev/null 2>&1 && { git status --porcelain 2>/dev/null | awk '{print $NF}' | grep -Ei '\.(cs|ts|tsx|js|jsx|py|sh|ps1|go|rs|java|rb|lua|sql|c|h|cpp|hpp|yaml|yml|toml)$' | head -10; :; } || echo "(git status unavailable)"`
15+
Uncommitted code files (preview, first 10; empty = none matched or the probe returned nothing): !`git status --porcelain -z >/dev/null 2>&1 && { git status --porcelain -z 2>/dev/null | awk 'BEGIN { RS = "\0" } skip { skip = 0; next } { if (substr($0, 1, 2) ~ /[RC]/) skip = 1; print substr($0, 4) }' | grep -Ei '\.(cs|ts|tsx|js|jsx|py|sh|ps1|go|rs|java|rb|lua|sql|c|h|cpp|hpp|yaml|yml|toml)$' | head -10; :; } || echo "(git status unavailable)"`
1616

1717
## Variables
1818

plugins/docs-hygiene/.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
33
"name": "docs-hygiene",
4-
"version": "0.21.26",
4+
"version": "0.21.27",
55
"description": "Documentation-hygiene toolkit: compress (flavor-trim markdown with a semantic-diff safety net), audit-noise (classify markdown noise), extract-ssot (deduplicate repeated content into a single source of truth), audit-encapsulation (detect citations into skill-private surfaces), rename-references (sweep stale references after renames), audit-derivability (classify whether a whole document earns its existence \u2014 could a fresh agent re-derive it from the code?), audit-progressive-disclosure (grade instruction files against a load-tier model for split opportunities and hub/spoke disclosure defects), write-for-agents (authoring-time doctrine that fires while agent-consumed markdown is being written), and write-for-humans (the same moment for the other reader \u2014 end-user READMEs, RFCs, release notes and guides \u2014 resolving the consuming project's own style guide first).",
66
"author": {
77
"name": "Melodic Software",

0 commit comments

Comments
 (0)