Problem
Every hook in this marketplace sources lib/hook-utils.sh, and buffering the hook payload spawns
external processes that do work bash can do in-process:
hook::resolve_read_slice spawns awk on every invocation to divide the read timeout by the
slice count — one float division.
hook::buffer_stdin pipes the buffered payload through printf | tr -d '\r' — a fork and an
exec to delete one byte class from a string bash rewrites with parameter expansion.
hook::buffer_stdin then runs jq -e . as a validity probe over the same buffer
hook::json_complete already parsed with jq inside the read loop, so the common Windows path pays
for two identical parses.
On Windows Git Bash, process creation is fork() emulation and each spawn costs roughly 140 ms
(measured: ~140 ms bash, ~115 ms jq, ~138 ms git). The library is synced into 16 plugins, so every
hook in the marketplace pays it on every tool call.
Consequences beyond wall time: hooks registered at timeout: 10 (context-guard's four
registrations) sit much closer to their declared ceiling than the work justifies, and this
repository's own hook test suites — 319 cases in block-dangerous-git.test.sh alone, each spawning
a hook — take tens of minutes locally.
Proposal
- Replace the
awk division with fixed-point shell arithmetic, keeping the exact three-decimal
output form read -t is given.
- Replace
printf | tr -d '\r' with ${var//$'\r'/}.
- Reuse
hook::json_complete's verdict instead of re-probing the identical buffer, without
disturbing the fail-open behavior on a host with no jq.
- Add a
hook::jq_fields helper that extracts several fields in ONE jq process, for hooks that
read two or three fields from the same envelope (block-dangerous-git reads
.tool_input.command and .tool_name separately today).
Acceptance
lib/hook-utils.test.sh passes, with new coverage for the slice format and the batch extractor.
scripts/sync-hook-utils.sh --check is clean and every carrying plugin's version is bumped.
- A measured before/after on a quiet Windows box, reported with the actual numbers.
Problem
Every hook in this marketplace sources
lib/hook-utils.sh, and buffering the hook payload spawnsexternal processes that do work bash can do in-process:
hook::resolve_read_slicespawnsawkon every invocation to divide the read timeout by theslice count — one float division.
hook::buffer_stdinpipes the buffered payload throughprintf | tr -d '\r'— a fork and anexec to delete one byte class from a string bash rewrites with parameter expansion.
hook::buffer_stdinthen runsjq -e .as a validity probe over the same bufferhook::json_completealready parsed with jq inside the read loop, so the common Windows path paysfor two identical parses.
On Windows Git Bash, process creation is
fork()emulation and each spawn costs roughly 140 ms(measured: ~140 ms bash, ~115 ms jq, ~138 ms git). The library is synced into 16 plugins, so every
hook in the marketplace pays it on every tool call.
Consequences beyond wall time: hooks registered at
timeout: 10(context-guard's fourregistrations) sit much closer to their declared ceiling than the work justifies, and this
repository's own hook test suites — 319 cases in
block-dangerous-git.test.shalone, each spawninga hook — take tens of minutes locally.
Proposal
awkdivision with fixed-point shell arithmetic, keeping the exact three-decimaloutput form
read -tis given.printf | tr -d '\r'with${var//$'\r'/}.hook::json_complete's verdict instead of re-probing the identical buffer, withoutdisturbing the fail-open behavior on a host with no jq.
hook::jq_fieldshelper that extracts several fields in ONE jq process, for hooks thatread two or three fields from the same envelope (
block-dangerous-gitreads.tool_input.commandand.tool_nameseparately today).Acceptance
lib/hook-utils.test.shpasses, with new coverage for the slice format and the batch extractor.scripts/sync-hook-utils.sh --checkis clean and every carrying plugin's version is bumped.