Skip to content

perf(hooks-plugin): dedup bash-antipatterns-teach hints per session#1475

Draft
laurigates wants to merge 1 commit into
mainfrom
claude/plugins-comment-review-Yge2x
Draft

perf(hooks-plugin): dedup bash-antipatterns-teach hints per session#1475
laurigates wants to merge 1 commit into
mainfrom
claude/plugins-comment-review-Yge2x

Conversation

@laurigates
Copy link
Copy Markdown
Owner

Context

Acting on external review feedback praising the hooks setup but flagging a real cost axis we hadn't fully accounted for: anything a hook makes the model see is replayed in the transcript on every subsequent turn. The reviewer specifically called out bash-antipatterns-teach.sh — its updatedToolOutput hint replays per matching call — and suggested capping or deduping.

This PR adopts the valid core insight and corrects one factual misunderstanding in the same feedback.

What changed

1. Session-scoped dedup in bash-antipatterns-teach.sh

The teach hint persists in the transcript like any tool result, so an un-capped hint on a high-frequency command (grep, cat) accumulated one replayed copy per matching call — identical lesson each time, pure bloat past the first. Each distinct hint (read-cat, read-headtail, glob-find, grep, glob-ls) now emits at most once per session via a sanitised-session-id seen-list under ${TMPDIR:-/tmp}/claude-bash-teach-seen/, mirroring git-stash-session-init.sh. Caps replayed banner at ~150 tokens/session regardless of call count.

  • git-session-cleanup.sh removes the seen-list at SessionEnd.
  • When session_id is absent, the hook falls through to the old always-emit behaviour — backward compatible, existing no-session tests still pass.

2. Codified the transcript-replay-cost axis in .claude/rules/hooks-reference.md (new "Transcript Replay Cost" section), including a corrected note on Stop semantics — see below.

3. Updated teach-mode-experiment.md cost accounting and risk table to reflect replay cost (not one-time ~30 tokens) and the dedup mitigation.

Refuting the misunderstanding

The feedback states "Stop output never reaches the model." That is not accurate. A blocking Stop hook's reason is shown to the model — that is precisely how it nudges the agent to keep working, and CLAUDE_CODE_STOP_HOOK_BLOCK_CAP (2.1.143+) exists because the model re-reads that reason. What makes our Stop checks (task-completeness.sh, git-stash-reminder.sh) cheap is frequency + conditionality + self-extinction, not invisibility:

  • fire at most once per stop attempt, not per tool call;
  • silent (exit 0) unless there is a concrete finding;
  • self-extinguishing — once the agent fixes the finding, the next stop passes clean.

The practical upshot matters: "move chatty checks to Stop because Stop is free" would mislead — a verbose Stop hook that blocks on every stop is both costly and can burn the 8-block cap.

Testing

  • test-bash-antipatterns-teach.sh: 22 passed (5 new dedup-path tests: first-emit / repeat-silent / different-pattern-still-emits / new-session-re-emits / no-session-fallthrough)
  • test-bash-antipatterns.sh: 36 passed · test-task-completeness.sh: 27 passed
  • scripts/lint-shell-scripts.sh: 0 errors, 0 warnings

https://claude.ai/code/session_01P2FJF1euBcG4w5apvve9n9


Generated by Claude Code

The PostToolUse teach hook augments tool output via updatedToolOutput,
which persists in the transcript and is replayed to the model on every
subsequent turn. An un-capped hint on a high-frequency command (grep,
cat) therefore accumulated one replayed copy per matching call - the
lesson is identical each time, so every copy past the first was pure
transcript bloat.

Cap each distinct hint (read-cat, read-headtail, glob-find, grep,
glob-ls) to once per session via a sanitised-session-id seen-list under
${TMPDIR:-/tmp}/claude-bash-teach-seen/, mirroring the
git-stash-session-init.sh convention; git-session-cleanup.sh removes it
at SessionEnd. When session_id is absent the hook falls through to the
old always-emit behaviour, so existing no-session tests still pass.

Also codify the transcript-replay-cost axis in hooks-reference.md,
correcting the common misconception that Stop output never reaches the
model: a blocking Stop reason IS shown (that is how it nudges) and DOES
replay - what makes our Stop checks cheap is frequency, conditionality,
and self-extinction, not invisibility.

Adds 5 regression tests for the dedup path (22 total, all passing).
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Plugin Compliance Review

Plugin plugin.json Frontmatter Body Marketplace Release Config Bash Patterns Descriptions When-to-Use Size Overall
hooks-plugin ⚠️ ⚠️

Recommendations

  • ⚠️ hooks-plugin/hooks-configuration: SKILL.md is 276 lines (>250) — consider extracting to REFERENCE.md or scripts/ (Anthropic ideal: 200, ceiling: 500)

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.

2 participants