Skip to content

block-windows-drive-tmp: non-Windows host gate leaves large payloads undrained on stdin #3504

Description

@kyle-sexton

Summary

Follow-up from #3503, surfaced by that PR's fresh-context verifier. Not introduced as a defect by the fix — it is an accepted trade-off of the fix, recorded here rather than widened into that PR.

plugins/guardrails/hooks/block-windows-drive-tmp.sh now evaluates its non-Windows OSTYPE gate before hook::buffer_stdin, so on Linux and macOS the hook exits 0 without reading stdin. That fix was necessary: with the gate below hook::require_jq_blocking, a jq-less non-Windows host took a fail-closed exit 2 on every Write/Edit/MultiEdit/NotebookEdit call.

The cost is that the payload is left undrained on every non-Windows tool call.

Measured

Payloads piped from cat, OSTYPE=linux-gnu, comparing 158ca9e (before the reorder) with 205fd68 (after):

payload size before after (linux-gnu) after (msys)
4,205 B writer rc 0 writer rc 0 writer rc 0
65,645 B writer rc 0 writer rc 0 writer rc 0
262,253 B writer rc 0 writer rc 141 (SIGPIPE) writer rc 0
1,048,685 B writer rc 0 writer rc 141 (SIGPIPE) writer rc 0

Above roughly 64KB–256KB the writer takes SIGPIPE. The Windows lane is unaffected.

Why it was accepted rather than fixed in #3503

  • It is not proven to affect Claude Code. The measurement used cat as the writer, which dies on SIGPIPE; that is not how the harness feeds a hook.
  • The harness demonstrably tolerates an undrained hook exit already: hook::check_enabled exits before draining whenever an operator disables any guardrails guard, and that is a supported configuration.
  • Both obvious fixes are worse. A naive builtin drain loop blocks until EOF and can hang a tool call — which is precisely why hook::buffer_stdin is a bounded idle-timeout read rather than a plain read. Draining through buffer_stdin first would put its rc-2 fail-closed exit back ahead of the host gate, reintroducing the bug the reorder fixed.

What is genuinely new

The shape (exiting without draining) was already present via check_enabled. The exposure is new: check_enabled is a kill switch that fires only when a guard is explicitly disabled, whereas this gate fires unconditionally on every non-Windows tool call — the default state for every Linux and macOS user.

Suggested direction

Decide whether guardrails wants a shared bounded-drain helper in hook-utils.sh for early-exit paths, usable by any hook that gates before reading. That is a cross-hook decision, not a single-guard one, which is why it is filed rather than patched here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions