Claude Code 实战(二):系统加固 — 权限、Hook、记忆、提示词与错误恢复
AI 教程教程进阶15 分钟阅读
学习路径:Claude Code 实战

Claude Code 实战(二):系统加固 — 权限、Hook、记忆、提示词与错误恢复

AI Agent系统加固实战!详解权限系统四步管道、Hook扩展机制、跨会话记忆管理、系统提示词流水线、错误恢复三条路径。

Claude Code 实战(二):系统加固 — 权限、Hook、记忆、提示词与错误恢复

Agent 能跑起来只是第一步。真正投入生产,必须先解决安全和稳定问题。本文覆盖系统加固阶段的 5 个关键机制。

前言

第一篇中,我们搭建了 AI Agent 的核心闭环:Agent 循环、工具使用、待办写入、子代理、技能系统、上下文压缩。

核心闭环跑起来以后,新的问题出现了:

  • 模型可能会写错文件、执行危险命令
  • 每次加需求都改主循环,代码越来越臃肿
  • 每次新会话都从零开始,像"第一次合作"
  • 所有东西塞进一个 system prompt,越来越乱
  • 一个错误就让整个循环崩掉

本文解决这 5 个问题,对应 5 个章节:

  1. 权限系统(s07)— 在工具执行前加一道安全闸门
  2. Hook 系统(s08)— 在固定时机扩展能力,不改主循环
  3. 记忆系统(s09)— 跨会话保存有价值的信息
  4. 系统提示词(s10)— 把 prompt 从硬编码升级成组装流水线
  5. 错误恢复(s11)— 从"报错就崩"升级到"先判断再恢复"

第 7 章:权限系统

"意图不能直接变成执行,中间必须经过权限检查"

问题

到了核心闭环阶段,你的 agent 已经能读文件、改文件、跑命令、做规划。问题也随之出现:

  • 模型可能会写错文件
  • 模型可能会执行 sudo rm -rf 这样的危险命令
  • 模型可能会在不该动手的时候动手

四步权限管道

tool_call
  |
  v
1. deny rules     -> 命中了就拒绝
  |
  v
2. mode check     -> 根据当前模式决定
  |
  v
3. allow rules    -> 命中了就放行
  |
  v
4. ask user       -> 剩下的交给用户确认

为什么 deny 先于 allow? 因为有些东西不应该交给"模式"去决定(明显危险的命令、明显越界的路径),这些应该优先挡掉。

三种权限模式

模式 含义 适合场景
default 未命中规则时问用户 日常交互
plan 只允许读,不允许写 计划、审查、分析
auto 简单安全操作自动过,危险操作再问 高流畅度探索

最小实现

def check_permission(tool_name: str, tool_input: dict) -> dict:
    # 1. deny rules
    for rule in deny_rules:
        if matches(rule, tool_name, tool_input):
            return {"behavior": "deny", "reason": "matched deny rule"}

    # 2. mode
    if mode == "plan" and tool_name in WRITE_TOOLS:
        return {"behavior": "deny", "reason": "plan mode blocks writes"}
    if mode == "auto" and tool_name in READ_ONLY_TOOLS:
        return {"behavior": "allow", "reason": "auto mode allows reads"}

    # 3. allow rules
    for rule in allow_rules:
        if matches(rule, tool_name, tool_input):
            return {"behavior": "allow", "reason": "matched allow rule"}

    # 4. fallback
    return {"behavior": "ask", "reason": "needs confirmation"}

Bash 需要特殊对待

所有工具里,bash 最危险。read_file 只能读文件,write_file 只能写文件,但 bash 几乎能做任何事。所以不能只把 bash 当成普通字符串。

建议至少挡住:sudorm -rf、命令替换、可疑重定向。

核心思想:bash 不是普通文本,而是可执行动作描述。


第 8 章:Hook 系统

"主循环只负责暴露时机,真正的附加行为交给 hook"

问题

很多需求不属于"允许/拒绝"这条线,而是:

  • 在某个固定时机顺手做一点事(比如记录日志)
  • 不改主循环主体,也能接入额外规则
  • 让用户或插件在系统边缘扩展能力

如果每增加一个需求就去改主循环,主循环就会越来越重,最后谁都不敢动。

Hook 的本质

Hook 理解成一个"预留插口":

  1. 主系统运行到某个固定时机
  2. 把当前上下文交给 hook
  3. hook 返回结果
  4. 主系统再决定下一步怎么继续

三个核心事件

事件 时机
SessionStart 会话开始时
PreToolUse 工具执行前
PostToolUse 工具执行后

