Skip to content

feat(hookify): add silent flag for assistant-only warn rules#1421

Closed
WingsOfPanda wants to merge 2 commits intoanthropics:mainfrom
WingsOfPanda:feat/hookify-silent-rules
Closed

feat(hookify): add silent flag for assistant-only warn rules#1421
WingsOfPanda wants to merge 2 commits intoanthropics:mainfrom
WingsOfPanda:feat/hookify-silent-rules

Conversation

@WingsOfPanda
Copy link
Copy Markdown

Summary

Adds a silent: true frontmatter flag to hookify rules. When a warn rule is marked silent, its systemMessage (user-facing banner) is suppressed and the message is delivered only via additionalContext (assistant-facing).

This enables purely assistant-facing self-correction rules — the agent sees the message in its context and adjusts, but the user isn't shown a banner for behaviors they don't need to be interrupted about.

Stacked on #1420

This PR builds on #1420 (fix(hookify): pass warn/block messages to Claude). If #1420 lands first, this PR's diff will narrow to just the silent-flag additions. If they land together, the combined change is the warn-path rewrite shown below.

Motivation

Warn rules now (as of #1420) reach Claude via additionalContext. That's a huge win — the AI can self-correct. But every warn rule also emits systemMessage, which banners the user. Two rule classes have emerged in practice:

Rule type Should user see banner?
User-relevant warnings (e.g., "you're about to rm -rf — confirm?") Yes — this is a shared decision
AI self-correction (e.g., "use python3, not python", "cd back to project root before wrapping up") No — it's a Claude-internal adjustment; banners create notification fatigue

Without this flag, all warn rules look the same to the user. With it, users can build coaching rules that nudge Claude silently.

Design

  • New optional frontmatter field: silent: true (default false)
  • When ANY matching rule is non-silent, systemMessage is emitted and contains ONLY the non-silent messages (silent rules don't leak to the banner even when bundled)
  • additionalContext always contains ALL matching messages (silent + non-silent) — assistant always sees everything
  • Backward-compatible: existing rules without silent keep current behavior

Example usage

---
name: return-to-repo-root
enabled: true
event: bash
pattern: \bcd\s+\w+-subrepo
action: warn
silent: true
---

You're crossing into a sub-repo. Do what you need, then `cd ../..`
before your final response — otherwise `git` commands target the wrong repo.

User runs Claude. Claude tries cd foo-subrepo. User sees nothing. Claude gets the message in its context, does the task, cds back. Invisible self-correction.

Changes

plugins/hookify/core/config_loader.py:

  • Add silent: bool = False field to Rule dataclass
  • Pass silent=bool(frontmatter.get('silent', False)) in from_dict

plugins/hookify/core/rule_engine.py:

  • Partition warn messages into all_messages (for additionalContext) and visible_messages (for systemMessage)
  • Emit systemMessage only if there are non-silent messages to show

Test plan

  • silent: true in frontmatter is parsed to Rule.silent = True
  • Missing silent field defaults to False (backward compat)
  • Silent-only warn rule on PreToolUse: no systemMessage in response; additionalContext has the message
  • Non-silent warn rule: both systemMessage and additionalContext (same as current behavior)
  • Mixed (silent + non-silent rules fire together): systemMessage has only non-silent messages, additionalContext has all
  • Block rules unaffected (different code path, kept verbatim)
  • Stop event unaffected
  • Verified end-to-end in a real Claude Code session: silent hookify rule fires, user confirms no banner appeared, assistant confirms additionalContext arrived

Rationale for the partition design

Four options were considered for mixed matching (some silent, some visible):

  1. ALL silent iff every rule is silent — loses information if any non-silent rule matches
  2. Boolean OR on silent — a single silent rule suppresses ALL banners; surprising
  3. Boolean AND on silent — a single visible rule shows ALL banners; leaks silent content
  4. Partition at the message level (this PR): banner shows only non-silent messages, additionalContext has all

Option 4 is the most predictable: silent means silent, non-silent means visible, mixed works as expected from each rule's own perspective.

Refs

Warn rules previously returned only `systemMessage`, which is shown to the
user console but never injected into Claude's context. Educational messages
(the entire point of warn rules) never reached the model. Block rules
similarly returned only a generic deny without `permissionDecisionReason`.

Now:
- Warn rules on PreToolUse/PostToolUse also emit `additionalContext` inside
  `hookSpecificOutput`, so Claude sees the message and can self-correct.
- Block rules on PreToolUse/PostToolUse also emit `permissionDecisionReason`,
  so Claude sees why the operation was denied.

PreToolUse `additionalContext` support landed in Claude Code v2.1.9
(see anthropics/claude-code#15664, anthropics/claude-code#15345). PostToolUse
has supported it since earlier. The capability is now available; this patch
makes hookify use it.

Refs:
- anthropics/claude-code#15203 (warn — closed as duplicate)
- anthropics/claude-code#12446 (block — closed)
- anthropics/claude-code#15664, anthropics/claude-code#15345 (SDK additionalContext support)
- Prior PR attempts: anthropics/claude-code#15218, anthropics/claude-code#15219 (closed unmerged, predate v2.1.9)
Adds a `silent: true` frontmatter field to hookify rules. When set,
the warn rule suppresses `systemMessage` (user-facing banner) and
delivers only via `additionalContext` (assistant-facing). This enables
purely Claude-internal self-correction rules without creating user
notification fatigue.

Behavior:
- `silent: true` → no banner, only additionalContext
- `silent: false` (default, backward compat) → banner + additionalContext
- Mixed matching: banner shows only non-silent messages; additionalContext
  always contains all messages (silent + non-silent)

Builds on anthropics#1420 (adds additionalContext to warn rules in the first place).

Motivation: warn rules fall into two classes — those the user should
see (shared decisions like "rm -rf confirm?") and those that are
Claude-internal coaching ("use python3 not python"). The new flag
lets rule authors distinguish them.

Refs:
- anthropics#1420 (prereq — adds additionalContext)
- anthropics/claude-code#15664, anthropics/claude-code#15345 (SDK PreToolUse additionalContext in v2.1.9)
@github-actions
Copy link
Copy Markdown

Thanks for your interest! This repo only accepts contributions from Anthropic team members. If you'd like to submit a plugin to the marketplace, please submit your plugin here.

@github-actions github-actions Bot closed this Apr 15, 2026
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