Skip to content

fix(guardrails): raise PreToolUse blocking-guard timeouts from 10/15s to 60s - #1379

Merged
kyle-sexton merged 1 commit into
mainfrom
fix/1345-pretooluse-guard-timeout
Jul 25, 2026
Merged

fix(guardrails): raise PreToolUse blocking-guard timeouts from 10/15s to 60s#1379
kyle-sexton merged 1 commit into
mainfrom
fix/1345-pretooluse-guard-timeout

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

This was generated by AI during work-loop execution.

Summary

  • The seven blocking PreToolUse guards in plugins/guardrails/hooks/hooks.json declared
    timeout: 10/15 — 40-60x below the platform's documented 600s default for PreToolUse command
    hooks (only UserPromptSubmit (30) and MessageDisplay (10) lower it; confirmed via WebFetch of
    https://code.claude.com/docs/en/hooks, fetched 2026-07-25). The harness kills a hook that exceeds
    its declared timeout, and a killed hook contributes no permissionDecision, so the guarded tool
    call proceeds without that guard's verdict — measured at 86.1% of PreToolUse runs on one machine
    (guardrails: PreToolUse guards fail open — killed at their declared timeout on 86% of runs #1345).
  • Raised timeout to 60 for the seven blocking guards: secret-pattern-detection,
    hardcoded-path-check, block-no-verify, block-dangerous-git, block-hook-bypass,
    block-noncanonical-commit, block-convention-violation. The two advisory PreToolUse hooks
    (flag-commit-pr-skill-bypass, workflow-resilience-check — never block regardless of outcome)
    and the PostToolUse hooks are unchanged: a missed advisory notice is not the security defect this
    fix addresses.
  • Version bump 0.15.00.15.1 (patch, Fixed) and a CHANGELOG entry recording the evidence.

Verification performed (per the item's "verify it genuinely closes the fail-open window" instruction)

  • Reproduced the reported failure from primary source, not just the issue's own claim: this
    session's local ~/.claude/projects/*/*.jsonl contains a hook_cancelled attachment for
    block-convention-violation.sh (timedOut: true, durationMs: 10184, timeoutMs: 10000)
    immediately followed by the guarded Bash tool call executing and returning a real result — the
    guard's verdict was silently dropped, confirmed empirically, not inferred from documentation (the
    official docs do not specify cancel/timeout behavior — confirmed by the same WebFetch above).
  • Checked whether the guards are inherently slow before changing the number. Read all affected
    guard scripts (hook-utils.sh, block-convention-violation.sh, block-hook-bypass.sh, etc.): pure
    bash string parsing plus a bounded handful of external process spawns (jq, git, one bash
    subshell for the convention resolver) — no network calls, no unbounded loops. Then timed all seven
    standalone
    (no concurrent hook load) with representative PreToolUse payloads against this repo:
    every guard completed in under 1.5s. This rules out "the script itself hangs" and confirms the
    bottleneck is process-spawn/contention overhead under real harness load, consistent with the issue's
    own root-cause data (median 3.0 concurrent hook starts on killed runs vs. 1.0 on completed ones).
  • Checked for a more structural cause (double hook registration) before accepting a config-only
    fix: no duplicate guardrails registration in ~/.claude/settings.json or the plugin cache, and no
    toolUseID in the sampled transcript fired the same guard command twice.
  • Ran all seven affected guards' existing contract test suites — untouched by this change since it
    only edits hooks.json, not any .sh script, but run for regression confidence: block-no-verify
    112/112, block-dangerous-git 251/251, block-hook-bypass 203/203, block-noncanonical-commit
    90/90, block-convention-violation 31/31, hardcoded-path-check 72/72, secret-pattern-detection
    42/42 — all passing, 0 failures.
  • Validated both edited JSON files (hooks.json, plugin.json) parse cleanly.

Scope boundary (why this is a mitigation, not a closure)

Raising the timeout narrows the fail-open window measured in #1345; it does not remove it. A
harness-killed hook process cannot itself report a decision — that is a harness-level property no
plugin can change. What should happen to a guarded tool call when a blocking guard is genuinely
killed (deny-by-default vs. today's silent fallback) is a policy decision outside this plugin's
control and outside this item's work-class: scoped brief. Filed as its own item rather than folded
into this fix: #1378.

Test plan

  • plugins/guardrails/hooks/*.test.sh for all seven touched guards — 701/701 passing (0 failures)
  • jq empty on hooks.json and plugin.json
  • Standalone timing of all seven guards confirms sub-1.5s completion absent contention
  • Primary-source jsonl confirms the fail-open mechanism this PR mitigates

Closes #1345

Related

… to 60s

The seven blocking PreToolUse guards declared `timeout: 10`/`15` — 40-60x below
the platform's documented 600s default for PreToolUse command hooks (only
UserPromptSubmit/MessageDisplay lower it; confirmed via fresh WebFetch of
https://code.claude.com/docs/en/hooks). The harness was killing them before
completion under real load (measured 86.1% of PreToolUse runs killed, #1345),
and a killed guard contributes no permissionDecision, so the guarded tool call
proceeds unguarded.

Confirmed empirically against this session's own local jsonl transcript: a
timedOut:true attachment for block-convention-violation.sh was immediately
followed by the guarded Bash call executing with a real result — the fail-open
is real, not merely a documentation gap. Standalone timing of all seven guards
(no concurrent load) completed in well under 1.5s each, and none contain
network calls or unbounded loops, confirming the guards are not inherently
slow — they were simply underprovisioned.

timeout raised to 60 for: secret-pattern-detection, hardcoded-path-check,
block-no-verify, block-dangerous-git, block-hook-bypass,
block-noncanonical-commit, block-convention-violation. The two advisory
PreToolUse hooks (never block regardless) and PostToolUse hooks are
unchanged — out of scope for this security-defect fix.

This narrows the timeout-driven fail-open window; it does not remove it. What
should happen to a guarded tool call when a blocking guard is genuinely killed
is a harness-level policy question outside plugin control, filed separately.

Closes #1345

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 35s —— View job


PR Review: fix(guardrails): raise PreToolUse blocking-guard timeouts from 10/15s to 60s

Tasks

  • Load review tools
  • Read PR diff and changed files
  • Review hooks.json changes
  • Review plugin.json version bump
  • Review CHANGELOG.md entry
  • Post findings

Summary

This is a minimal, surgical fix to a real security defect. The change is correct and appropriately scoped. No blocking concerns; a few observations below.


hooks.json — Timeout changes

The seven blocking guards (secret-pattern-detection, hardcoded-path-check, block-no-verify, block-dangerous-git, block-hook-bypass, block-noncanonical-commit, block-convention-violation) are correctly raised from 10/15 to 60. The two advisory PreToolUse hooks (flag-commit-pr-skill-bypass at timeout: 10, workflow-resilience-check at timeout: 10) and the PostToolUse hooks are deliberately left unchanged — the scope discipline here is correct: a timed-out advisory hook loses a warning notice, not a security verdict.

The value 60 is well-reasoned: empirical standalone timing shows all seven guards complete in under 1.5s, and the buffer_stdin ceiling adds at most 2s, giving a worst-case of ~3.5s from start to decision under no contention. The measured kill events were ~10s (process-spawn/contention overhead), so 60s provides ≈6× headroom over observed failure values while remaining well below the platform's 600s cap so genuinely hung processes are still bounded.

One observation (not a defect): the two advisory PreToolUse hooks are left at timeout: 10, which is the same budget that caused the blocking-guard kills. Under the same high-load conditions that drove #1345, advisory hook notices may still time out silently. This is explicitly de-scoped here and the reasoning is sound — a missed advisory is categorically different from a missed block. Worth a follow-up issue if user-experience reports surface.

permalink: hooks.json lines 44–47, 71–76


plugin.json — Version bump

0.15.00.15.1 is the correct patch increment for a bug fix with no API or behavioral contract change. Version field matches the new [0.15.1] CHANGELOG section.

permalink: plugin.json line 4


CHANGELOG.md — 0.15.1 entry

The entry is thorough and correctly formatted under ### Fixed. It records: the root cause, the empirical evidence (transcript hook_cancelled attachment), the standalone timing methodology, the 86.1% kill-rate measurement, the scope of the fix (blocking guards only), and the acknowledged residual limitation (harness-level policy question filed separately as #1378). This is appropriately detailed for a security-relevant change.

The prose is long relative to other entries in this file, but given that the fix narrows a fail-open security window, the level of evidence documentation is justified rather than excessive.

permalink: CHANGELOG.md lines 7–35


Overall

Approve. The change is correct, minimal, and well-evidenced. No script logic changed (only hooks.json configuration), the version bump is correct, and the CHANGELOG entry meets the project's documentation bar. The residual limitation (harness can't enforce deny-by-default on a killed blocking guard) is correctly acknowledged and tracked separately in #1378.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 21s —— View job


Security Review — PR #1379

  • Gather context and understand the request
  • Fetch PR diff and read changed files
  • Analyze for security vulnerabilities
  • Post findings

Scope

Three files changed: hooks.json (7 integer timeout values), plugin.json (version string), CHANGELOG.md (prose). Review covers all three.


Findings

No security vulnerabilities found.

What was checked

hooks.json — timeout values 10/15 → 60

The only change is to integer timeout fields. Command paths ("${CLAUDE_PLUGIN_ROOT}"/hooks/*.sh), matcher patterns, hook types, and the overall hook structure are all unchanged. There is no new shell interpolation, no new command execution surface, no new trust delegation, and no change to what code runs — only how long the harness waits before killing it.

The change is security-positive: the prior 10/15s values caused blocking guards to be killed by the harness before they could return a permissionDecision, resulting in a fail-open condition (guarded tool calls proceeding silently without any guard verdict). Raising to 60s reduces that window materially while remaining far below the 600s platform ceiling, so a genuinely hung process is still bounded within a sane wall-clock limit.

The advisory hooks (flag-commit-pr-skill-bypass, workflow-resilience-check) correctly remain at 10s — they never block regardless of outcome, so their timeout has no security bearing.

plugin.json — version bump 0.15.0 → 0.15.1

Patch bump only. No structural change to the manifest, no new userConfig keys, no added permissions.

CHANGELOG.md — prose entry

No secrets, credentials, real hostnames, or machine-specific paths. The ~/.claude/projects/*/*.jsonl reference is a generic glob used as a documentation example, not an exposed real path.

GitHub Actions / supply-chain surface

No workflow files touched; no action pins changed; no dependency manifests modified.


Summary

This is a pure configuration patch — seven integer values raised from 10/15 to 60. It does not introduce any injection surface, authorization gap, trust escalation, or dangerous GitHub Actions pattern. It strengthens the security posture of the plugin by reducing the probability of blocking guards failing open due to harness-imposed timeouts.

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

Labels

None yet

Projects

None yet

1 participant