统一返回约定

退出码 含义 作用
0 正常继续 观察
1 阻止当前动作 拦截
2 注入补充消息,再继续 补充

最小实现

# 事件到处理器的映射
HOOKS = {
    "SessionStart": [on_session_start],
    "PreToolUse": [pre_tool_guard],
    "PostToolUse": [post_tool_log],
}

def run_hooks(event_name: str, payload: dict) -> dict:
    for handler in HOOKS.get(event_name, []):
        result = handler(payload)
        if result["exit_code"] in (1, 2):
            return result
    return {"exit_code": 0, "message": ""}

# 接进主循环
pre = run_hooks("PreToolUse", {"tool_name": block.name, "input": block.input})
if pre["exit_code"] == 1:
    results.append(blocked_tool_result(pre["message"]))
    continue
if pre["exit_code"] == 2:
    messages.append({"role": "user", "content": pre["message"]})

第 9 章:记忆系统

"只有跨会话、无法从当前工作重新推导的知识,才值得进入 memory"

问题

如果一个 agent 每次新会话都完全从零开始,它会不断重复忘记:

  • 用户长期偏好
  • 用户多次纠正过的错误
  • 某些不容易从代码直接看出来的项目约定
  • 某些外部资源在哪里找

这会让系统显得"每次都像第一次合作"。

先立一个边界

memory 不是什么都存。 如果你把一切有用信息都记下来,很快就会出现两个问题:

  1. memory 变成垃圾堆,越存越乱
  2. agent 开始依赖过时记忆,而不是读取当前真实状态

原则:只有那些跨会话仍然有价值,而且不能轻易从当前仓库状态直接推出来的信息,才适合进入 memory。

4 类适合存储的 memory

类型 说明 示例
user 用户偏好 代码风格、回答简洁/详细
feedback 用户纠正过的地方 "不要这样改"、"以后先做 X"
project 不容易从代码看出来的项目约定 合规要求、不能动的目录
reference 外部资源指针 问题单看板、监控面板 URL

不要存的东西

不要存 为什么
文件结构、函数签名 可以重新读代码得到
当前任务进度 属于 task/plan,不属于 memory
临时分支名、PR 号 很快会过时
修 bug 的具体代码细节 代码和提交记录才是准确信息
密钥、密码、凭证 安全风险

数据结构

每条 memory 一个文件,用 frontmatter 标注元数据:

---
name: prefer_tabs
description: User prefers tabs for indentation
type: user
---
The user explicitly prefers tabs over spaces when editing source files.

索引文件 MEMORY.md 帮系统快速知道"有哪些 memory 可用"。

# Memory Index
- prefer_tabs: User prefers tabs for indentation [user]
- avoid_mock_heavy_tests: User dislikes mock-heavy tests [feedback]

最小实现

MEMORY_TYPES = ("user", "feedback", "project", "reference")

def save_memory(name, description, mem_type, content):
    path = memory_dir / f"{safe_name}.md"
    path.write_text(frontmatter + content)
    rebuild_index()

def load_memories() -> str:
    """会话开始时重新加载,拼成 memory section"""
    index = (memory_dir / "MEMORY.md").read_text()
    return f"[Memory Index]\n{index}"

初学者最容易犯的错

  1. 把代码结构存进 memory — "这个项目有 src/ 和 tests/",系统完全可以重新读
  2. 把当前任务状态存进 memory — "我正在改认证模块",这是 task/plan
  3. 把 memory 当成绝对真相 — memory 可能过时,优先相信当前观察到的真实状态

第 10 章:系统提示词

"模型看到的不是一坨固定 prompt,而是一条按阶段拼装的输入流水线"

问题

很多初学者一开始会把 system prompt 写成一大段固定文本。一旦系统开始长功能:

  • 工具列表会变
  • skills 会变
  • memory 会变
  • 当前目录、日期、模式会变

最小心智模型

把 system prompt 想成 6 段:

1. 核心身份和行为说明
2. 工具列表
3. skills 元信息
4. memory 内容
5. CLAUDE.md 指令链
6. 动态环境信息

拼接:core + tools + skills + memory + claude_md + dynamic_context = final system prompt

最小实现

class SystemPromptBuilder:
    def build(self) -> str:
        parts = []
        parts.append(self._build_core())      # 核心身份
        parts.append(self._build_tools())     # 工具说明
        parts.append(self._build_skills())    # 技能元信息
        parts.append(self._build_memory())    # 记忆内容
        parts.append(self._build_claude_md()) # 指令文件链
        parts.append(self._build_dynamic())   # 动态环境
        return "\n\n".join(p for p in parts if p)

每一段只负责一种来源,职责清晰。

关键边界

稳定说明 vs 动态提醒:

  • system prompt:身份、规则、工具、长期约束 — 保持相对稳定
  • system reminder:每轮临时需要的补充上下文 — 独立追加

CLAUDE.md 分层叠加:

  • 用户全局级
  • 项目根目录级
  • 当前子目录级
  • 全部拼进去,而不是互相覆盖

第 11 章:错误恢复

"错误不是例外,而是主循环必须预留出来的一条正常分支"

问题

到了 s10,系统已经有了主循环、工具、规划、压缩、权限、hook、memory、prompt。一旦真的在做事,错误必然出现:

  • 模型输出写到一半被截断
  • 上下文太长,请求直接失败
  • 网络暂时抖动,API 超时或限流

没有恢复机制,主循环会在第一个错误上直接停住。

3 类问题,3 条恢复路径

LLM call
  +-- stop_reason == "max_tokens"
  |   -> 注入续写提示,再试一次
  +-- prompt too long
  |   -> 压缩旧上下文,再试一次
  +-- timeout / rate limit / transient error
      -> 等一会儿,再试一次

关键数据结构

# 恢复状态 — 防止无限重试
recovery_state = {
    "continuation_attempts": 0,  # 续写次数
    "compact_attempts": 0,       # 压缩次数
    "transport_attempts": 0,     # 网络重试次数
}

# 续写提示(非常重要!告诉模型不要重来)
CONTINUE_MESSAGE = (
    "Output limit hit. Continue directly from where you stopped. "
    "Do not restart or repeat."
)

最小实现

def choose_recovery(stop_reason, error_text):
    if stop_reason == "max_tokens":
        return {"kind": "continue", "reason": "output truncated"}
    if error_text and "prompt" in error_text and "long" in error_text:
        return {"kind": "compact", "reason": "context too large"}
    if error_text and any(w in error_text for w in ["timeout", "rate", "connection"]):
        return {"kind": "backoff", "reason": "transient failure"}
    return {"kind": "fail", "reason": "unknown error"}

while True:
    try:
        response = client.messages.create(...)
        decision = choose_recovery(response.stop_reason, None)
    except Exception as e:
        decision = choose_recovery(None, str(e).lower())

    if decision["kind"] == "continue":
        messages.append({"role": "user", "content": CONTINUE_MESSAGE})
        continue
    if decision["kind"] == "compact":
        messages = auto_compact(messages)
        continue
    if decision["kind"] == "backoff":
        time.sleep(backoff_delay(...))
        continue
    if decision["kind"] == "fail":
        break

总结

系统加固阶段让 Agent 从"能跑"变成"可靠":

章节 机制 核心价值
s07 权限系统 安全闸门,防止危险操作
s08 Hook 系统 可扩展,不改主循环
s09 记忆系统 跨会话持续学习
s10 系统提示词 可维护的 prompt 流水线
s11 错误恢复 遇到错误不崩,先判断再恢复

下一篇我们将进入"任务运行时"阶段,学习任务系统、后台任务和定时调度。


常见问题

Q: 权限系统和 sudo 密码有什么区别?

sudo 密码是操作系统层面的控制。权限系统是 Agent 层面的控制——即使模型想执行危险命令,在 API 调用到达 shell 之前就会被拦截。两层防护互不冲突。

Q: Hook 和中间件是一回事吗?

概念类似,但 Hook 更轻量。中间件通常需要修改请求管道,而 Hook 只需注册一个回调函数。Hook 的退出码约定(0=继续, 1=阻止, 2=补充)非常简洁。

Q: 记忆应该存多少条?

少而精。5-10 条高质量的偏好和纠正,远胜过 50 条垃圾信息。原则:如果系统可以重新从代码推导出来,就不要存。

Q: 错误恢复会不会导致无限重试?

不会,因为有计数器限制。recovery_state 跟踪每种恢复类型的尝试次数,超过阈值就停止。防止"续写3次还是截断"这种死循环。

Q: 系统提示词太长会影响模型性能吗?

会的。提示词越长,模型处理越慢,费用越高。所以要把稳定部分和动态部分分开——稳定部分可以被缓存(如 Anthropic 的 prompt caching),动态部分按需追加。详见 Claude Code 配置教程 了解更多优化技巧。


本文基于 Learn Claude Code 开源项目(GitHub: shareAI-lab/learn-claude-code)整理改编。

相关阅读:

相关阅读: