Forces noisy commands — builds, tests, linters, static analysis, anything that produces a wall of output — to run inside a Sonnet subagent instead of the main agent's context window. Main-agent tokens are for work that needs them, not for 2,000 lines of Maven output.
Prereq: install bash-classify, used by the hook to parse Bash commands:
uv tool install bash-classifyThen add the marketplace and install:
claude plugin marketplace add fprochazka/claude-code-plugins --scope user
claude plugin install noisy-tools-in-subagent@fprochazka-claude-code-plugins --scope userIf bash-classify isn't on your PATH, the hook refuses to proceed and tells the agent to install it.
Two pieces:
-
A
PreToolUsehook onBashthat parses each command withbash-classify(walking pipes, chains, and subshells) and matches every parsed command against a regex whitelist of noisy build/test/lint tools. In the main agent context, a match gets denied with a short message instructing the agent to delegate to the subagent. In any subagent context, the hook passes through — other subagents aren't affected, and thenoisy-runnercan freely run what it was asked to run. -
A
noisy-runnersubagent (Sonnet, strictly scoped toBashandRead) that runs the commands it is given, wraps them inteeto capture full logs to/tmp, reads any files the output references to interpret errors, and reports back a concise summary plus log paths. It does not explore, fix, or modify anything — its only job is run, read, interpret, report.
Net effect: when the main agent wants ./mvnw test, it gets redirected into a "3 tests failed at X:42, Y:87, Z:12 because …, full logs at /tmp/…" summary instead of a flood of output.
The whitelist and its tuning live in hooks/enforce-subagent.py. Small introspection commands (mvn help:*, gradle tasks, mvn dependency:tree) pass through to the main agent — only the heavy lifecycle phases are blocked.
The noisy-runner subagent isn't limited to the hook's whitelist. Invoke it via the Task tool any time you want a command run and interpreted rather than transcribed into your context:
subagent_type: "noisy-tools-in-subagent:noisy-runner"
prompt: run `./mvnw test -pl foo` and `./gradlew :bar:check`, tell me what broke
You can pass multiple commands in one invocation — the subagent runs them sequentially.
When the hook denies a command, Claude Code's terminal UI may render the rejection message 2–3 times in the same tool-call block (an empty "blocking error" header, a red Error: card, sometimes a side-panel overlay). This is a Claude Code rendering quirk — upstream issues anthropics/claude-code#34713 and #21504. The model itself only sees the rejection reason once in its context, so it's visual clutter, not token waste.
claude plugin marketplace update fprochazka-claude-code-plugins
claude plugin update noisy-tools-in-subagent@fprochazka-claude-code-pluginsMIT
