Getting rules into models that cannot read them

Intent routing on prompt submit and Iron Law injection into every spawned subagent — the two hooks built because prose rules in CLAUDE.md measurably do not fire.

View source
On this page
  1. route-intent.sh
  2. Why this is a hook
  3. Categories
  4. Anti-annoyance design
  5. Performance
  6. Never exit 2 here
  7. inject-iron-laws.sh
  8. Relationship to omitClaudeMd
  9. Gating
  10. Related

About this hook group

Hooks UserPromptSubmitSubagentStart

Intent routing on prompt submit and Iron Law injection into every spawned subagent — the two hooks built because prose rules in CLAUDE.md measurably do not fire.

Context Injection (UserPromptSubmit, SubagentStart)

Two hooks that put text directly into a model’s context. They exist because of the same measured failure: instructions written as prose in CLAUDE.md do not reliably fire.

ScriptEventInjects into
route-intent.shUserPromptSubmitThe main conversation, per prompt
inject-iron-laws.shSubagentStartEvery spawned subagent

route-intent.sh

Detects high-signal task intents in your prompt and injects a one-line /phx: suggestion.

Why this is a hook

Session analysis across 400 sessions measured CLAUDE.md prose routing rules firing ~0% of the time. The rules were correct and simply never consulted.

UserPromptSubmit is one of only two events whose stdout is injected into Claude’s context (the other is SessionStart), so detection here actually reaches the model. The routing table still exists in CLAUDE.md for the ambiguous multi-step cases, but the high-signal cases moved into this hook.

Categories

CategoryTriggerSuggestion
pr-reviewA GitHub PR URL, or phrasing like “review this PR”, “reviewer feedback”, “address the review”/phx:pr-review
investigate-uiA Tidewave <context name="current-page"> block on the prompt/phx:investigate
investigate-errorAn Elixir crash signature — ** (SomeError), an (elixir 1.x) / (ecto …) frame line, or a lib/foo.ex:42 stack frame/phx:investigate

Output is always marked as advisory:

[plugin hint] Looks like a stack trace / error paste — /phx:investigate does
structured root-cause analysis (add --parallel for deep dives). (suggestion only)

Anti-annoyance design

Three properties keep this from becoming noise:

  1. Never fires on a slash command. If your prompt starts with /, you have already routed yourself.
  2. Once per category per session. State lives in /tmp/.claude-elixir-routing/$SESSION_ID/<category>, keyed by session so a new session starts fresh.
  3. One suggestion per prompt. First match wins; hints never stack.

The error category requires a real exception shape, not the word “error” — otherwise “fix the error message copy” would trigger it.

Performance

Only the first 4000 characters of the prompt are scanned. Long pastes keep their signal up front, and grepping a 50 KB prompt on every submit is wasted work. This keeps the hook well under 100 ms.

Never exit 2 here

For UserPromptSubmit, exit 2 blocks processing and erases the user’s prompt. Every path in this script exits 0, and there is no set -e, so a non-zero from an internal grep or jq can never destroy what you typed.


inject-iron-laws.sh

Injects all 26 Iron Laws into every spawned subagent via hookSpecificOutput.additionalContext.

This addresses the #1 finding from session analysis: zero skill auto-loading in subagents. A subagent starts with a fresh context. It does not inherit the conversation, and plugin skill references/*.md live outside the project directory in ~/.claude/plugins/cache/, so agents cannot reliably read them at runtime. Without this hook, a review agent would apply generic Elixir knowledge rather than the project’s non-negotiable constraints.

The injected block is the condensed one-line-per-law form, covering LiveView, Ecto, Oban, security, OTP, Elixir idioms, code style, and verification.

Relationship to omitClaudeMd

These are separate mechanisms, and the distinction matters when writing agents:

MechanismWhat it controls
omitClaudeMd: true in agent frontmatterSkips the project CLAUDE.md body — commit rules, lint guidance, scope cues
This hookInjects Iron Laws, on every subagent spawn

So omitClaudeMd: true is safe on read-only agents: it trims the parts they do not need while the Iron Laws still arrive. Set it freely.

Gating

Skipped when the project has no mix.exs. A subagent working in a Rust or Python repo should not be told about assign_async and cast_assoc.

  • Session Lifecycle — the other event whose stdout reaches Claude
  • Workflow StatePreCompact re-injection, the third injection path
  • CLAUDE.md → “Workflow Routing (Hook-Driven)”