一、user-prompt-submit Hook概述
user-prompt-submit 是 Claude Code Hook 系统中最核心的钩子之一。它在用户向 Claude 提交提示词时触发,允许开发者在提示词发送给模型之前(before)和收到模型回复之后(after)执行自定义脚本,从而实现提示词预处理、安全检查、内容增强、对话管理等功能。
该 Hook 的工作机制分为两个阶段:
- before 阶段:在用户提示词发送给大模型之前执行。开发者可以在此阶段检查、修改、增强甚至阻断提示词的提交。常用于内容长度检查、关键词过滤、提示词增强等场景。
- after 阶段:在模型生成回复并返回给用户之后执行。开发者可以在此阶段处理对话历史、记录日志、管理上下文窗口等。
核心价值:user-prompt-submit Hook 让开发者能够以非侵入式的方式在 Claude 对话流程中注入自定义逻辑,无需修改 Claude Code 本身即可实现强大的提示工程自动化。
Hook 脚本可以是一个可执行文件(如 .sh、.bat、.py、.js 等),Claude Code 会自动检测项目中的 hook 配置并调用。配置文件通常位于项目根目录的 .claude/hooks.json 中。
// .claude/hooks.json 配置示例
{
"hooks": {
"user-prompt-submit": {
"command": "python scripts/prompt_hook.py",
"timeout": 5000
}
}
}
二、提示内容长度检查Hook(before)
在 before 阶段,可以读取用户输入的提示词长度,判断其是否超出模型的令牌(token)限制。当提示词过长时,可以拆分长提示、向用户发出警告或直接阻断执行,避免因上下文溢出导致生成质量下降。
2.1 读取提示内容与环境变量
Claude Code 在调用 Hook 时会通过环境变量将当前上下文信息传递给脚本。关键的环境变量包括:
CLAUDE_HOOK_TYPE:Hook 类型,值为 user-prompt-submit
CLAUDE_HOOK_PHASE:阶段,值为 before 或 after
CLAUDE_PROMPT:用户提交的提示词内容(base64 编码)
CLAUDE_CONVERSATION_ID:当前对话的唯一标识
2.2 令牌检查实现
以下是一个 Python 实现的令牌长度检查 Hook 示例,当用户提示词超出限制时给出警告并允许用户确认是否继续:
#!/usr/bin/env python3
# scripts/token_check_hook.py
import os
import sys
import base64
import json
import math
# 读取环境变量
hook_phase = os.environ.get("CLAUDE_HOOK_PHASE", "")
if hook_phase != "before":
sys.exit(0)
# 解码提示内容
encoded_prompt = os.environ.get("CLAUDE_PROMPT", "")
try:
prompt_text = base64.b64decode(encoded_prompt).decode("utf-8")
except Exception:
sys.exit(0)
# 估算令牌数(中文约1.5字符/令牌,英文约4字符/令牌)
char_count = len(prompt_text)
# 简单估算:混合内容平均约2.5字符/令牌
estimated_tokens = math.ceil(char_count / 2.5)
# 设置阈值
WARN_THRESHOLD = 60000 # 超过6万令牌时警告
BLOCK_THRESHOLD = 100000 # 超过10万令牌时阻断
if estimated_tokens > BLOCK_THRESHOLD:
print(f"提示词过长(约{estimated_tokens}令牌),已超过最大限制。")
print("请缩短提示词后重试。")
sys.exit(1) # 非零退出码表示阻断
if estimated_tokens > WARN_THRESHOLD:
print(f"提示词较长(约{estimated_tokens}令牌),接近上下文限制。")
print("建议精简提示词以获得更好的生成效果。")
# 退出码0表示允许继续,但输出会显示给用户
# 输出标准JSON格式的元数据供Claude Code参考
result = {
"char_count": char_count,
"estimated_tokens": estimated_tokens,
"status": "ok" if estimated_tokens <= WARN_THRESHOLD else "warning"
}
print(json.dumps(result, ensure_ascii=False))
sys.exit(0)
重要:Hook 脚本的退出码(exit code)决定了是否阻断提示提交。退出码 0 表示允许继续,非零退出码会阻断提示词发送到模型。before 阶段脚本的输出将显示在 Claude Code 的提示确认界面中。
2.3 配置与测试
将 Hook 配置到项目中后,当提交超长提示词时,Claude Code 会先执行令牌检查脚本,在确认界面显示警告信息。用户可以根据提示决定继续提交或修改提示词。
最佳实践:令牌检查阈值应根据实际使用的模型上下文窗口设置。Claude 3.5 Sonnet 的上下文窗口为 200K 令牌,但为了保证生成质量,建议在 60K-80K 令牌时提醒用户。
三、关键词过滤和安全检查Hook(before)
在 before 阶段实施关键词过滤和安全检查,可以有效防止用户提交包含敏感内容或潜在危险指令的提示词。这不仅是安全合规的需要,也是保护系统稳定性的重要措施。
3.1 敏感关键词过滤
通过维护一个敏感词列表,在用户提交提示时进行扫描匹配。匹配到敏感词时的处理策略包括:替换为安全内容、提示用户修改、或直接阻断执行。
#!/usr/bin/env python3
# scripts/safety_filter_hook.py
import os
import sys
import base64
import re
hook_phase = os.environ.get("CLAUDE_HOOK_PHASE", "")
if hook_phase != "before":
sys.exit(0)
encoded_prompt = os.environ.get("CLAUDE_PROMPT", "")
try:
prompt_text = base64.b64decode(encoded_prompt).decode("utf-8")
except Exception:
sys.exit(0)
# 敏感关键词列表(示例)
sensitive_keywords = [
r"(?i)执行系统(命令|指令)",
r"(?i)删除所有文件",
r"(?i)格式化硬盘",
r"(?i)关闭安全(措施|检查)",
r"(?i)绕过(权限|限制|认证)",
r"(?i)获取他人(密码|隐私|数据)",
]
# 安全替换规则
replace_rules = {
r"(?i)使用真实数据": "[使用测试数据]",
}
# 检查敏感词
found_sensitive = []
for pattern in sensitive_keywords:
matches = re.findall(pattern, prompt_text)
if matches:
found_sensitive.append(pattern)
if found_sensitive:
print("检测到提示词中包含以下敏感内容:")
for s in found_sensitive:
print(f" - {s}")
print("请移除相关描述后重新提交。")
sys.exit(1) # 阻断
# 执行安全替换
modified_prompt = prompt_text
for pattern, replacement in replace_rules.items():
modified_prompt = re.sub(pattern, replacement, modified_prompt)
# 如果内容被替换,需要通知Claude Code新的提示内容
if modified_prompt != prompt_text:
encoded_modified = base64.b64encode(
modified_prompt.encode("utf-8")
).decode("utf-8")
print(f"CLAUDE_PROMPT={encoded_modified}")
print("已自动替换敏感内容,请确认后继续。")
sys.exit(0)
3.2 安全审计日志
所有安全检查事件都应记录到日志文件中,以便事后审计和追溯。日志应包含时间戳、用户标识、触发规则和处理结果。
日志示例格式:每次安全过滤触发时,记录 audit.log 条目:
[2026-05-08 10:00:00] 用户: user1 | 规则: 删除所有文件 | 处理: 阻断 | 会话: conv_abc123
# 日志记录辅助函数示例
import logging
from datetime import datetime
def setup_audit_logger(log_path="audit.log"):
logger = logging.getLogger("prompt_audit")
handler = logging.FileHandler(log_path)
formatter = logging.Formatter(
"[%(asctime)s] 用户: %(user)s | 规则: %(rule)s | 处理: %(action)s | 会话: %(conv)s"
)
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)
return logger
# 使用
audit_logger = setup_audit_logger()
audit_logger.info("", extra={
"user": os.environ.get("USER", "unknown"),
"rule": "sensitive_keyword_match",
"action": "blocked",
"conv": os.environ.get("CLAUDE_CONVERSATION_ID", "unknown")
})
3.3 处理策略对比
| 策略 |
适用场景 |
退出码 |
用户体验 |
| 提示修改(replace) |
非恶意但需规范化的内容 |
0 |
自动替换后提示用户确认 |
| 警告放行(warn) |
轻微违规但可自行判断 |
0 |
显示警告但不阻止 |
| 直接阻断(block) |
高危操作或敏感内容 |
1 |
显示原因并阻止提交 |
四、提示词增强Hook(before)
提示词增强是最强大的应用场景之一。通过在 before 阶段自动向用户提示词中添加项目级上下文、编码规范、格式要求等增强信息,可以显著提升 Claude 的回复质量和一致性。
4.1 自动附加项目上下文
根据项目类型和当前工作内容,自动向提示词中插入项目相关的上下文信息,帮助 Claude 更好地理解项目的技术栈、架构和编码规范。
#!/usr/bin/env python3
# scripts/prompt_enhancer.py
import os
import sys
import base64
import json
hook_phase = os.environ.get("CLAUDE_HOOK_PHASE", "")
if hook_phase != "before":
sys.exit(0)
encoded_prompt = os.environ.get("CLAUDE_PROMPT", "")
try:
prompt_text = base64.b64decode(encoded_prompt).decode("utf-8")
except Exception:
sys.exit(0)
# 项目增强配置
enhancements = {
"python": {
"header": "【项目规范】本项目使用 Python 3.11+,遵循 PEP 8 编码规范。"
"类型注解是必须的。使用 pytest 进行测试。",
"keywords": ["python", "django", "flask", "fastapi", "pip"]
},
"javascript": {
"header": "【项目规范】本项目使用 TypeScript + React 18。"
"使用 ESLint + Prettier 规范代码风格。",
"keywords": ["javascript", "typescript", "react", "vue", "node"]
},
"default": {
"header": "【通用规范】请提供清晰、完整、可维护的代码解决方案。"
"包含必要的注释和错误处理。"
}
}
# 检测项目类型(简化示例)
project_type = "default"
for ptype, config in enhancements.items():
if ptype == "default":
continue
for kw in config["keywords"]:
if kw in prompt_text.lower():
project_type = ptype
break
# 获取当前工作目录中的配置文件信息
config_files = {
"pyproject.toml": "python",
"package.json": "javascript",
"Cargo.toml": "rust",
"go.mod": "go"
}
for cfg_file, ptype in config_files.items():
if os.path.exists(cfg_file):
project_type = ptype
break
# 构建增强后的提示词
enhanced_prompt = enhancements[project_type]["header"] + "\n\n" + prompt_text
# 编码并输出
encoded_enhanced = base64.b64encode(
enhanced_prompt.encode("utf-8")
).decode("utf-8")
# 告知Claude Code使用修改后的提示词
print(f"CLAUDE_PROMPT={encoded_enhanced}")
print(f"已自动附加 {project_type} 项目规范上下文")
sys.exit(0)
4.2 增强策略配置化
将增强规则抽取为独立的 JSON 配置文件,避免硬编码。这样不同项目可以共享相同的 Hook 脚本,只需更换配置文件即可。
{
"version": "1.0",
"rules": [
{
"name": "中文学术增强",
"match_type": "always",
"append_header": "请使用中文回答,术语首次出现时标注英文原文。"
"引用来源需注明出处。"
},
{
"name": "代码审查模式",
"match_type": "keyword",
"keywords": ["review", "code review", "审查代码", "CR"],
"append_header": "请从以下几个方面进行代码审查:"
"1. 安全漏洞和潜在风险"
"2. 性能瓶颈和优化建议"
"3. 代码风格和可维护性"
"4. 测试覆盖率和边界情况"
"请给出具体的代码行号和修改建议。"
},
{
"name": "调试模式",
"match_type": "keyword",
"keywords": ["debug", "调试", "bug", "error", "报错"],
"append_header": "请按以下步骤分析问题:"
"1. 复现问题步骤"
"2. 分析错误信息和堆栈"
"3. 定位根因"
"4. 提供修复方案"
"5. 添加预防措施"
}
]
}
提示:提示词增强应保持克制。过度增强会消耗宝贵的上下文窗口,反而降低模型对用户核心问题的关注度。建议每次增强附加的内容控制在 200 令牌以内。
4.3 多规则组合策略
实际应用中,同一个提示词可能同时匹配多个增强规则。需要设计合理的优先级和组合策略:
- 优先匹配原则:当多个规则匹配时,优先使用匹配度最高(关键词命中数最多)的规则
- 拼接策略:低优先级规则的增强内容可以附加在末尾,形成层次化的增强结构
- 互斥规则:某些规则互斥(如"简洁模式"和"详细模式"),应只应用优先级最高的一个
实战经验:为每种命令类型(如 debug、review、implement、refactor)编写专门的增强提示词。通过提示词中出现的动词自动判断用户意图类型,匹配最合适的增强策略。这样可以实现零配置的智能提示增强。
五、多轮对话管理Hook(after)
在 after 阶段,可以在模型回复返回给用户后执行对话管理逻辑。这对于维护对话历史、管理上下文窗口、记录会话日志等场景非常有用。
5.1 对话历史记录
自动记录每一轮对话的提示词和回复内容,保存为结构化的日志文件,便于后续查阅、分析和调试。
#!/usr/bin/env python3
# scripts/conversation_logger.py
import os
import sys
import base64
import json
from datetime import datetime
from pathlib import Path
hook_phase = os.environ.get("CLAUDE_HOOK_PHASE", "")
if hook_phase != "after":
sys.exit(0)
# 读取环境变量
conv_id = os.environ.get("CLAUDE_CONVERSATION_ID", "unknown")
encoded_prompt = os.environ.get("CLAUDE_PROMPT", "")
try:
prompt_text = base64.b64decode(encoded_prompt).decode("utf-8")
except Exception:
prompt_text = "[解码失败]"
# 从stdin读取模型回复(after阶段可用)
response_text = ""
try:
response_length = int(os.environ.get("CLAUDE_RESPONSE_BYTES", "0"))
if response_length > 0:
response_text = sys.stdin.read(response_length)
except Exception:
pass
# 构建日志记录
log_entry = {
"timestamp": datetime.now().isoformat(),
"conversation_id": conv_id,
"prompt_preview": prompt_text[:200] + "..." if len(prompt_text) > 200 else prompt_text,
"prompt_length": len(prompt_text),
"response_length": len(response_text),
}
# 保存到对话日志
log_dir = Path(".claude/conversations")
log_dir.mkdir(parents=True, exist_ok=True)
log_file = log_dir / f"{conv_id}.jsonl"
with open(log_file, "a", encoding="utf-8") as f:
f.write(json.dumps(log_entry, ensure_ascii=False) + "\n")
print(f"对话记录已保存: {log_file}")
sys.exit(0)
5.2 上下文窗口管理
多轮对话中,随着对话轮次增加,上下文窗口可能被填满。可以在 after 阶段检查当前对话的令牌使用量,并在接近上限时发出警告或自动触发摘要压缩。
# 上下文窗口管理逻辑(after阶段)
import math
def check_context_usage(prompt_text, response_text, conv_id):
total_chars = len(prompt_text) + len(response_text)
estimated_tokens = total_chars / 2.5
# 读取已积累的上下文统计
quota_file = f".claude/quota/{conv_id}.json"
quota_data = {"total_tokens": 0, "turn_count": 0}
if os.path.exists(quota_file):
with open(quota_file, "r") as f:
quota_data = json.load(f)
# 更新统计
quota_data["total_tokens"] += estimated_tokens
quota_data["turn_count"] += 1
# 检查阈值
MAX_TOKENS = 200000 # 模型最大上下文
WARN_AT = 160000 # 警告阈值
os.makedirs(os.path.dirname(quota_file), exist_ok=True)
with open(quota_file, "w") as f:
json.dump(quota_data, f)
if quota_data["total_tokens"] > WARN_AT:
remaining = MAX_TOKENS - quota_data["total_tokens"]
print(f"对话已累积约 {int(quota_data['total_tokens'])} 令牌,"
f"剩余约 {int(remaining)} 令牌。建议开始新对话。")
if remaining < 10000:
print("严重警告:上下文即将耗尽,请立即创建新对话!")
else:
print(f"当前对话第 {quota_data['turn_count']} 轮,"
f"已使用约 {int(quota_data['total_tokens'])} / {MAX_TOKENS} 令牌")
5.3 自动清理过期对话
在每次 after Hook 执行时,可以顺便检查并清理过期的对话记录,避免历史日志无限增长占用磁盘空间。
# 过期对话清理逻辑
import time
from pathlib import Path
CLEANUP_INTERVAL = 86400 # 每天执行一次清理
RETENTION_DAYS = 30 # 保留30天的对话记录
# 使用标记文件控制清理频率
last_cleanup_file = Path(".claude/last_cleanup")
last_cleanup = 0
if last_cleanup_file.exists():
try:
last_cleanup = int(last_cleanup_file.read_text().strip())
except ValueError:
pass
now = time.time()
if now - last_cleanup > CLEANUP_INTERVAL:
cutoff = now - (RETENTION_DAYS * 86400)
conv_dir = Path(".claude/conversations")
if conv_dir.exists():
cleaned = 0
for f in conv_dir.iterdir():
if f.is_file() and f.stat().st_mtime < cutoff:
f.unlink()
cleaned += 1
last_cleanup_file.write_text(str(int(now)))
if cleaned > 0:
print(f"已清理 {cleaned} 个过期对话记录(超过 {RETENTION_DAYS} 天)")
5.4 对话统计与洞察
利用积累的对话日志,可以分析使用模式,生成有价值的统计信息:
| 指标 |
描述 |
用途 |
| 平均提示词长度 |
每轮对话用户输入的平均字符数 |
了解用户使用习惯,优化提示工程 |
| 平均对话轮次 |
每次会话的平均交互次数 |
评估任务复杂度,改进上下文管理 |
| 高频触发规则 |
哪些安全或增强规则被频繁触发 |
优化规则配置,减少误报 |
| 阻断率 |
提示词被阻断的比例 |
评估安全策略的严格程度 |
日志管理建议:对话日志可能包含敏感信息,建议:1) 对日志文件设置严格的读写权限;2) 不要在版本控制中提交日志目录;3) 定期轮转和归档日志;4) 考虑对提示词内容进行脱敏处理后再记录。
核心要点总结:
1. user-prompt-submit Hook 在用户提交提示词时触发,分为 before(预处理)和 after(后处理)两个阶段。
2. before 阶段的退出码决定是否阻断提示词提交:0 允许继续,非零阻断。
3. before 阶段可以通过输出 CLAUDE_PROMPT=... 来修改即将发送给模型的提示词内容。
4. 令牌检查、关键词过滤、提示词增强是 before 阶段的三大典型应用。
5. after 阶段更适合对话日志记录、上下文窗口管理和统计分析。
6. Hook 脚本支持任何可执行语言(Python、Bash、Node.js 等),通过环境变量与 Claude Code 交互。
7. 退出码是控制流程的核心机制,合理利用可以实现精细化的提示词处理流水线。