Skip to content

perf(hook-utils): drop buffer_stdin startup subshells, repo_root tr, and emit_tel jq - #3732

Merged
kyle-sexton merged 4 commits into
mainfrom
cursor/shell-script-perf-phase1-c774
Sep 5, 2026
Merged

kyle-sexton merged 4 commits into
mainfrom
cursor/shell-script-perf-phase1-c774

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

No linked issue

Summary

Phase 1 of a measurement-first shell-script performance pass: cut remaining process creations on the always-on hook hot path in lib/hook-utils.sh (17 plugin copies) and stop spending jq on wired-sink {tool,subject,form} telemetry.

Fix

Discovery ranked remaining waste after the hook-performance program (#3623). Guardrails already measured and rejected dispatcher repo_root priming, per-guard dirname replacement, and merging hardcoded-path-check git probes. This PR takes the three leftover cuts that had a drift-immune counter and a matching repo pattern:

  1. hook::buffer_stdin timeout/slice wrappers. GNU Bash runs command substitution and process substitution in a subshell even for builtins. The suite already documented those two startup forks. _to helpers write into caller variables with printf -v.
  2. hook::repo_root git | tr. Same in-shell CR strip buffer_stdin already uses (${root//$'\\r'/}).
  3. hook::json_str_object_to + seven always-on Bash-guard emit_tel builders. Compact {tool,subject,form} is now builtin and byte-identical to jq -nc --arg …. The envelope was already builtin (perf(hook-utils): build the telemetry envelope and read file_path with builtins #3678). Unwired default path is unchanged (zero telemetry spawns).

Carrying plugin versions are bumped so consumers receive the synced copies. After merging main twice, those versions were re-bumped past the numbers main shipped in the meantime, and every earlier heading this branch introduced (including claude-ops 0.41.14) is kept so --check-preserved holds.

Phase 2 (not in this PR): context-guard resolver in-process; skill/stale-path grep short-circuit; affected-tests.sh manifest cache.

Verification

Headline counters on this Linux host (wall-clock refused as binding; Windows Git Bash remains the consumer cost model):

Subject Before After
buffer_stdin timeout/slice two subshells ($$$BASHPID) in-process ($$ = $BASHPID)
hook::repo_root spawns=2 [1 git 1 tr] spawns=1 [1 git]
emit_tel data object spawns=1 [1 jq] spawns=0
emit compact bytes {"tool":"Bash","subject":"git status --short","form":""} identical (cmp)

bash lib/hook-utils.test.sh: PASS=286 FAIL=0.

Critical guard contracts: block-no-verify 238, block-dangerous-git 479, block-hook-bypass 576, block-noncanonical-commit 213, block-convention-violation 70, block-windows-drive-tmp 214, block-exported-msys-pathconv 127, run-guards 66 — all FAIL=0.

scripts/affected-tests.sh --run: 142 shell suites passed; 9 selected non-shell suites named as NOT RUN (CI lanes). Zero FAIL lines.

Post-merge gates vs origin/main: sync-hook-utils.sh --check / --check-bump, check-changelog-parity.sh --check / --check-bump / --check-preserved / --check-order, check-shell-portability.sh, and shellcheck -x lib/hook-utils.test.sh all OK. Re-ran hook-utils.test.sh (286/0) on the merge.

Related

Refs #3623, #1403, #3678. Follows docs/conventions/hook-budget/README.md (budget never relaxes) and plugins/performance/reference/harness-integrity.md (spawn count over two-pass wall-clock).

Open in Web Open in Cursor 

…and emit_tel jq

buffer_stdin resolved timeout and slice through $( ) and process substitution.
GNU Bash forks a subshell for both even when the body is builtins only; those
two wrappers ran in distinct pids. The _to helpers write into caller variables
with printf -v instead.

hook::repo_root piped git through tr -d '\r'. The payload path already strips
CR in-shell; repo_root now does the same, so the census is 1 git instead of
git+tr.

hook::json_str_object_to builds compact string-field objects byte-identical to
jq -nc --arg. The seven always-on Bash-guard emit_tel builders use it, so a
wired HOOK_TELEMETRY_SINK no longer spends one jq per guard on
{tool,subject,form}.

Synced to all 17 carrying plugins; versions bumped for delivery.

Co-authored-by: ksextonmelodic <ksextonmelodic@gmail.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review September 4, 2026 22:21
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 4, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-04T22:31:33.977120Z af51275 Draft marked ready
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

cursoragent and others added 3 commits September 5, 2026 02:30
Main shipped the same patch numbers this PR used for the hook-utils
delivery. Keep main's notes under its headings and place the Phase 1
note under a new higher version so changelog-parity and --check-bump
still hold.

Co-authored-by: ksextonmelodic <ksextonmelodic@gmail.com>
…ions

Main shipped the same patch numbers as the previous rematch, plus a
guardrails 0.32.0 minor. Keep every heading from main and from this
branch's earlier bumps (including claude-ops 0.41.14) and place the
Phase 1 hook-utils note under a new higher version so --check-bump
and --check-preserved both hold.

Co-authored-by: ksextonmelodic <ksextonmelodic@gmail.com>
The print-form timeout wrapper takes no arguments; passing "$@" in the
BASHPID pin made SC2120/SC2119 fire on the existing no-arg calls.
printf -v destinations need an empty initializer for SC2154. grep -q
quiet matches in this file trip the grep -P portability token, so the
sites carry a portability-ok reason.

Co-authored-by: ksextonmelodic <ksextonmelodic@gmail.com>
@kyle-sexton
kyle-sexton enabled auto-merge (squash) September 5, 2026 02:50
@kyle-sexton
kyle-sexton merged commit 3ea592b into main Sep 5, 2026
20 checks passed
@kyle-sexton
kyle-sexton deleted the cursor/shell-script-perf-phase1-c774 branch September 5, 2026 02:57
kyle-sexton pushed a commit that referenced this pull request Sep 5, 2026
…eview-cf00b1

Resolves the context-guard CHANGELOG conflict. Both sides independently
claimed 0.7.41: main's #3732 shipped the vendored hook-utils.sh
buffer_stdin/repo_root change under that version, and this branch's
027d11a claimed it for the marker-write skip. Main's 0.7.41 is already
released, so it keeps the version it shipped under and this branch's entry
moves up to 0.7.42, with plugin.json bumped to match. Both entries survive,
per the branch's own stated convention that each merge places this entry
above the newer one under its own version.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VPLatLkg4329L8eyfxhuMa
kyle-sexton added a commit that referenced this pull request Sep 5, 2026
…er (#3736)

No related issue: the defect was found by an in-session fleet audit that
had no tracking issue; the audit's findings on other lanes are tracked
in #3685, #3721 and #3727.

## Summary

`zone-crossing-inject.sh` fires once per `UserPromptSubmit` and once per
`PostToolBatch`, so a three-batch turn that stays in one zone fired four
times and rewrote both state markers four times to the values they
already held. The armed-rank gate already suppressed the extra notices,
so this was wasted I/O rather than duplicate injection. Alongside it,
`hooks.json` lacked its documented top-level `description`.

## Fix

- Each marker is written only when its raw on-disk bytes differ from the
new value. The comparand is the read before normalization, so a
legacy-format marker is still rewritten in the current format and an
absent marker still latches on the first fire. Write ordering, the
fail-open posture and the `.zone` rollback on a failed `.armed` write
are unchanged; the rollback now fires only when this call was the one
that moved `.zone`.
- A new contract-test case (12c) pins the skip. Every existing assertion
reads stdout, which is identical whether the markers were rewritten or
skipped, so a revert to unconditional writes would have passed all 58.
The new case backdates both markers to 2000-01-01 and a reference file
to 2000-01-02, fires on an unchanged zone, and asserts neither marker is
newer than the reference, with a positive control that a mismatched
marker is still rewritten. `bash -nt` keeps it free of any `stat(1)`
dialect, and mtimes are not subject to root's permission bypass, which
is why it is not a chmod test.
- `hooks.json` gains a top-level `description`.
- `context-guard` bumps to 0.7.42 with a CHANGELOG entry. Main shipped
0.7.39 in #3718, 0.7.40 in #3727 and 0.7.41 in #3732 while this branch
was open; each merge placed this entry above the newer one under its own
version, so consumers on any of them still receive this change.

**Withdrawn in review.** The draft also dropped the four `hooks.json`
timeouts from 60 to 10, reasoning from the hooks reference's 30-second
`UserPromptSubmit` default and a 30 ms quiet-host measurement. The Codex
review on `5acee106` pointed at the 0.4.8 CHANGELOG entry and the README
paragraph that own that value: on Windows 11 / Git Bash with Defender
real-time protection, `zone-crossing-inject.sh` measured 22.0 s, and a
10-second cap was the exact defect 0.4.8 fixed, cancelling the advisory
on essentially every fire while still charging the full wall time.
Nothing on this branch re-measured that profile, so `3db2c457` restores
all four rows to 60 and the CHANGELOG entry says why they stay.

Five files: `zone-crossing-inject.sh`, `zone-crossing-inject.test.sh`,
`hooks.json`, `plugin.json`, `CHANGELOG.md`, all under
`plugins/context-guard/`. The interview Brief that scoped this lived in
`docs/topics/prompt-hooks-review/` on the branch and is pruned before
merge per the topic-docs convention; its substance is this body, and the
audit's measured baseline is routed to #3685.

## Verification

- `shellcheck` clean on the changed script and the test, plain and with
the repo `.shellcheckrc`; `check-shell-portability.sh origin/main` clean
on both.
- `plugins/context-guard/hooks/zone-crossing-inject.test.sh`: 58 pass, 0
fail at every head through `3db2c457`; 63 pass, 0 fail on `860d66d6`
with case 12c. Against `origin/main`'s hook the suite reads 61 pass, 2
fail, the two failures being exactly the skip assertions. The first
attempt at the fix failed one case (`0.7.1-format marker not
normalized`): comparing against the normalized read skipped the rewrite
of a legacy marker. Fixed by comparing against the raw read.
- `scripts/affected-tests.sh --run`: all selected suites pass at every
head (26 before the first merge, 27 on `f7cb502f`, 2 on the pruned head
`511b549f` once the Brief stopped driving reference-based selection, 2
on `fcc3f8cd`, all selected on `5acee106`, 2 on `3db2c457`, 3 on
`860d66d6`), zero `FAIL` lines. A mid-merge run had reported three
failures in guardrails, session-flow and source-control; on a clean
`origin/main` worktree the guardrails suite passes 213/0 and the
session-flow failure reproduces identically, and this branch touches
none of those plugins.
- On `fcc3f8cd`, after merging the #3727 squash: the three context-guard
suites pass 18/58/25, and main's new `scripts/check-killswitch-hoist.sh`
reports 15 of 15 guards clean.
- On `5acee106`, after merging the #3732 squash (resolved by the babysit
lane, see its comment on this PR): the three context-guard suites pass
18/58/25 again, `scripts/sync-hook-utils.sh --check` reports all 17
copies matching, and the diff against main is still exactly the files
above.
- On `3db2c457`: the three context-guard suites pass 18/58/25,
markdownlint clean on the CHANGELOG, `hooks.json` parses and every
`timeout` reads 60 again.
- `scripts/check-changelog-parity.sh` `--check-bump origin/main`,
`--check-order`, `--check-preserved origin/main`: all exit 0 on every
head.
- `scripts/check-contract-slice-prune.sh --check-diff origin/main`:
passes on `511b549f`. This was the one failing sub-gate of 56 on the
lint lane at `f7cb502f` (`contract-slice-diff`); every other hygiene
sub-gate was already green there.
- Review lanes on `5acee106`: security-review reported no findings; the
Claude review lane found no blocking issue and raised the missing skip
test, addressed in `860d66d6`; the Codex review raised the timeout
finding above, addressed in `3db2c457`.

## Related

- #3685: per-tool-call overhead thread; the audit's measurement caveats
on the hook-budget convention are posted there as evidence.
- #3718: shipped context-guard 0.7.39 (zone-gate deny-reason text).
- #3721: `block-hook-bypass` corpus sweep (PreToolUse lane).
- #3727: kill-switch hoist, PreToolUse half, with
`scripts/check-killswitch-hoist.sh`. Landed first and took context-guard
0.7.40.
- #3732: vendored `hook-utils.sh` startup-subshell trim. Landed second
and took context-guard 0.7.41, so this PR's entry sits above it as
0.7.42.
- Interview ledger: `.work/prompt-hooks-review/interview-checklist.md`
(untracked, memory tier).

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

https://claude.ai/code/session_01ECoW7mi7Zod61utjdyWtRE

---------

Co-authored-by: Claude <noreply@anthropic.com>
kyle-sexton pushed a commit that referenced this pull request Sep 5, 2026
Harness re-run after merging main (#3732 changed the library startup),
plus direct cold-cache, warm, and execve probes of cli-flag-verify: 980 ms
cold, ~120 ms warm, 88 failed execve per warm run, all the env PATH walk
for bash behind five #!/usr/bin/env execs.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019DaWEB8Daq1xAXy2Xj1Pme
kyle-sexton added a commit that referenced this pull request Sep 5, 2026
… cache hits in-process (#3747)

No related issue: the verifier-lane performance fix was routed to this
lane by the operator through the prompt-hooks session (relayed
2026-09-04, confirmed 2026-09-05) and tracked in the topic's contract
slice rather than an issue.

## Summary

The three guardrails PostToolUse verifiers were the most expensive hooks
on a markdown write. `skill-reference-verify` spent 468 of its 643 ms in
a loop that spawned two `jq` and two `tr` per plugin manifest (74
manifests, 296 processes) to build an index one process builds in 5 ms.
`cli-flag-verify` spawned the bundled verifier for every candidate even
on a warm 24 h cache, where the verifier's whole job is to read one file
and match one pattern; each spawn also paid the `#!/usr/bin/env` PATH
walk for `bash`. This PR is the first of three from the
hook-logging-pipeline topic (the PostToolUse kill-switch hoist and the
per-session pipeline follow as separate PRs, each opened after the
previous merges).

## Fix

1. **`skill-reference-verify.sh`**: `build_plugin_index` reads every
manifest in one `jq -r` emitting `<manifest>\x1e<name>\x1e<paths>` (raw
fields on non-whitespace separators, so a nameless manifest keeps its
empty field in place and a declared path arrives byte-identical);
manifests jq could not parse, and any behind a parse error in the batch,
take the per-manifest read the batch replaced, so a malformed sibling
changes nothing for the others.
2. **`cli-flag-verify.sh`**: a second pre-gate exits before the fragment
pipeline when the content names none of the scanned bins (the `-` gate
alone passed every hyphenated paragraph); cache hits are answered
in-process (one lazy `find` indexes the fresh cache files on the first
candidate whose bin is installed; the cached `--help` text is matched
with `=~`), and the verifier is spawned only on a miss.
3. **`lib/verification/cli-flag-cache.sh`** (new): the cache directory,
key, freshness window and flag-match pattern, defined once and sourced
by both the hook and the verifier. Review of the first cut found the
hook's hand-copied pattern had dropped the `[` terminator, so
`--flag[=VALUE]` was known cold and unknown warm; one definition is the
fix for that class.
4. **`verify-cli-flag.sh`**: `[[ -d ]]` before `mkdir -p`; the 24 h
freshness test compares against a reference file touched to that
timestamp (one POSIX `touch -t` in place of `find | grep`, `find` kept
for Bash before 5.0); the flag match is `=~` over the cached text
instead of `printf | grep -E`.
5. guardrails 0.32.1 to 0.32.2 with the CHANGELOG entry carrying the
before and after numbers (ADR 0003 rule 2).

## Verification

Measured on this Linux host with the topic's harness (N = 15) and direct
probes; the raw tables live in the topic's memory slice, the distilled
rows in FINDINGS.md on the branch:

| Row | Before | After |
| --- | --- | --- |
| `skill-reference-verify`, `.md` citing a skill | 642.9 ms (334.8 S) |
63.8 ms (33.8 S) |
| index loop, isolated (74 manifests) | 468.5 ms | 5.2 ms |
| `cli-flag-verify`, warm cache | 118.9 to 135.8 ms | 55.1 to 79.2 ms |
| `execve` per warm run (strace) | 152, 88 failed | 34, 11 failed |

Every new suite case was written first and run against the unmodified
hooks (`git stash` of the three scripts): 2 failures in the
skill-reference suite and 3 in the cli-flag suite on the committed
hooks, 0 after; the review-round cases (`--color[=WHEN]` on a warm
cache, a nameless manifest, a backslash in a declared path) likewise
fail on the first cut and pass on the head. Suites:
`skill-reference-verify.test.sh` 133/133, `cli-flag-verify.test.sh`
92/92 (was 123 and 70). `scripts/affected-tests.sh --run`: all 26
selected suites pass. `shellcheck -x` clean on the four scripts and both
suites. `scripts/check-changelog-parity.sh --check-bump origin/main`
exit 0. A fresh-context code review at high effort ran on the first cut;
all six of its findings were verified by reproduction and are fixed in
the head commit.

The topic's contract slice (`docs/topics/hook-logging-pipeline/`) rides
this branch and is pruned in the final commit before the ready flip, per
the topic-docs convention; the Brief and the phases this PR ships are
below.

<details>
<summary>Brief (TLDR, Goal, corrections) and Phases 0 and 1 of the
plan</summary>

### TLDR

Design the logging and telemetry pipeline for the marketplace's hooks,
and settle the upstream decisions that determine what it instruments.
Evidence base is FINDINGS.md, a measured read-only audit of the 26 wired
`PostToolUse` rows plus a doc-alignment pass. Interview complete: five
rounds, 21 questions, 19 answered and 2 deferred with named arbiters.
Register gate clean.

### Goal

Observability across every hook event, defaulting to off, costing
effectively nothing when off and as close to nothing as measurable when
on, with no surface left as a black box and every toggle reachable by
Claude on the operator's behalf rather than by hand.

### The verifier-lane allocation (confirmed after the interview closed)

The three PostToolUse verifiers on a markdown write citing a skill and
CLI commands cost about 825 ms (peer measurement, corroborated at 601 to
622 ms for the dominant guard alone). Four patch shapes, each measured
by the prompt-hooks lane and re-measured here before landing: the
batched index; the bin-name pre-gate; the in-parent cache read; the
three cache-path spawns in the verifier. Read ADR-0003 before touching
any of the three; it was written about exactly these guards.

### Phase 0: Baseline measurements [DONE]

Re-measure before touching anything (ADR-0003 rule 1). PR #3732 changed
the library's startup path after the handoff figures were taken, so
every number is re-captured. Harness at N = 15; cold cache for
`cli-flag-verify` with the directory cleared; PATH tax by `strace -f -e
trace=execve`; distilled figures into FINDINGS.md under "Re-measurement
2026-09-05".

### Phase 1: Verifier-lane fix (PR A) [DOING]

Four patch shapes, each with a repro-first test per `hook-precision`.
Timing assertions live in the harness rows recorded in FINDINGS.md; the
suites assert structure (spawn counts through PATH shims, equivalence),
which is stable on shared CI runners. 1a the batched index with a
fixture of three manifest shapes and a one-invocation assertion; 1b the
bin-name pre-gate with a zero-`sed` case on hyphenated prose; 1c the
in-parent cache read with a zero-verifier-spawn case on a warm cache and
identical findings; 1d the verifier's own cache path with the
`--save-dev` versus `--save-developer` boundary cold and warm. Harness
re-run; before and after in FINDINGS.md and the CHANGELOG entry;
guardrails patch bump above `origin/main`; `scripts/affected-tests.sh
--run`; shellcheck; PR as draft with the body contract; prune commit
before the ready flip.

Sanity check: harness "with refs" row for `skill-reference-verify` under
100 ms; the guardrails suites exit 0 with the shim-counter cases;
`scripts/affected-tests.sh --run` exit 0;
`scripts/check-changelog-parity.sh --check-bump origin/main` exit 0.

</details>

## Related

- Refs #3685, the hook-budget convention's spawn-dominated versus
work-dominated split, which this topic's measurements bear on
(`markdown-format` at 343 ms is Node startup, not spawns) and which this
PR does not change
- Refs #3719 and PR #3727, the PreToolUse half of the kill-switch hoist;
the PostToolUse half is the next PR from this branch's topic
-
`docs/adr/0003-verification-guards-earn-default-on-by-measured-precision.md`,
rule 1 (measure before shipping) and rule 2 (report the number in the
PR)
- `docs/conventions/hook-precision/README.md`, the repro-first
discipline every new case follows
- `lib/hook-utils.sh` and its 17 vendored copies are untouched

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

https://claude.ai/code/session_019DaWEB8Daq1xAXy2Xj1Pme

---
_Generated by [Claude
Code](https://claude.ai/code/session_019DaWEB8Daq1xAXy2Xj1Pme)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Sep 7, 2026
…ners (#3878)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
No linked issue

## Summary

Drop leftover process creations on the hottest Bash paths in this
marketplace: the shared hook library every always-on hook sources, the
always-on formatter Write/Edit paths (typos, ruff, biome, bash,
powershell, go, actionlint, eol, markdown), the always-on
desktop-notification Notification path, always-on guardrails verifiers,
and CI scanners that used to spawn once per file, per plugin, or per
allowlist entry.

## Fix

GNU Bash runs command substitution in a subshell even for builtins
(Command Substitution, [Bash Reference
Manual](https://www.gnu.org/software/bash/manual/html_node/Command-Execution-Environment.html);
[Greg's Wiki](https://mywiki.wooledge.org/CommandSubstitution)).
Cygwin's `fork` is a non-copy-on-write Win32 `CreateProcess` ([Cygwin
User's Guide, Process
Creation](https://ftp.cygwin.com/cygwin-ug-net/highlights.html)): "fork
will almost certainly always be inefficient under Win32."

### Shared hook library (`lib/hook-utils.sh`, synced to 17 carriers,
patch bump)

Same `_to` / in-process pattern as #3838, #3732, and #3678:

- `hook::json_escape_to` deletes residual C0 bytes with parameter
expansion instead of `printf | tr -d`
- `hook::emit_channels` writes through `_to` instead of
`$(hook::json_escape …)`
- Fractional `read -t` landed in bash-4.0-alpha (CHANGES).
`hook::read_supports_fractional_timeout` is `BASH_VERSINFO`; no TMPDIR
probe file
- `hook::notice_once` reads the marker with `read`, creates the
directory only when missing, and prunes stale markers once per process
- `hook::bash_parse_segments` walks `${cmd:i:1}` instead of `read -N1`
from a process substitution, and `$'…'` bodies decode through
`ansi_c_decode_to` (`printf -v`)
- `hook::repo_root_to` / `hook::repo_relative_path_to` write in this
shell so callers skip a leftover capture around git or builtins-only
work

Isolation `$(source …)` forks are unchanged (#3685).

### typos-format (always-on Write|Edit|NotebookEdit)

- Basename via `${FILE##*/}` (plus a backslash trim), not `basename(1)`
- `repo_root_to` / `repo_relative_path_to` instead of capture subshells
- Directory existence check instead of `$(cd && pwd)`
- `command -v typos` is no longer captured; the later exec looks the
name up on PATH

### Remaining always-on formatters (ruff, biome, bash, powershell, go,
actionlint, eol, markdown)

Same leftover class as typos-format, now applied to every always-on
formatter that still captured `_to` helpers or spawned `basename` /
leftover `cd && pwd`:

- `FILE_BASE` is `${FILE##*/}` (and a backslash trim)
- `repo_root_to` / `repo_relative_path_to` write in-process
- `$(cd && pwd)` canonicalize is an existence check on the path git
already answered (ruff, biome, bash-format EditorConfig walk)
- Nested `$(normalize_path "$(physical_path …)")` in powershell-format
uses the `_to` forms
- `command -v ruff|biome|goimports` is no longer captured
- markdown-format keeps physical `pwd -P` containment and config
discovery; leftover helper-capture and membership dirname on the
root-resolution path are gone

### desktop-notification (always-on Notification)

- Field extract fuses into `hook::buffer_stdin_to` so completeness and
`.notification_type` / `.message` share one jq process
- C0 stripping is parameter expansion, not `printf | tr`
- `repo_root_to` writes in-process; OSC 9 / BEL use `printf -v`;
`terminalSequence` uses `json_escape_jq_to`
- `uname` stays so tests can PATH-stub Darwin; git for `repo_root` stays

### guardrails verifiers (always-on PostToolUse / PreToolUse)

- `skill-reference-verify`, `stale-path-verify`, and `cli-flag-verify`
call `repo_root_to` / `repo_relative_path_to` in-process
- `hardcoded-path-check` and `secret-pattern-detection` use
`normalize_path_to` instead of leftover `$(hook::normalize_path)`
captures
- Isolation `$(source …)` forks are unchanged (#3685)

### CI scanners

- Orphaned-fixture scan: one `*.test.*` index, cached `evals.json`
`files[]`, in-shell ERE escape. Unquoted `\\` matches one backslash (a
quoted `'\\'` arm is two chars and leaves `\b` as a word boundary)
- Purged-em-dash scan: one `git ls-files -z` with every `:(glob)`
pathspec; in-process component-wise attribution so `*` cannot cross `/`.
`--list` stdout is byte-identical to origin/main
- Cross-plugin source drift: one `find plugins` plus one `sha256sum` of
2+ cluster paths. Discover stdout is byte-identical to origin/main
- Discriminating-test-skips / silent-skips: one awk per corpus (`FNR` +
`FILENAME`; mawk has no `ENDFILE`)
- Hook-exec-form: one jq over every `hooks.json` and one over every
`plugin.json` (`input_filename` attributes rows). Unreadable
`hooks.json` still fails closed via per-file fallback; unreadable
manifests are still skipped

Hook-specific leftover-fork work already in flight (#3873, #3872, #3871,
#3870, #3869, #3851, #3849, #3779, #3880, #3886) is out of scope here.

## Verification

Independent census re-derived spawn counts from `84adf87b` vs `cdb93f61`
without inheriting implementer figures. Kernel census `strace -f -e
trace=clone,clone3,fork,vfork,execve`; counter over duration; 3
identical trials.

**always-on formatters** (this revision vs `84adf87b`):

| Hook | clones before | clones after | execve before | execve after |
|---|---|---|---|---|
| ruff-format no-config skip | 14 | 10 | 4 | 4 |
| powershell-format no-settings skip | 23 | 16 | 7 | 7 |
| bash-format no-EditorConfig (ShellCheck finding) | 17 | 13 | 6 (1
`basename`) | 5 (0 `basename`) |

**guardrails** (this revision):

| Hook | clones before | clones after | execve |
|---|---|---|---|
| skill-reference-verify Write, no skill refs | 18 | 17 | 8 unchanged |
| secret-pattern-detection clean Write | 10 | 8 | 4 unchanged |

Secret-pattern absolute counts are with `CLAUDE_PLUGIN_ROOT` set (Claude
Code always sets it). Without that env the leftover `PLUGIN_ROOT=$(cd …
&& pwd)` fallback adds one clone on both sides (11→9); the drop of 2 is
the same.

**CI scanners** (successful execve, exclude ENOENT; earlier commits on
this PR):

| Gate | origin/main or prior HEAD | HEAD |
|---|---|---|
| purged-em-dashes `--list` | 478 | 9 |
| cross-plugin-source-drift `--check` | 181 | 4 |
| discriminating-test-skips | 316 (awk 312) | 5 (awk 1) |
| silent-skips | 120 (awk 118) | 4 (awk 2) |
| hook-exec-form `--check` | 196 execve, jq 96, tr 96, clones 292 | 7
execve, jq 2, tr 0, clones 9 |

`--list` / discover stdout for the two listing gates is byte-identical
to origin/main.

**Local `scripts/affected-tests.sh --run`:** 153 shell suites passed or
were skipped; 14 NOT RUN python/mjs ecosystems (exit 3, expected on this
runner). No `FAIL`. Including: `lib/hook-utils.test.sh` PASS=323;
bash-format PASS=54; eol-normalizer PASS=54; markdown-format PASS=174;
powershell-format PASS=17; cli-flag-verify PASS=92; hardcoded-path-check
PASS=118; secret-pattern-detection PASS=86; skill-reference-verify
PASS=140; stale-path-verify PASS=108. ruff/biome/go/actionlint
behavioral cases skipped here (binaries absent); skip-path and source
pins still ran. `session-event-log.test.sh` PASS=53 isolated under the
fan-out.

**CI on `cdb93f61`:** lint, hook-utils, test-linux (0–3), test-windows,
changes, ci-status, and managed-files-guard all succeeded.
https://github.com/melodic-software/claude-code-plugins/actions/runs/34066676378
https://github.com/melodic-software/claude-code-plugins/actions/runs/34066676488

## Related

Refs #3838, #3732, #3678, #1979, #3488, #2891. Same leftover-fork class
as open PRs #3849 / #3851 / #3869 / #3873 / #3872 / #3871 / #3870 /
#3779 / #3880 / #3886 (those stay hook-specific). N/A for a dedicated
issue.

<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-fdfdc962-be1b-4c9c-9833-3aec57852330?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-fdfdc962-be1b-4c9c-9833-3aec57852330&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: ksextonmelodic <ksextonmelodic@gmail.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.

2 participants