[FEATURE] Hooks can inject text but cannot load a rule, a skill, or a file
Preflight Checklist
- [x] I have searched existing requests and this feature hasn't been requested yet
- [x] This is a single feature request (not multiple features)
Problem Statement
here is no way to load an instruction because of something a script computed.
The documentation covers the two static cases and stops. Instructions that never
change go in CLAUDE.md. Instructions conditional on a path go in paths:
frontmatter on a rules file or a skill. Instructions conditional on anything
else have no channel.
The only thing a hook can do is return additionalContext, and the
documentation says that is the wrong shape for this:
"Write the text as factual statements rather than imperative system
instructions... Text framed as out-of-band system commands can trigger
Claude's prompt-injection defenses, which causes Claude to surface the text to
you instead of treating it as context."
Rule text is imperative by nature. So the workaround is documented as risky for
the exact use it is being put to.
I run a PreToolUse hook that inspects each pending edit. It can see things no
glob can: which library the file imports, whether the edit crosses an auth
boundary, whether it calls an API that was deprecated last release. In every one
of those cases it knows exactly which rule or skill applies. It cannot load it.
It can only paste text and hope, which means:
- no dedupe, so the same rule is re-sent on every fire
- nothing under /context, so nothing can account for it
- no InstructionsLoaded event, so it cannot be audited
- no reload after compaction
- the 10,000 character cap. One of my rule files is 2,781 characters, so three
of them fill it
The same hook cannot load a skill either, and pasting is not an option there:
a skill is a folder with references/ and scripts/ beside SKILL.md, andadditionalContext is a string.
Proposed Solution
One new hook output field, load:, taking a list of targets typed by kind:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"load": [
{ "rule": ".claude/rules/comments.md" },
{ "skill": "file-findings" },
{ "file": "docs/api/openapi.yaml" }
]
}
}
Each target loads through the loader that already handles it, so it dedupes,
appears under /context, and fires InstructionsLoaded with a new load_reason
value of "hook". A target already in context is a no-op.
Events that should carry it: PreToolUse, PostToolUse, UserPromptSubmit,
SessionStart, PostCompact. PostCompact matters most after the first: it has no
decision control at all today, and it is the natural place to restore a rule
that a compaction dropped.
Ranked by how much each is worth, since they are not equal:
rule. Nothing else reaches the instruction machinery.skill. Cannot be approximated at all, because of the bundled folder.file. Weakest. A hook cancata file intoadditionalContextand it
mostly works. What it loses is identity: Claude gets a blob rather than a
path it can re-read or edit. Worth having, first to cut if you disagree.
There is precedent for a hook reaching into this. A SessionStart hook can
already return reloadSkills: true and Claude Code re-scans the skill
directories. That stops at making a skill discoverable, at session start, all or
nothing. This is the same door, opened one notch.
Alternative Solutions
Paste the text into additionalContext. What I do now, and the five problems
above are why it is not equivalent.
Put the instruction in CLAUDE.md. Loads in every session including the ones it
is irrelevant to, which is the cost conditional loading exists to avoid.
Richer declarative triggers on the rule or skill itself. Better wherever the
condition is a glob, and I have filed that separately. It cannot express "the
check for this rule just failed", which is why this issue is separate.
A tool Claude calls to load a rule. Rules exist to constrain the model, so
letting the model decide whether to load its own constraints inverts that.
InstructionsLoaded. Observability only. The documentation states it cannot block
or modify instruction loading.
Priority
Medium - Would be very helpful
Feature Category
Configuration and settings
Use Case Example
- My project keeps one skill per library convention: how we write Zod schemas,
how we shape Drizzle queries, our React Query cache keys. Around thirty of
them. Loading all thirty every session is not possible, which is exactly why
they are skills and not CLAUDE.md sections.
paths:cannot pick between them. All thirty apply tosrc/**/*.ts, so a
glob matches all of them or none. What decides which one applies is the
import line inside the file being edited. That is content, not a path, and a
glob cannot see content.
- A PreToolUse hook can. It reads the pending edit, sees
import { z } from "zod", and knows precisely which skill applies. It cannot
load it. Pasting is not an option either, because that skill is a folder with
references/ beside SKILL.md and additionalContext is a string.
- With
load:the hook returns { "skill": "zod-schemas" } and the one skill out
of thirty that matters is in context before the edit lands.
The same hook returns { "file": "packages/api/openapi.yaml" } when the edit
touches our generated API client, so Claude works from the live schema instead of
guessing, and knows the path well enough to re-read it later.
Additional Context
Claude Code 2.1.267.
https://code.claude.com/docs/en/hooks#add-context-for-claude
the additionalContext cap and the guidance against imperative text
https://code.claude.com/docs/en/hooks#sessionstart-decision-control
reloadSkills
https://code.claude.com/docs/en/hooks#instructionsloaded
"They can't block or modify instruction loading."
Related, and the declarative half of the same problem: paths: frontmatter on
rules and skills triggers on reads only. https://github.com/anthropics/claude-code/issues/93248