Every prompt you submit to Claude Code travels through a critical decision point before Claude sees it. That’s where UserPromptSubmit hooks live. They’re your gatekeepers—preprocessing input, enriching context, validating security, and routing requests intelligently. Let’s dig into how to build them effectively. These hooks are essential if you want Claude Code to be context-aware and safe at scale.
When you type a prompt and hit enter, your request doesn’t go straight to Claude. Instead, it flows through a preprocessing pipeline where hooks can read it, validate it, enrich it, or block it entirely. This preprocessing layer is where you embed organizational policies, security requirements, and intelligent context injection. It’s the difference between Claude Code feeling generic and Claude Code feeling like a deeply integrated part of your development workflow.
This matters enormously in real teams. A developer asks Claude Code something without realizing they’re about to paste a production database password. A question gets asked that should go to a security specialist instead of a generic agent. Someone is about to make a change that violates your team’s architecture standards. These are the moments where a good hook saves you from serious problems.
What Is a UserPromptSubmit Hook?
A UserPromptSubmit hook fires when a user submits a prompt to Claude Code, right before Claude processes it. It’s an event-driven intercept that gives you powerful capabilities:
- Input validation (checking for secrets, malformed requests, policy violations)
- Context injection (automatically attaching relevant git branch, project metadata, active tasks, recent errors)
- Prompt enrichment (connecting user intent to existing patterns, past failures, applicable skills, organizational knowledge)
- Routing logic (deciding which agent handles the request, or blocking entirely when necessary)
- Policy enforcement (blocking prompts that violate your guidelines, preventing accidental access to deprecated systems)
- Audit logging (recording all interactions for compliance and analysis)
Think of it as middleware for your prompt—you can read, modify, block, or forward it with extra data. The hook sees the raw user input and can augment it with system knowledge before passing it along. This is where you make Claude Code organizational-aware rather than just user-aware.
The power of this pattern is that it lets you build intelligence into the request layer without modifying Claude Code itself. You’re not forking the tool or maintaining custom versions. You’re just adding hooks that run on your infrastructure, respecting your requirements, understanding your context. This is where Claude Code becomes not just a tool but an extension of your development process.
When organizations successfully adopt Claude Code at scale, they’re not just installing software. They’ve woven Claude Code into the fabric of how they work. That happens through integration layers like UserPromptSubmit hooks. Every time someone asks Claude something, organizational knowledge injects context. When developers ask about testing, specialized testing agents answer. When they ask about infrastructure, infrastructure experts answer. When they’re about to make a policy violation, they get stopped and redirected toward proper channels. This doesn’t happen by magic—it happens because someone spent time building the integration layer carefully.
Hook Lifecycle and Timing: Understanding the Flow
When a user types a prompt, Claude Code executes this precise sequence:
- Captures input – Raw prompt text, current working directory, git metadata, user information, environment variables
- Serializes to JSON – Sends to hook via stdin with full context in a structured format
- Waits for hook response – Polls continuously for completion with tight time constraints:
- Exit code
0= allow (continue normally with original or modified prompt) - Exit code
2= block (show error message, stop processing immediately) - Exit code
1= error (log and continue with original prompt as fallback) - Injects hook output – Any console.log output becomes context prepended to the prompt Claude sees
- Processes enriched input – Claude now has both original prompt plus injected context from the hook
This happens in milliseconds. Speed matters enormously because users shouldn’t wait for hooks to complete before seeing results. The key insight is that hooks are preprocessing, not in-flight transformation. They can’t modify the prompt after Claude has started thinking about it. They can only add context beforehand, modify the prompt before submission, or block entirely. This architectural constraint shapes everything about how you design effective hooks.
Understanding this timing is crucial for designing effective hooks. You have a narrow window—typically less than one second for users to not consciously perceive the delay—to validate, enrich, and route the prompt. Hooks that exceed this window change user behavior. People start typing faster, interrupting them, working around them. They learn to anticipate delays and adjust their workflow to avoid hooks. This is why we emphasize performance optimization so heavily. A hook that’s objectively fast by traditional standards (five hundred milliseconds) becomes problematic when executed hundreds of times per day by multiple users across your organization.
The sequencing matters too. Validation hooks should run first—why spend time enriching a prompt you’re going to reject anyway? Enrichment hooks should run after validation succeeds. Policy enforcement can be mixed in, but generally validation-first is the right pattern. Logging hooks should be last and non-blocking because logging is informational, not safety-critical. If logging fails, the prompt should still proceed. If validation fails, everything stops.
This ordering isn’t just optimization—it’s fundamental to how hooks work together. A well-ordered hook chain feels invisible. Validation happens and you don’t know it. Enrichment happens and suddenly you have context you didn’t type. Policy checking happens and everything flows smoothly. Logging happens and you get a nice record. Each hook does its job and gets out of the way. This is what professional hook design looks like.
Why Hook Speed Matters: The Real Cost of Latency
Users tolerate a 100-millisecond hook. They accept a 500-millisecond hook during setup or when it’s doing something obviously valuable. But hooks slower than 1 second start changing behavior. People start working around them. They use --no-hook flags if available. They change their workflow to avoid slow preprocessing. They batch operations differently to reduce hook executions. All of this reduces the value you get from the hook.
The most important optimization is avoiding redundant work. Don’t re-scan files you’ve already scanned. Cache results aggressively. Use in-memory lookups instead of filesystem reads when possible. Lazy-load data only when needed. For hooks that might be inherently slow (like fetching from a remote service), make them non-blocking so they don’t slow down prompt submission. You might show users results asynchronously after they’ve started working.
Consider the compound effect. If a hook takes 500 milliseconds and your organization has fifty developers each using Claude Code twenty times a day, that’s fifty thousand hook executions per day, consuming 25,000 seconds of total execution time. That’s seven hours of machine time spent on a single hook daily. Suddenly you’re burning significant infrastructure. Fast hooks aren’t just about user experience—they’re about resource efficiency at scale. A five-minute optimization to your hook logic might save thousands of dollars monthly in infrastructure costs.
Input Validation: Blocking Dangerous Prompts
The most critical hook is validation. You don’t want users accidentally pasting AWS keys, GitHub tokens, database passwords, or API credentials into their prompts—and neither does Claude. A simple pattern-based validation can catch 90 percent of credential leakage without being invasive or generating false positives.
Here’s a production-ready validation hook that scans for credentials and other sensitive data:
#!/usr/bin/env node
/**
* userPromptSubmit/validate-prompt.mjs
* Blocks prompts containing actual credentials and sensitive data
*/
const credentialPatterns = [
{
pattern: /password\s*[:=]\s*["']?[^\s"']{8,}["']?/i,
reason: "Password detected",
},
{
pattern: /api[_-]?key\s*[:=]\s*["']?[a-zA-Z0-9_-]{20,}["']?/i,
reason: "API key detected",
},
{
pattern: /secret\s*[:=]\s*["']?[a-zA-Z0-9_-]{20,}["']?/i,
reason: "Secret detected",
},
{ pattern: /sk_live_[a-zA-Z0-9]{24,}/, reason: "Stripe live key detected" },
{
pattern: /ghp_[a-zA-Z0-9]{36,}/,
reason: "GitHub personal access token detected",
},
{
pattern: /-----BEGIN (RSA |DSA |EC |OPENSSH )?PRIVATE KEY-----/,
reason: "Private key detected",
},
{ pattern: /AWS[A-Z0-9]{16,}/, reason: "AWS key pattern detected" },
{
pattern: /eyJ[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}\.[a-zA-Z0-9_-]{10,}/,
reason: "JWT token detected",
},
];
async function main() {
const input = await readStdin();
const prompt = input.prompt || "";
const user = input.user || "unknown";
for (const rule of credentialPatterns) {
if (rule.pattern.test(prompt)) {
return block({
message: `🔒 Security Check: ${rule.reason}\n\nYour prompt contains what looks like a ${rule.reason.toLowerCase()}. Please remove sensitive credentials before submitting.\n\nNever share:\n• API keys or tokens\n• Passwords or secrets\n• Private keys\n• Database credentials\n• AWS keys or Stripe keys\n• JWT tokens\n\nIf you need to discuss credentials, describe them (e.g., "my AWS key") instead of sharing the actual value.`,
});
}
}
return allow();
}
main().catch((err) => {
console.error("Hook error:", err);
process.exit(1);
});
This hook is simple but effective. It catches the most common credential patterns. When triggered, it blocks the prompt and explains why, guiding users toward safer behavior. The patterns are carefully tuned to catch real credentials while avoiding false positives. A regex like /password\s*[:=]\s*["']?[^\s"']{8,}["']?/i will match password=secret123 but won’t match the word “password” in a sentence describing something about passwords.
Context Injection: Making Claude Contextual
The second type of hook is context injection. Instead of blocking, you augment the prompt with information Claude needs. This is where Claude Code becomes organizationally aware. When Claude understands your current git branch, recent commits, active issues, and team standards, it becomes exponentially more useful.
For example, automatically attach git context:
#!/usr/bin/env node
/**
* userPromptSubmit/inject-context.mjs
* Enriches prompts with project context
*/
async function getGitContext() {
try {
const branch = await exec("git rev-parse --abbrev-ref HEAD");
const lastCommit = await exec("git log -1 --oneline");
const status = await exec("git status --short");
const remoteUrl = await exec("git config --get remote.origin.url");
return `
[CONTEXT INJECTED BY HOOK]
Current branch: ${branch.trim()}
Remote: ${remoteUrl.trim()}
Last commit: ${lastCommit.trim()}
Uncommitted changes:
${status.trim() || "none"}`;
} catch (err) {
// Git not available or error occurred
return "";
}
}
async function main() {
const input = await readStdin();
let prompt = input.prompt || "";
const context = await getGitContext();
prompt += context;
return allow({ prompt });
}
main().catch((err) => {
console.error("Hook error:", err);
process.exit(1);
});
Now every prompt Claude receives includes git context. If someone asks “What changed?”, Claude immediately knows the branch and recent commits. If someone asks “Why is the build failing?”, Claude can see which files changed and suggest likely culprits. The hook is invisible but invaluable.
This context injection pattern scales beautifully. You can inject:
- Active tasks from your project management system (Jira, GitHub Projects, Linear)
- Recent errors from your logs (last 5 error stack traces)
- Test results from the last build (which tests failed, why)
- Documentation about your codebase (architecture decisions, style guides)
- Standards and conventions your team follows (naming patterns, folder structure)
- Active bugs or known issues (what’s currently broken that we’re working around)
- Team member expertise (who to ask about what)
Each piece of context makes Claude more effective without the user having to type it. The synergy is remarkable—when Claude knows your context deeply, the quality of its suggestions improves drastically.
Routing Logic: Directing Prompts to Appropriate Agents
Some prompts benefit from specialized handling. A query about testing should go to a test-focused agent. A question about infrastructure should go to your DevOps expert agent. A request for code review should go to a code review agent. You can build this routing into the hook layer.
#!/usr/bin/env node
/**
* userPromptSubmit/route-prompt.mjs
* Routes prompts to appropriate agents based on intent
*/
const routes = [
{
keywords: ["test", "unit", "integration", "e2e", "spec", "coverage"],
agent: "test-engineer",
context:
"You are a testing specialist. Focus on test coverage, patterns, and best practices. Recommend using the arrange-act-assert pattern.",
},
{
keywords: [
"deploy",
"devops",
"infrastructure",
"kubernetes",
"docker",
"helm",
],
agent: "devops-expert",
context:
"You are a DevOps specialist. Focus on deployment, scaling, reliability, and infrastructure as code. Think about observability.",
},
{
keywords: [
"security",
"vulnerability",
"attack",
"exploit",
"threat",
"sanitize",
],
agent: "security-specialist",
context:
"You are a security specialist. Assess threats and recommend mitigations. Always think about attack surface.",
},
{
keywords: [
"performance",
"slow",
"latency",
"optimize",
"profile",
"benchmark",
],
agent: "performance-engineer",
context:
"You are a performance specialist. Identify bottlenecks and optimization opportunities. Profile before optimizing.",
},
{
keywords: ["database", "sql", "query", "index", "schema", "migration"],
agent: "database-expert",
context:
"You are a database specialist. Design schemas for performance and consistency. Always think about indexes.",
},
];
async function main() {
const input = await readStdin();
let prompt = input.prompt || "";
const promptLower = prompt.toLowerCase();
for (const route of routes) {
const hasKeyword = route.keywords.some((kw) => promptLower.includes(kw));
if (hasKeyword) {
prompt = `[ROUTED TO: ${route.agent}]\n${route.context}\n\nUser request: ${prompt}`;
return allow({ prompt });
}
}
// No specific route matched, use default
return allow();
}
main().catch((err) => {
console.error("Hook error:", err);
process.exit(1);
});
With this routing hook, a prompt about testing automatically goes to an agent trained on test patterns. A prompt about DevOps goes to an infrastructure expert. A prompt about performance goes to someone who understands profiling and optimization. No extra user work. The system intelligently routes based on content.
This is particularly powerful when combined with agent specialization. You’ve trained agents on different domains. The hook recognizes the domain and routes the prompt to the expert agent for that domain. Claude becomes less generic and more specialist. The results are dramatically better because the agent has been fine-tuned for the specific domain.
Policy Enforcement: Blocking Organizational Violations
Some organizations need to enforce policies in real-time. Don’t allow work on deprecated systems. Don’t allow changes to production without explicit confirmation. Don’t allow access to certain sensitive modules without approval. These aren’t just nice-to-haves—they’re regulatory requirements for many companies.
Here’s a policy enforcement hook:
#!/usr/bin/env node
/**
* userPromptSubmit/enforce-policies.mjs
* Blocks prompts that violate organizational policies
*/
const policies = [
{
pattern: /legacy[_-]?system|deprecated|old[_-]?api/i,
message:
"Our organization has deprecated that system. Please work with the migration team.",
override: "APPROVED_BY_TECH_LEAD",
},
{
pattern: /production.*delete|drop.*table.*production|destroy.*prod/i,
message:
"Production destructive operations require explicit approval and a documented rollback plan.",
override: "AUTHORIZED_DESTRUCTIVE_COMMAND",
},
{
pattern: /admin[_-]?panel|internal[_-]?tools|secret[_-]?endpoints/i,
message:
"Admin panel work requires security review. Check with the security team first.",
override: "SECURITY_APPROVED",
},
{
pattern: /bypass[_-]?auth|skip[_-]?validation|disable[_-]?checks/i,
message:
"Bypassing security checks is not allowed without explicit approval.",
override: "SECURITY_WAIVER_APPROVED",
},
];
async function main() {
const input = await readStdin();
const prompt = input.prompt || "";
for (const policy of policies) {
if (policy.pattern.test(prompt)) {
const overrideKeyword = input.metadata?.overrideKeyword || "";
if (overrideKeyword === policy.override) {
console.log(
`Policy override approved: ${policy.override}. Proceeding with policy-restricted action.`,
);
return allow();
}
return block({
message: `⚠️ Policy Enforcement\n\n${policy.message}\n\nTo override this check, include "${policy.override}" in your message.`,
});
}
}
return allow();
}
main().catch((err) => {
console.error("Hook error:", err);
process.exit(1);
});
This hook prevents users from accidentally performing actions that violate organizational policy. It’s not about preventing incompetence—it’s about adding a safety checkpoint for high-impact actions. It’s similar to requiring confirmation for destructive operations, but at the prompt level rather than the execution level. When someone asks Claude Code to help with something policy-restricted, they get immediate feedback that they need special approval and they know exactly how to get it.
Logging and Audit: Building Compliance Records
Every organization needs logs. Who asked Claude what? When? What was the result? For compliance, security auditing, and understanding how Claude Code is being used, logging hooks are essential. In regulated industries (finance, healthcare, legal), audit trails aren’t optional—they’re mandatory.
#!/usr/bin/env node
/**
* userPromptSubmit/audit-log.mjs
* Logs all prompts for audit trail
*/
async function main() {
const input = await readStdin();
const timestamp = new Date().toISOString();
const auditEntry = {
timestamp,
user: input.user || "unknown",
cwd: input.cwd || process.cwd(),
prompt: input.prompt || "",
promptLength: (input.prompt || "").length,
metadata: {
terminal: !!input.interactive,
branch: input.git?.branch || "unknown",
hostname: input.hostname || "unknown",
},
};
const logPath = path.join(process.env.CLAUDE_LOG_DIR || ".", "audit.jsonl");
try {
fs.appendFileSync(logPath, JSON.stringify(auditEntry) + "\n");
} catch (err) {
console.error("Failed to write audit log:", err);
// Don't block the prompt if logging fails
}
return allow();
}
main().catch((err) => {
console.error("Hook error:", err);
process.exit(1);
});
This hook logs every interaction. Not for surveillance—for understanding. After a month, you can analyze: What are developers asking Claude most? Which features are underutilized? Are there patterns in the types of requests? What domains get the most attention? This data guides feature development and training.
For compliance requirements (HIPAA, SOC 2, ISO 27001, etc.), audit logs are non-negotiable. This hook provides the foundation. You can later extend it to include response hashes (without storing the full response for privacy), execution time, and outcome.
Combining Hooks: The Full Pipeline
Most organizations use multiple hooks together. Here’s how they compose:
- Validation hook runs first – blocks prompts with secrets
- Policy hook runs – blocks policy violations
- Audit hook runs – logs the attempt (even if blocked)
- Context injection hook runs – adds organizational context
- Routing hook runs – directs to appropriate agent
- Prompt sent to Claude with enriched context
Each hook is independent. Each does one job. Together they build a sophisticated system that feels intelligent and responsive to organizational needs.
Performance Optimization: Making Hooks Fast
Hooks must be fast. Slow hooks kill adoption. Here are optimization strategies:
Caching: Cache frequently accessed data. If you’re checking git branch every prompt, cache it for five seconds. Git operations are relatively fast, but five hundred milliseconds per prompt multiplies across hundreds of developers.
Early exit: Check the cheapest validations first. Checking prompt length is free. Scanning for credentials is more expensive. Checking policy is medium cost. Order them cheaply-to-expensively.
Async where safe: Non-critical logging can be async. The user submits their prompt while logging happens in the background.
Pre-computation: If possible, pre-compute context during IDE startup rather than on every prompt. Store it in memory and just reference it.
Here’s an optimized hook:
#!/usr/bin/env node
/**
* userPromptSubmit/optimized-enrichment.mjs
* Fast context injection with caching
*/
let cachedContext = null;
let cacheTime = 0;
const CACHE_TTL = 5000; // 5 second cache
async function getContext() {
const now = Date.now();
// Return cached context if still fresh
if (cachedContext && now - cacheTime < CACHE_TTL) {
return cachedContext;
}
// Compute new context (this would normally call git, read files, etc)
const context = {
branch: "main",
time: new Date().toISOString(),
environment: process.env.NODE_ENV || "development",
};
cachedContext = context;
cacheTime = now;
return context;
}
async function main() {
const input = await readStdin();
const context = await getContext();
const enrichedPrompt =
input.prompt +
`\n\n[Context: branch=${context.branch}, env=${context.environment}]`;
return allow({ prompt: enrichedPrompt });
}
main().catch((err) => {
console.error("Hook error:", err);
process.exit(1);
});
Caching brings hook execution from hundreds of milliseconds to near-instant.
Testing Hooks: Verification and Debugging
Before deploying a hook, test it thoroughly:
// test-hook.js
const testCases = [
{
input: { prompt: "password = secret123" },
expectedAction: "block",
name: "blocks password",
},
{
input: { prompt: "What is 2 + 2?" },
expectedAction: "allow",
name: "allows normal prompt",
},
{
input: { prompt: "Deploy to production" },
expectedAction: "allow", // This one needs override to block
name: "allows production request without blocking",
},
{
input: { prompt: "Drop table users in production" },
expectedAction: "block",
name: "blocks destructive production command",
},
];
for (const test of testCases) {
const result = execSync(
`echo '${JSON.stringify(test.input).replace(/'/g, "'\\''")}' | node hook.mjs`,
{ encoding: "utf8" },
);
const passed = result.includes(test.expectedAction);
console.log(`${test.name}: ${passed ? "✓" : "✗"}`);
if (!passed) {
console.log(` Expected: ${test.expectedAction}`);
console.log(` Got: ${result.slice(0, 100)}`);
}
}
Test-driven hook development catches bugs before they affect users. You want to know that your credential detection doesn’t have false positives. You want to know that your routing logic directs prompts correctly. Testing prevents embarrassing failures.
The Real Power: Making Claude Organizational
UserPromptSubmit hooks are where Claude Code stops being a generic tool and becomes deeply integrated into your organization. With validation, you prevent security leaks. With context injection, Claude becomes aware of your projects, your standards, your practices. With routing, Claude becomes specialized. With policy enforcement, you embed organizational rules. With logging, you have transparency.
The combined effect is that Claude Code feels like it was built for your organization. It knows your context. It respects your policies. It routes intelligently. It prevents mistakes. This is what separates organizations that successfully adopt Claude Code from those that don’t. It’s not the tool—it’s the integration layer.
Think about organizations that have successfully embedded AI into their workflows. They haven’t just installed software. They’ve woven AI into the fabric of how they work. That happens through integration layers like UserPromptSubmit hooks. Every time someone asks Claude something, the organization’s knowledge injects context. When developers ask about testing, the testing specialist agent answers. When they ask about infrastructure, the infrastructure expert answers. When they’re about to make a policy violation, they get stopped and redirected. This doesn’t happen by magic—it happens because the organization spent time building the integration layer.
The power multiplier effect is enormous. A generic Claude Code installation maybe saves you twenty percent of development time. An integrated, customized installation with well-designed hooks saves you forty, fifty, sometimes sixty percent of time because Claude understands your context deeply. Claude knows what’s important to your organization. Claude knows your standards. Claude routes intelligently to specialized agents. The time savings compound because developers are no longer context-switching between Claude and your documentation. Claude is your documentation, personalized to your specific needs.
Build your hooks carefully. Test them thoroughly. Deploy gradually. Monitor performance. Tune based on data. Your organization will thank you.
—iNet