一句话

用 Claude Agent SDK 自建 agent 时,即使把系统提示词整个替换成自己的字符串,宿主的 CLAUDE.md、~/.claude/settings.json 和自己配置的 MCP server 仍然会被注入。除非显式设置 settingSources: []。

不报错、不警告,只是 agent "有点怪" —— 属于最难查的一类问题。

发生条件

  • 用 @anthropic-ai/claude-agent-sdk(query() API)
  • 通过 systemPrompt: "<纯字符串>" 替换了默认提示词
  • 没有设置 settingSources
  • 运行环境的用户/工程目录下存在着 Claude Code 的配置文件(几乎所有用过 Claude Code 的机器都满足)

症状

搭建 mini-agent 时实测(同一句提问,只差一个 settingSources 字段):

观察点未设置(默认)设为 []
system/init 消息里的 tools.length3026
mcp_servers 列表出现 bing-search: failed、google-search: connected(用户自己的 MCP 配置)只剩自己注册的 local: connected
模型 thinking 块的语言与内容复述宿主全局 ~/.claude/CLAUDE.md 的规则(英文、关于"回答后必须带工具调用")干净,只遵循自定义提示词
最终回答风格偏离自定义提示词符合自定义提示词("用中文回答,简短")

关键点:第三行是判定依据。工具数变化可能是系统版本差异,但模型念出宿主 CLAUDE.md 的内容,只可能是渗漏。

先说清楚:三条独立的通道

渗漏之所以难查,是因为"给模型的指令"其实走三条互不相干的通道,改了一条不代表改了别的。

通道是什么怎么控制
系统提示词Claude Code 内置那一大段(身份、工具用法、安全规则、环境信息)Options.systemPrompt
CLAUDE.md文件系统里的记忆文件(~/.claude/CLAUDE.md、工程 CLAUDE.md)Options.settingSources 里含 'project' 才加载
settings.json~/.claude/settings.json、.claude/settings.json 等同上

关键:systemPrompt 传纯字符串只换第一条,不会关掉另外两条。

所以会出现"提示词明明整个换掉了,模型还在念宿主 CLAUDE.md 的规矩"这种怪事 ——
两条通道各管各的,你只拨了一个开关。

完整的三层可配项(以及 plugins 这条绕过 settingSources 的路)另见
Claude Code 插件与 Skill 加载机制 (Claude Code Plugin and Skill Loading)。

关于"能不能修改内置提示词"

分四种情况,别混:

想干什么能不能怎么做
整个换掉✅systemPrompt: '<纯字符串>'
在它基础上追加✅systemPrompt: { type: 'preset', preset: 'claude_code', append: '...' }
拿到它的原文⚠️官方闭源,在打包的二进制里;要看得 MITM 抓(见 Claude Code 系统提示词抓取 (Capturing Claude Code System Prompt via MITM) )
改它本身❌那是产品的一部分,只能用或不用

"换掉"不等于"修改"。 传字符串是把内置的整个丢掉,换成自己的 —— 对模型来说内置那套压根不存在。

但换说明书 ≠ 换手脚:

系统提示词  = 岗位说明书   ← 可以整个换
工具集      = 手上的家伙   ← 换不掉(除非改 allowedTools / disallowedTools)
hooks/权限  = 门禁         ← 换不掉(除非自己配 hooks)

推论:换了提示词之后,必须自己在提示词里交代"你有哪些工具、该怎么用" ——
自带的那部分说明跟着原提示词一起消失了。这是换提示词最容易漏的一条。

根因

Claude Agent SDK 本质是启动一个 Claude Code CLI 子进程。这个 CLI 沿用命令行默认行为加载"文件系统配置",来源有三层:

  • user → ~/.claude/settings.json
  • project → 工程 .claude/settings.json
  • local → 工程 .claude/settings.local.json

且 CLAUDE.md 的记忆加载挂在 project 源下。

SDK 文档原文:

When omitted, all sources are loaded (matches CLI defaults).
Pass [] to disable filesystem settings (SDK isolation mode).
Must include 'project' to load CLAUDE.md files.

所以"替换 system prompt"只换了提示词本身,没有换掉配置加载。两条路径独立,各管各的。

这与直觉冲突的地方在于:systemPrompt 传纯字符串的语义确实是"整个替换,不追加"(已由 SDK 类型定义确认:只有 {type:'preset', preset:'claude_code'} 才包含默认提示词)。提示词替换本身是对的,渗漏来自另一个开关。

最终解决

query({
  prompt,
  options: {
    systemPrompt: myPromptString,   // 换提示词(这个本来就是对的)
    settingSources: [],             // 关键:关掉配置加载
    // ...其它 options
  }
})

需要读工程自己的 CLAUDE.md 时再改成 ['project']。

配套的两项加固(同源问题,建议一起做):

  1. 剥掉宿主会话变量再传给子进程:CLAUDECODE、CLAUDE_CODE_SESSION_ID、CLAUDE_CODE_ENTRYPOINT、CLAUDE_CODE_CHILD_SESSION、CLAUDE_CODE_EXECPATH、CLAUDE_PID。不剥的话,被启动的 CLI 会误判自己处在嵌套会话中。
  2. 显式覆盖 ANTHROPIC_BASE_URL:从 Claude Code 进程内运行时该变量会被继承成宿主的中转地址,不是使用者自己配置的端点。

验证方法

npm start -- --show-config   # 看继承到的 ANTHROPIC_* 环境
npm start -- "随便问一句"     # 看启动日志里的 tools.length 和 mcp_servers

对照表见上文。只要 mcp_servers 里出现了自己没注册的名字,就是渗漏。

下次如何识别

  • 自建 agent "不听话"、"答非所问"、风格忽变,但提示词明明是对的
  • 启动日志里 tools 数量比预期多
  • 模型复述了任何你没有写进自定义提示词里的规则
  • 从 Claude Code 内部(或任何装有 Claude Code 的机器)启动自建 agent

以上任一条命中,先查 settingSources,再查环境变量继承。

相关

出现场景

C:\Users\18086\.workspace\git-workspace\mini-agent —— 一个"自加工具 + 自换提示词"的小 agent 壳子,2026-09-11 搭建。该问题在首次实跑时暴露。


本文归档于个人知识库 · 领域:CS & AI · 模块:04 Project_CS_AI/AI apps