Harness 实战:从零搭建最小可用的 Harness 系统
问题的起源
当我用 Claude Code 做项目后,大概第三四天的时候,就逐渐出现问题了。
一次,AI 在实现一个功能时,顺手把 .env 文件里的数据库连接字符串写进了代码注释里。另一次,AI 执行了一段 Bash 命令之后,把返回的 API Key 中间结果复制成了变量名,然后又把这个变量名写进了代码文件。还有一次,AI 修改 migrations 目录下的文件,觉得那是"顺便的优化",但实际上把之前的数据迁移记录覆盖了一部分。
说到底,这三件事都指向同一个原因:AI 有完整的文件系统访问权限,却没有系统层面的边界约束。
我想过用更好的提示词来解决。但提示词是软约束——AI 可以听,也可以不听。当上下文变长、任务变复杂时,提示词里的要求经常被忽略,这不是 Claude 的问题,是提示词本身无法被强制执行。
真正有效的方式,是在系统层面把边界画死:不是告诉 AI"你应该这样做",而是让 AI"必须这样做"。在它试图越界的那一刻就拦住,比事后发现再打补丁要有效得多。这就是 Harness 的思路——不是更好的提示,是系统强制执行的约束。
核心设计思路
想清楚这些之后,我开始设计自己的 Harness。先从三个遇到的最核心问题开始:
AI 能访问哪些文件? 我的项目里,敏感文件就几种:.env(环境变量)、*.pem(密钥文件)、credentials.json(第三方凭证)。这几种必须拦截。
AI 在什么时候最容易越界? 刚启动会话时最危险——Claude 不了解项目上下文,容易在不该操作的地方动手。其次是改完代码之后,它倾向于"顺手"修复旁边看起来不顺眼的东西,但 AI 顺手修复的那部分根本不在这次任务范围内。
AI 的产出需要满足什么条件才能接受? 就三个条件:没有硬编码凭证、没有越界访问、日志输出规范。这三条必须进行自动化检查,不能靠肉眼看。
把这三个问题的答案梳理清楚,Harness 系统的骨架就有了——不能靠 AI 的自觉,而是要靠系统来强制约束。
四层架构概览
搭建一套在 Claude Code 中最小可用的 Harness 系统,只需要实现下面的四个层面:
- 约束层:两份上下文文件,定义 AI 的行为边界,告诉它什么该做什么不该做。
- 工具层:权限配置,控制 AI 能调用什么工具、不能调用什么工具。
- 中间件层:三层拦截器,分别在工具调用前、工具调用后、会话结束时触发,处理不同的安全问题。
- 编排层:规范先行的开发流程,让 AI 在动手之前先确认理解是否正确。
不是说只有上面这四层,而是说这四层是必备的——少任何一层,系统就有明显的缺口。
核心原则只有一条:不是靠 AI 自觉,是靠系统强制。
第一层:约束层——通过两份文件定义 AI 的行为边界
最底层是上下文文件。Claude Code 每次启动时必然会读取这两个文件,它们是整个 Harness 系统的基石。
CLAUDE.md:项目总规范
第一份叫 CLAUDE.md,放在项目根目录,告诉 AI 这个项目是干什么的、谁在里面扮演什么角色:
# 项目概况
这是一个提供 RESTful API 的 Node.js 后端服务。
技术栈:Express + PostgreSQL + Redis。
## 团队协作方式
- 我(人类):产品决策,代码审查,最终上线
- 你(AI):主力开发,响应需求实现
## 当前阶段
MVP 开发中,预计 2 周后首版发布。
## 工作约定
1. 方案确认后再动手,不要边想边写
2. 改动超过 20 行必须主动告知我
3. 凭证相关操作必须停下来等我确认
ARCHITECTURE.md:不可违反的铁律
第二份叫 ARCHITECTURE.md,里面写的是绝对不允许 AI 违反的规则:
# 架构铁律(不可违反)
## 凭证铁律
- 绝对不读取 `.env` 文件内容
- 绝对不将任何 Key、Token、密码写入代码或注释
- 所有外部凭证必须通过环境变量注入,代码中只引用 `process.env.XXX`
## 访问边界
- 禁止对 `src/migrations/` 目录执行 Write/Edit 操作
- 禁止修改 `.gitignore` 文件
- 禁止对 `node_modules/` 执行任何操作
## 代码质量
- 所有 API 路由必须有输入校验
- 错误必须返回结构化 JSON,不允许裸字符串
- console.log 只用于开发调试,提交前必须替换为日志库
这两份文件是刻在系统里的约束,不是给 AI 看的建议。AI 读不到是我的失职,读到了还违反,那就是它的责任。
第二层:工具层——按角色给权限,最小权限原则
Claude Code 的权限由 settings.local.json 控制。我在这里定义了一套权限组合:
{
"permissions": {
"allow": ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
"deny": ["NotebookEdit", "WebFetch"],
"automaticToolApproval": false
}
}
光配置权限不够。有些权限(如 Bash)是双刃剑,AI 可能用它执行危险命令。我在拦截器里进一步细化了对命令的拦截逻辑。
第三层:中间件层——三层拦截器,解决三个关键问题
这是 Harness 系统的核心。三层拦截器,分别在工具调用前、工具调用后,会话结束时触发。
拦截器 1:工具调用前拦截——把不该碰的拦住
AI 每次调用工具前都会触发这个拦截器。用它拦截对敏感文件和禁止目录的操作:
#!/usr/bin/env python3
# hooks/pre-tool-guard.py
import json
import re
import sys
SENSITIVE_PATTERNS = [
r'\.env$',
r'\.pem$',
r'_key$',
r'id_rsa',
r'credentials\.json$',
r'\.p12$',
]
BLOCKED_DIRS = [
'src/migrations',
'.git',
'node_modules/',
]
def check_path(path: str) -> tuple[bool, str]:
"""检查路径是否命中敏感规则。返回 (blocked, reason)"""
if not path:
return False, ""
for pattern in SENSITIVE_PATTERNS:
if re.search(pattern, path, re.IGNORECASE):
return True, f"敏感文件模式: {pattern}"
for blocked in BLOCKED_DIRS:
if blocked in path:
return True, f"禁止目录: {blocked}"
return False, ""
def main():
try:
raw = sys.stdin.read()
if not raw.strip():
sys.exit(0)
payload = json.loads(raw)
tool = payload.get("tool", "")
path = payload.get("path", "") or payload.get("command", "")
if tool in ("Read", "Write", "Edit", "Bash"):
blocked, reason = check_path(path)
if blocked:
print(f"\n[Harness Guard] BLOCKED: {tool} on {path}")
print(f"[Harness Guard] 原因: {reason}")
print("[Harness Guard] 如需解除限制,请联系项目管理员。\n")
sys.exit(1)
except Exception as e:
print(f"[Harness Guard] 检查异常: {e}", file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()
测试结果:
# 测试:Claude 试图读取 .env 文件
echo '{"tool": "Read", "path": "/project/.env"}' | python3 pre-tool-guard.py
# 输出:[Harness Guard] BLOCKED: Read on /project/.env
# 退出码:1
# 测试:Claude 试图编辑 migrations 目录
echo '{"tool": "Edit", "path": "/project/src/migrations/001_add_users.sql"}' \
| python3 pre-tool-guard.py
# 输出:[Harness Guard] BLOCKED: Edit on ...src/migrations/...
# 退出码:1
# 测试:正常文件操作
echo '{"tool": "Read", "path": "/project/src/controllers/user.js"}' \
| python3 pre-tool-guard.py
# 退出码:0
注册方式,在 settings.local.json 里配置:
{
"hooks": {
"PreToolUse": {
"Read": "python3 /path/to/hooks/pre-tool-guard.py",
"Write": "python3 /path/to/hooks/pre-tool-guard.py",
"Edit": "python3 /path/to/hooks/pre-tool-guard.py",
"Bash": "python3 /path/to/hooks/pre-tool-guard.py"
}
}
}
拦截器 2:工具调用后审查——检测输出中的敏感信息
这道拦截器不阻断操作,但会扫描 AI 的 Bash 输出,检测是否包含泄露的凭证信息:
#!/usr/bin/env python3
# hooks/post-output-guard.py
import json
import re
import sys
SECRET_PATTERNS = [
(r'(?i)(api[_-]?key|secret[_-]?key)\s*[:=]\s*["\']?[\w\-]{16,}',
"API/密钥泄露"),
(r'-----BEGIN\s+(RSA\s+)?PRIVATE\s+KEY-----', "私钥泄露"),
(r'password\s*[:=]\s*["\'][^"\']{6,}["\']', "密码硬编码"),
]
def scan_output(output: str) -> list[str]:
violations = []
for pattern, label in SECRET_PATTERNS:
if re.search(pattern, output):
violations.append(label)
return violations
def main():
try:
raw = sys.stdin.read()
if not raw.strip():
sys.exit(0)
payload = json.loads(raw)
tool_name = payload.get("tool", "")
output = payload.get("output", {})
if tool_name not in ("Bash", "Write"):
sys.exit(0)
output_text = ""
if isinstance(output, dict):
output_text = output.get("stdout", "") + str(output.get("stderr", ""))
elif isinstance(output, str):
output_text = output
violations = scan_output(output_text)
if violations:
print(f"\n[Harness Output Guard] 检测到敏感信息: {', '.join(violations)}")
print("[Harness Output Guard] 请审查输出内容,确保无误。\n")
except Exception as e:
print(f"[Harness Output Guard] 扫描异常: {e}", file=sys.stderr)
sys.exit(0)
if __name__ == "__main__":
main()
注册方式:
{
"hooks": {
"PostToolUse": {
"Bash": "python3 /path/to/hooks/post-output-guard.py"
}
}
}
这里我选择只警告、不阻断,因为误报率比预期高——Claude 的 Bash 输出里有时候恰好匹配变量名 api_key,直接阻断会影响正常流程。
拦截器 3:会话结束拦截——自动生成工作摘要
这是最容易被忽略的一项内容。我设计了一个会话结束拦截器,在 AI 完成工作时自动生成一份结构化的工作记录:
#!/bin/bash
# hooks/session-summary.sh
SESSION_DIR="$HOME/.claude/sessions/$(date +%Y-%m-%d)"
mkdir -p "$SESSION_DIR"
SESSION_FILE="$SESSION_DIR/summary-$(date +%H%M%S).md"
{
echo "# 会话摘要 - $(date '+%Y-%m-%d %H:%M:%S')"
echo ""
echo "## 项目"
echo "$(pwd)"
echo ""
echo "## 变更文件"
git diff --name-only --diff-filter=ACMR 2>/dev/null \
|| echo "(非git仓库或无变更)"
echo ""
echo "## 新增文件"
git ls-files --others --exclude-standard 2>/dev/null \
|| echo "(无新文件)"
echo ""
echo "## 拦截记录"
grep -r "BLOCKED\|Harness" "$HOME/.claude/logs/" 2>/dev/null \
| tail -10 || echo "无"
echo ""
} > "$SESSION_FILE"
echo "[Harness] 会话摘要已生成: $SESSION_FILE"
以上三层拦截器,各司其职:工具调用前拦截是不让 AI 碰到不该碰的东西;工具调用后审查是检测输出中的敏感信息泄露;会话结束时拦截是每次 AI 离开后留下可追溯的痕迹。
第四层:编排层——规范先行的开发流程
三层拦截器解决了"AI 不越界"的问题。但光不越界还不够——AI 经常在错误的理解上做正确的事。
为此我引入了一套规范先行的开发流程,让 AI 在动手之前先确认理解是否正确:
需求进来 → /追问(追问澄清)→ /固化(固化规范)→ /实现(执行)→ /验证(逐条检查)
/追问:挖掘隐含假设
在动手之前,先把需求里的隐含假设全部挖出来。
比如我说"给用户模块加上注册功能",AI 会追问:
- 密码强度规则是什么?最小几位?必须包含特殊字符吗?
- 邮件验证是指注册时发送验证链接,还是可选操作?
- 注册后直接登录,还是需要人工审核?
- 一个邮箱是否允许重复注册?
/固化:形成合约
我把答案补全,AI 整理成一份规范:
# 规范:用户注册功能 v1.0
## 功能描述
支持邮箱注册,邮件验证后才能登录。
## 详细规则
1. 密码:最小8位,必须包含数字和字母
2. 邮件验证:注册后发送验证链接,24小时内有效
3. 登录:验证通过前禁止登录,返回 401 + {"error": "email_not_verified"}
4. 重复注册:已注册邮箱返回 409 + {"error": "email_already_registered"}
## API 设计
- POST /api/auth/register → {email, password} → {message, user_id}
- POST /api/auth/verify → {token} → {success, token}
## 验收标准
- [ ] 正常注册流程完整
- [ ] 密码强度校验生效
- [ ] 重复注册被拒绝
- [ ] 所有错误返回结构化 JSON
这份规范经过我确认之后,AI 才会开始实现。实现完成后,AI 对照规范逐条自检是否满足。
规范是 AI 和人共同签署的合约。 在此之前,不动手。
CI/CD 门禁:Git 合并前的最后一道防线
通过实现上面的四层架构,已经可以实现一套适用于 Claude Code 的最小可用的 Harness 系统,但我不希望有问题的代码绕过 Harness 系统直接进入主干,为此我在 Git 层面又加了一道门禁,作为代码合并前的最后一道防线。
#!/bin/bash
# .git/hooks/pre-commit.d/harness-gate.sh
set -e
echo "[Harness Gate] 开始检查..."
# 1. 凭证扫描
if grep -rEn "(api[_-]?key|secret[_-]?key|password\s*=)" \
--include="*.js" --include="*.ts" --include="*.py" \
--include="*.md" --include="*.json" \
-e "-----BEGIN.*PRIVATE KEY-----" \
. 2>/dev/null | grep -v "node_modules" | grep -v ".git"; then
echo "[Harness Gate] 凭证扫描未通过"
echo "请使用环境变量或 .env.example 管理凭证"
exit 1
fi
# 2. migrations 目录变更拦截
CHANGED_FILES=$(git diff --cached --name-only)
if echo "$CHANGED_FILES" | grep -q "src/migrations/"; then
echo "[Harness Gate] 检测到 migrations 目录变更,需人工审查"
exit 1
fi
echo "[Harness Gate] 门禁通过"
exit 0
踩过的两个坑
坑 1:工具调用后审查的误报问题
一开始,我让这道拦截器检测所有包含 password 字样的输出。结果 AI 执行 grep -r "password" ./src 做代码审查时,输出里每个匹配行都被误判为"密码泄露",然后阻断操作。
后来我把检测规则调紧了:不再检测变量名,只检测真正的密钥格式(16 位以上的字母数字组合前面带 = 或 : 的)。误报率降下来了。
坑 2:规范流程用在小需求上太重
我发现 /追问 → /固化 → /验证 这套流程,用在小需求上反而降低效率。一个两句话能说清楚的需求,让 AI 完整走一遍规范流程,做的事情比直接实现还多。
现在的做法是:简单需求直接实现,遇到复杂需求(涉及多个模块、有状态依赖、需要权衡方案)才启用这套流程。
流程是为复杂度服务的,不是必须遵守的教条。
实际效果——用了两个月后的感受
我没有对数据进行精确的量化对比,只说感受层面的变化。
之前最让我头疼的两件事:凭证不小心泄露到代码里(Claude 有时候会把 Bash 输出的中间结果直接写进注释)、越界修改 migrations 目录的文件(Claude Code 觉得这是"顺手的事")。引入了 Harness 系统之后,这两类问题基本消失,因为 AI 在动手之前就被拦住了。
代码返工率有明显下降。之前 AI 按照自己理解实现功能,经常在 code review 时发现理解偏差,然后重写。现在多了规范确认那一步,理解偏差在动手之前就被纠正了。
对话轮次也少了。之前一个任务要来回改 3、4 轮,现在基本 1-2 轮就定。
不是因为 AI 变聪明了,是因为它在动手之前先确认理解,理解对了,实现自然就对了。
组件清单
上面这套 Claude Code Harness 系统(约束层→工具层→中间件层→编排层)加上 Git 门禁,共同构成一套最小可用的 Harness。以下是各层对应的具体组件:
| 层级 | 组件 | 作用 |
|---|---|---|
| 约束层 | CLAUDE.md | 项目总规范 |
| 约束层 | ARCHITECTURE.md | 架构铁律 |
| 工具层 | settings.local.json | 权限配置 |
| 中间件层 | pre-tool-guard.py | 事前拦截敏感文件操作 |
| 中间件层 | post-output-guard.py | 事后检测输出敏感信息 |
| 中间件层 | session-summary.sh | 会话结束生成工作摘要 |
| 编排层 | /追问 | 追问需求,挖掘隐含假设 |
| 编排层 | /固化 | 固化规范,形成合约 |
| 编排层 | /实现 | 按规范执行实现 |
| 编排层 | /验证 | 对照规范逐条验证 |
| CI/CD 层 | harness-gate.sh | Git 合并前最后门禁 |
总结
搭这套系统,我花了大约 10 个小时一次性投入。之后每个新项目,Clone 下来改一下上下文文件内容,5 分钟完成基础配置。
真正难的不是写代码,是想清楚你的边界在哪里。你允许 AI 做什么,不允许它做什么,什么情况下让它停下来等你确认——这些问题在你写代码之前就得有答案。代码只是把你做的决定翻译成系统可执行的规则。
Claude Code 的 Harness 四层架构(约束层→工具层→中间件层→编排层)加上 Git 门禁,构成了一套完整的最小可用系统。这个系统不是一次性建成的,它是和你的项目一起长大的。