Skip to content

perf(hook-utils): cut the per-invocation subprocess spawns in the shared hook library #1978

Description

@kyle-sexton

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

  1. Replace the awk division with fixed-point shell arithmetic, keeping the exact three-decimal
    output form read -t is given.
  2. Replace printf | tr -d '\r' with ${var//$'\r'/}.
  3. 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.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: mediumReal value, no hard deadline; normal backlog flow.work-class: structuralRefactors, migrations, contract changes; cross-cutting and hard to reverse.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions