Skip to content

examples/hooks: demonstrate compound-command pre-flight with deny-and-steer in the bash validator example#76289

Open
ss251 wants to merge 2 commits into
anthropics:mainfrom
ss251:fix/example-hook-compound-command-preflight
Open

examples/hooks: demonstrate compound-command pre-flight with deny-and-steer in the bash validator example#76289
ss251 wants to merge 2 commits into
anthropics:mainfrom
ss251:fix/example-hook-compound-command-preflight

Conversation

@ss251

@ss251 ss251 commented Jul 10, 2026

Copy link
Copy Markdown

Summary

Extends examples/hooks/bash_command_validator_example.py to demonstrate compound-command pre-flighting: the hook detects command chaining (;, &&, ||, unquoted newlines), pipelines (with an opt-in allowlist for read-only filters like head/wc), active command substitution, find -exec, xargs, and plain-file compose redirects — and denies with construct-specific guidance steering Claude to split the operation into separate Bash calls, using the hook's existing stderr + exit-2 contract.

Motivation

Claude Code's permission analyzer can reject compound Bash commands; interactive sessions get a prompt, but headless/unattended sessions can drop the denied action silently (#31523 tracks that UX). A user-side PreToolUse hook can pre-empt this deterministically: catch the compound shape before the analyzer sees it and steer the model to equivalent single commands. This pattern comes from field use — in our own session transcripts we counted 50+ silent denials that this pre-flight would have converted into actionable steers. The existing example only style-lints grep/find, so it was the natural home to demonstrate the pattern.

This example does not change or claim to fix the CLI's analyzer.

What changed

  • Compound-construct scanner (stdlib-only, shlex-based): chaining, pipes, substitution, find -exec, xargs, >/>>/tee file composition.
  • Careful false-positive handling, locked in by tests: quoted operators (git commit -m "fix: a && b", awk '{print $1;}') pass; heredoc bodies are not scanned; backslash-newline continuations pass; file-descriptor redirects (2>/dev/null, 2>&1, &>, N>>, exec 3>&1) are excluded so everyday single commands are never denied.
  • Construct-specific steer messages (e.g. "Detected && chaining. Run git add -A and git commit -m x as two separate Bash calls.").
  • The original grep/find style-lint behavior is preserved byte-for-byte for its target inputs; the one intentional change (piped grep is now caught by the pipe rule rather than exempted) is noted in a comment.
  • An honesty caveat states the lexing is a naive approximation of bash grammar (no process-substitution or background-& detection) and users should tune the rules.
  • 20 dependency-free stdlib unittest cases exercising the real hook via subprocess (test_bash_command_validator_example.py).

Validation

  • python3 -m py_compile clean on both files; 20/20 tests green.
  • Live payload demonstrations: ls 2>/dev/null → exit 0; make 2>&1 → exit 0; git add -A && git commit -m x → exit 2 with split guidance; python3 script.py > out.log 2>&1 → exit 2 with steer naming only out.log; newline-separated command pair → exit 2; heredoc containing a && b → exit 0.
  • Two independent adversarial review rounds (≈40 payloads including fd-redirect variants, quoted-operator, and line-continuation cases) — the first round's findings (fd-redirect false denials, newline gap) are fixed and covered by tests.

AI-assisted: implemented by a Codex (gpt-5.6-terra) worker agent, adversarially reviewed by a fresh-context Claude Opus verifier (initial REJECT drove the fd-redirect and newline fixes above), human-supervised. We run this hook pattern daily on our own machines.

ss251 and others added 2 commits July 10, 2026 10:32
…ator hook

Extend the PreToolUse bash validator example to detect compound
command shapes (chaining, pipes, command substitution, find -exec,
xargs, compose redirects) and deny with construct-specific guidance
steering the model to split into separate Bash calls, using the
hook's existing stderr + exit 2 contract. Quoted strings and heredoc
bodies are excluded from scanning; an opt-in allowlist permits
read-only pipe filters. Adds dependency-free unittest coverage.

Mitigates the silent headless auto-deny UX tracked in
anthropics#31523 from the user side.

AI-assisted (Codex gpt-5.6-terra worker, Claude-supervised).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8NGaAFCBE4HLGLojEwvvy
…ing caveat

Address cross-provider review: N>, N>>, >&, &>, and N>&M no longer
trigger the compose-redirect rule (ls 2>/dev/null and make 2>&1 pass;
steer text for plain file redirects stays coherent alongside fd dups);
unquoted newlines outside heredoc bodies now count as chaining; add a
naive-lexing honesty caveat and note the intentional grep-pipe
behavior change. Locks all cases in with 5 new tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8NGaAFCBE4HLGLojEwvvy
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant