Use the ! prefix to run shell commands inside slash commands, automatically injecting diffs, file contents, and test results so Claude gets real information instead of vague descriptions
Most people's slash commands look like this:
Review the quality of the current code changes and give specific suggestions.
It works, but there's a fundamental limitation: Claude has to figure out on its own what "the current changes" actually are. If your command can proactively inject the context, the result is completely different.
This post is about turning a command into one that "has eyes" — it automatically reads file contents, git status, and project info at trigger time, stuffs them into the prompt, and saves Claude from guessing.
!`command` injects shell outputCustom slash commands let you embed shell commands inside the prompt. The syntax is to wrap the command in backticks and prefix it with an exclamation mark: !`command`. When triggered, the command runs first, its output replaces the placeholder, and what Claude receives is the fully assembled prompt.
Here is the current git diff:
!`git diff HEAD`
Please review these changes, focusing on logic errors and security issues.
For multi-line commands, use a fenced code block starting with
```!:```! node --version git status --short ```
When you trigger /review, what Claude actually sees is:
Here is the current git diff:
diff --git a/app/models/user.rb b/app/models/user.rb
index 3a2f1c8..9b4e2d1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -12,6 +12,9 @@ class User < ApplicationRecord
...
Please review these changes, focusing on logic errors and security issues.
No manual copy-paste. The moment the command fires, the diff is already inside.
---
allowed-tools: Bash(cat:*)
---
Here is the full content of the current file:
!`cat $ARGUMENTS`
Find every potential performance issue in this file and give specific line numbers and fixes.
Usage: /perf app/models/order.rb
$ARGUMENTS receives the file path, and cat reads the content into the prompt. Claude gets the actual code, not a vague "please look at the current file."
allowed-toolsin the frontmatter pre-authorizescat, so triggering the command doesn't pop up a permission prompt. Without this line, you'd have to click "Allow" every time. The examples below all need the same kind of declaration.
Current branch and change status:
!`git status --short`
!`git log --oneline -10`
Based on the above, generate a concise PR description that covers what changed and why.
This /pr command doesn't need you to describe "what I changed" — it reads it itself.
Project tech stack:
!`cat .claude/context/stack.md`
Current database schema (key tables):
!`head -100 db/schema.rb`
Based on the above, write a migration script for $ARGUMENTS that follows the project's conventions.
Put background info into .claude/context/ ahead of time, and let the command pull it in on demand. No need to re-explain the project every time.
Latest test run results:
!`bundle exec rspec --format progress 2>&1 | tail -30`
Above are the failing tests. Analyze the root cause and give a fix — don't change the tests themselves.
When /fix-tests fires, it runs the tests and grabs the output directly. Claude sees the real error messages.
Combine the above and /review becomes pretty sharp:
---
description: Review current changes, automatically injecting diff and relevant context
allowed-tools: Bash(git diff:*), Bash(cat:*)
---
## Current changes
!`git diff HEAD`
## Files touched
!`git diff HEAD --name-only`
## Project coding standards (summary)
!`cat .claude/context/coding-standards.md 2>/dev/null || echo "(no standards file)"`
---
Please review the changes above:
1. Correctness: any unhandled edge cases or logic errors
2. Security: SQL injection, authorization checks, exposed secrets
3. Standards: does it follow the project's coding standards
4. Readability: naming, comments, structure
For each issue, give the file, line number, and a specific suggestion. If there are no issues, say so — don't pad.
This command needs zero prep on your part — diff, file list, and coding standards are all injected automatically when it fires.
Shell commands can fail (file missing, command not on PATH, etc.). Use || to provide a fallback so the command doesn't blow up:
!`git diff HEAD 2>/dev/null || echo "(no git changes or not in a git repo)"`
!`cat .env.example 2>/dev/null || echo "(no .env.example file)"`
!`which rspec > /dev/null 2>&1 && bundle exec rspec --dry-run 2>&1 | head -20 || echo "(RSpec not detected)"`
When a command fails, Claude receives an explanatory message instead of a blank, and can adjust its strategy accordingly.
The output of ! commands all goes into the context window. A few common traps:
Don't cat a huge file
# Dangerous: could inject tens of thousands of lines
!`cat db/schema.rb`
# Better: only grab what you need
!`grep -A 5 "create_table \"orders\"" db/schema.rb`
Cap log files with line limits
!`tail -50 log/development.log`
Truncate test output
!`bundle exec rspec 2>&1 | tail -40`
The tighter your context, the better Claude's answers. Stuffing the entire codebase in doesn't make it smarter — it just buries it in noise.
A common question: should project background info live in CLAUDE.md, or get injected dynamically with !?
A simple rule:
| Type of info | Where it goes |
|---|---|
| Project background needed every time (tech stack, layout, conventions) | CLAUDE.md |
| State that changes over time (current diff, test results, file contents) | ! dynamic injection |
| Background only specific tasks need (design notes for a particular module) | !cat on demand |
CLAUDE.md is persistent background knowledge; ! is a real-time snapshot at task time. They complement each other — don't duplicate.
.claude/commands/ directory.claude/
├── commands/
│ ├── review.md # Review: inject diff + standards
│ ├── test.md # Write tests: inject target file
│ ├── fix-tests.md # Fix tests: inject failures
│ ├── pr.md # PR description: inject git log + status
│ ├── explain.md # Explain code: inject file content
│ └── migrate.md # Migrations: inject schema fragment
├── context/
│ ├── stack.md # Tech stack notes
│ └── coding-standards.md # Coding standards
└── settings.json
The context/ directory holds static background files for the various commands to read on demand. This whole structure can go straight into git and be shared across the team.
Static command:
Please review the current code changes.
Claude has to ask "where are the changes?" or hunt for them itself, and the result is hit-or-miss.
Context-aware command:
Here's the diff (actual content), here are the relevant standards (actual content), please review.
Claude jumps straight into analysis. Answers are sharp, no fluff, no back-and-forth.
The difference isn't in Claude's ability — it's in how much real information you handed it.