Skip to content

Per-Message Injection and Flags

Inject reminders, constraints, or dynamic context on every turn. Flags to toggle special behaviors without editing CLAUDE.md.

CLAUDE.md handles persistent instructions. But what about context that changes every turn? Your current branch. Files you've modified since the last prompt. Stuff that's different thirty seconds from now than it was thirty seconds ago.

That's what per-message injection is for.

Injecting Context on Every Message

UserPromptSubmit fires every time you send a prompt, right before Claude Code processes it. If your hook exits with code 0 and writes to stdout, that output becomes context attached to your message.

Branch and recent commits on every prompt:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"Branch: $(git branch --show-current). Last 3 commits: $(git log --oneline -3)\""
          }
        ]
      }
    ]
  }
}

Empty matcher means every prompt. Every time you hit enter, Claude Code runs that command, grabs the output, attaches it as additionalContext. You never paste your branch name again. You never explain what you were working on.

You could inject anything. Sprint goals. Coding standards reminders. Environment variables. Current date and time. List of files changed today. Whatever is useful and dynamic.

Surviving Compaction

Long sessions fill the context window. Claude Code compacts the conversation to free space. Problem: compaction can lose things. An important reminder you injected early in the session? Summarized away. Gone.

There's a clean fix for this. SessionStart has a "compact" matcher that fires specifically after compaction:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'Reminder: use Bun, not npm. Run bun test before committing. Current sprint: auth refactor.'"
          }
        ]
      }
    ]
  }
}

After every compaction, fresh reminders. Your rules survive no matter how long the session runs.

Make these dynamic if you can. Instead of a static echo, call a script that reads from a file or queries your project management tool. Reminders that reflect reality instead of what reality was when you wrote the hook.

PreCompact also exists, firing before compaction starts. Useful for controlling what gets preserved or logging context before compression.

Flags for One-Off Behavior

Sometimes you want different behavior for a single session. No config file editing. Just a flag.

claude --append-system-prompt "Always use TypeScript strict mode. Never use any."

Stacks on top of CLAUDE.md. Base instructions plus whatever you add. Frontend day? Append component pattern reminders. Security audit? Append stricter rules. Dead simple.

Flags and hooks combine well. A PreToolUse hook can check conditions before allowing tool calls. Block writes to certain directories during a read-only review. Require tests to pass before commits. You can get pretty creative with this.

Also worth mentioning: the Stop hook. Fires when Claude Code thinks it's done. Your hook checks if tasks are actually complete. Non-zero exit code? Claude Code keeps going. It doesn't get to decide it's finished until your hook agrees.

Try It

Set up a UserPromptSubmit hook. Edit .claude/settings.json:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "echo \"[Context] Branch: $(git branch --show-current) | Modified files: $(git diff --name-only | tr '\\n' ', ') | Node: $(node -v)\""
          }
        ]
      }
    ]
  }
}

Start a session, send any prompt. Claude Code knows your branch, changed files, and Node version without you saying a word. Modify a file, send another prompt. The list updates automatically.

Then add a compaction survivor: SessionStart with "compact" matcher, echoing your three most important project rules. Run a long session, wait for compaction, verify the rules still hold.

What to Inject (and What Not To)

Context window space is finite. Every injection eats into it.

Good injections: git branch, modified file list, active sprint name, critical constraints (package manager, language version). Things that shift constantly.

Bad injections: entire config files, full dependency lists, coding style guides. Those are static. They belong in CLAUDE.md where they load once.

Rule of thumb: if it changes every few minutes, use a hook. If it stays the same all week, put it in CLAUDE.md.

Key Takeaways

  • UserPromptSubmit injects dynamic context on every prompt (branch, modified files, env info)
  • SessionStart with "compact" matcher re-injects reminders after compaction
  • --append-system-prompt toggles behavior per-session, no config editing needed
  • Keep injections short and dynamic. Static stuff goes in CLAUDE.md.

Mastering Claude Code

Lesson 42 of 70