Skip to content

perf(claude-ops): Stop-event unsurfaced-failure detector times out 113x, ~44x the per-turn hook budget #3512

Description

@kyle-sexton

Summary

Child of #3508. The Stop-event hook that audits for unsurfaced hook failures times out repeatedly, at roughly 44x the marketplace per-turn hook budget.

This issue was originally filed against guardrails. That was wrongguardrails registers no Stop hooks at all. Corrected below.

The hook

Plugin claude-ops
Hook plugins/claude-ops/hooks/hook-failure-audit.sh
Registration plugins/claude-ops/hooks/hooks.json, Stop event
Configured timeout 10 s
Timeouts in window 113
Average recorded duration 21.9 s
Worst observed 74.1 s

The bar

docs/conventions/hook-budget/README.md (adopted #1809, surfaced by .claude/rules/hook-budget.md) sets the per-turn ceiling for the whole always-on Stop-shaped set at <= 500 ms, measured as parallel wall time.

At 21.9 s average this hook alone is ~44x the entire per-turn budget, and it shares that budget with two siblings:

Issue Plugin Stop timeout Timeouts Avg
#3512 (this) claude-ops 10 s 113 21.9 s
#3515 autonomy 15 s 73 27.6 s
#3516 disk-hygiene 20 s 52 31.8 s

Three plugins, three separate processes, one 500 ms budget between them. Consolidation across the three is likely worth more than optimizing any one, and none of the three can meet the ceiling alone at current spawn cost.

Why it is slow

Per #3508, process creation on the affected host is bimodal at 180-2,841 ms (median 1,108 ms at 501 concurrent processes). Lead any fix with a spawn count, not a duration — durations on that host are not comparable across time.

This hook is also structurally last in line: it inspects other hooks' results, so it runs after the rest of the turn's fan-out and compounds whatever contention they created.

Proposed work

  1. Count spawns first, via a PATH shim of wrapper scripts that log their own name then exec the real tool. Deterministic, drift-immune, cheap for a reviewer to verify.
  2. Early-exit before any spawn when no hook failure was recorded this turn — the common case.
  3. Consider sampling or async rather than running on every Stop.
  4. Evaluate consolidating the three Stop hooks into one process, given the shared 500 ms ceiling.

Acceptance criteria

  • Spawn count on the common path reported before and after, measured by PATH shim.
  • A turn with no hook failures exits before any external process spawn.
  • The hook's measured share, by the convention's method (EPOCHREALTIME around direct invocation, >= 10 runs, concurrent launch), is stated in the claude-ops README per hook-budget Rule 1.
  • The always-on per-turn set stays within <= 500 ms parallel wall, reported alongside the spawn-cost baseline at measurement time.
  • Existing behavioural tests pass; genuine unsurfaced failures are still detected.
  • Validated with scripts/affected-tests.sh --run.

Notes

Prior art in #1403 / #1385 (hook::jq_fields, strip_quoted_spans, deferred git rev-parse; a 65-spawn-per-Bash-call census and a 437 ms unit price). hook-utils.sh exists in 17 synced copies — changes go through scripts/sync-hook-utils.sh, never one plugin's copy.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent-readyFully specified and briefed; eligible for autonomous pickup from the frontier.priority: highSignificant impact, or blocks an imminent release; staff this cycle.work-class: scopedA briefed fix or small feature; blast radius bounded by the brief, tests exist.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions