ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Claude Code 扩展机制(二):从 Session 启动到一次响应,完整执行流走一遍

Claude Code 扩展机制(二):从 Session 启动到一次响应,完整执行流走一遍 1. 为什么你的 Skill 没触发、Hook 没拦住先看清执行流Claude Code 的扩展机制里Skill、Hook、MCP 经常被当成三个独立功能来学结果一到调试就懵明明写了 Skill模型不读明明配了 Hook命令照样跑MCP 工具调起来还比内置工具慢半拍。这些问题的根子不在配置写错而在于不知道 Claude Code 内部到底在什么时候读了什么、发了什么、拦了什么。Claude Code 的一次完整执行分两个阶段Session 启动和单 turn 响应。启动阶段只发生一次把所有材料装进 system prompt单 turn 循环每次发消息都跑一遍模型决策、工具调用、结果回传反复循环。所有“它什么时候被加载/触发”的问题答案都落在这两个阶段的某一步里。这篇就把这条管道从头到尾走一遍给出可复制的 settings.json 配置骨架和 Hook 注册示例再演示一次请求从发起到返回的逐步验证动作帮你定位扩展点。2. TaoToken 前置把模型接入层先跑通在拆执行流之前得先保证模型调用这一层是通的。Claude Code 本身是客户端真正干活的是背后的模型服务。我这边习惯用 TaoToken 做统一接入官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 入口是 https://taotoken.net/api 它把模型对话、Coding Plan、控制台、API Keys 这些入口都收在一处省得在多个平台之间来回切。如果你只是想把 Claude Code 的扩展机制跑通最省事的路径是先用模型对话验证模型本身能正常响应再去配 Coding Plan 做长期编码。API Keys 在控制台里生成接入文档里有完整的 base_url 和鉴权头写法。这里要强调一点TaoToken 是合规的模型接入服务不是所谓的中转配置时按文档给的地址填就行不要自己拼奇怪的 endpoint。拿到 Key 之后Claude Code 侧的配置骨架大概长这样放在项目根目录的.claude/settings.json{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的key }, hooks: { PreToolUse: [ { matcher: Bash, command: scripts/check-dangerous-commands.sh } ], PostToolUse: [ { matcher: Edit|Write, command: scripts/run-lint.sh } ] } }这段配置里env负责把模型请求指向 TaoToken 的 APIhooks负责注册拦截脚本。注意 Hook 此刻只是被注册并没有真正运行它要等单 turn 阶段的特定事件发生才会被宿主进程触发。这一点很关键后面排障会反复用到。3. 可复制配置Session 启动五步与 Hook 注册Session 启动阶段Claude Code 在你按回车之前已经悄悄做了五件事。理解这五步是后面所有调试的基础。第一步读 CLAUDE.md。它按层级从下到上逐级读取并合并项目根.claude/CLAUDE.md、父目录.claude/CLAUDE.md、用户级~/.claude/CLAUDE.md。合并后的内容全量塞进 system prompt从此驻留在每一次 turn 的上下文里直到 session 结束。这就是为什么过大的 CLAUDE.md 会“爆 context”。经验值是控制在 2KB 以内超出的部分拆成 Skill 按需加载。第二步扫描所有 Skill只加载“目录”而不是正文。Claude Code 会扫.claude/skills/、~/.claude/skills/以及已装 plugin 中的 skills 目录。每找到一个 SKILL.md只读 frontmatter 的 name 和 description拼成一份索引塞进 system prompt每个 skill 大约花 100 token。--- name: code-review description: 用于审查 Python/Java 后端代码检查命名规范、空指针、异常处理。当用户请求 review、检查代码、找 bug 时使用 --- # 下面是 SKILL.md 的正文此时不会被加载为什么这样设计如果你装了 50 个 skill每个正文 1500 字启动时全部加载会立刻吃掉 7 万 token。改成“只装目录用到再翻章节”启动开销能控制在 5K token 以内。这就是渐进披露机制。第三步加载 Subagent 定义。扫描.claude/agents/、~/.claude/agents/、plugin 里的 agents但不启动它们只把每个 agent 的描述注册进一张“可调用列表”供主 agent 通过 Task 工具派活。Subagent 是懒启动的你不调用它它就不存在。第四步解析 Hook 配置。读hooks/hooks.json或.claude/settings.json的 hooks 字段把“哪个事件触发哪个脚本”的映射建好。上面那段 settings.json 里的 hooks 就是这一步被解析的。第五步启动 MCP server 子进程。这是启动期里唯一有明显耗时的一步。读.mcp.json{ mcpServers: { jira: { command: npx, args: [-y, modelcontextprotocol/server-jira], env: { JIRA_TOKEN: $JIRA_TOKEN } } } }Claude Code 会起一个子进程跑这个 server通过 stdio 做 JSON-RPC 握手调用 tools/list 拿到这个 server 暴露的所有工具及其 schema再把这些 schema 跟内置工具合并成一份完整的工具白名单塞进 system prompt。冷启动可能 5 到 10 秒后续 turn 里 MCP server 一直在线直接通信。启动结束时system prompt 的布局是内置 System Instructions、CLAUDE.md 内容、Skill 索引表、Available Tools内置加 MCP 暴露的完整 JSON Schema、Available Subagents。记住这个布局后面所有“为什么没触发”的问题都能回到这张图找答案。4. 验证请求一次响应从发起到返回的逐步动作启动完了你输入第一句话真正的执行循环开始。把这个循环走一遍关键节点都标出来。节点 1你的输入进来。如果你输的是/skill-nameClaude Code 会先把对应 SKILL.md 的正文展开拼到你这条消息后面作为完整的用户输入发给模型。这是用户驱动的触发方式绕过模型自主判断。如果你输的是普通文本直接进入下一步。节点 2模型决策。模型基于完整 system prompt、历史对话、你的输入做出决策产出三种结果之一直接回复文本则 turn 结束请求读取一个 Skill 则通过 Read 工具读.claude/skills/xxx/SKILL.md正文进入 context 继续循环请求调用工具则进入节点 3。这里就是 Skill auto-invoke 真正发生的地方。模型看到 system prompt 里有code-review: 用于审查 Python/Java 后端代码……如果你刚说“帮我看看 auth.py 有没有问题”它会自己决定读一下正文。节点 3Hook 拦截 PreToolUse。工具调用还没真正执行宿主进程先看 hooks 配置有没有匹配这个工具的 PreToolUse hook。有的话执行对应脚本脚本的退出码决定后续退出码 0 放行非 0 拦截并把错误信息作为 tool_result 回给模型模型重新决策。#!/bin/bash # scripts/check-dangerous-commands.sh input$(cat) if echo $input | grep -q rm -rf /; then echo Blocked: dangerous command 2 exit 1 fi exit 0模型甚至不知道 hook 的存在它只看到工具调用要么成功要么报了一个错。这就是 Hook 作为硬约束的实现方式。节点 4工具执行。按工具类型分支内置工具由 Claude Code 进程直接调本地 APIMCP 工具通过 stdio 给 MCP server 发 JSON-RPC 等返回Task 派 subagent 则启动一个新的 Claude 实例把 prompt 字符串发过去等它跑完返回最终消息。节点 5Hook 后置 PostToolUse。工具执行完再走一遍 Hook有没有匹配这个工具的 PostToolUse hook有的话执行比如改完文件后跑 lint。PostToolUse 的退出码也会影响流程非 0 时 lint 错误会作为额外信息附加到 tool_result 里给模型提示它修复。节点 6结果回传给模型。工具结果可能被 hook 包裹了一层回到模型模型基于新信息再决策回到节点 2直到模型选择直接回复文本turn 结束。验证时你可以这样操作在项目里放一个故意会触发 PreToolUse 的脚本让 Claude 执行一条包含危险模式的命令观察它是否被拦截并返回错误。如果拦截成功说明 Hook 注册和退出码逻辑都对如果没拦住回到节点 3 检查 matcher 配置和脚本退出码。5. 本篇常见错排查Skill、Hook、MCP 的坑把结论汇总成调试速查表出问题直接对号入座。现象该查哪一步Skill 该触发但没触发启动期第 2 步检查 description 是否清晰具体CLAUDE.md 改了不生效启动期第 1 步检查文件位置是项目级还是用户级Hook 没拦住命令单 turn 节点 3检查 matcher 配置和脚本退出码MCP 工具调不通启动期第 5 步看 server 进程是否起来日志在~/.claude/logs/mcp-*.logSubagent 拿不到上下文单 turn 节点 4Subagent 不继承父 context看 Task 调用时传了什么 prompt改了配置看起来没生效重启 session所有启动期内容都需要重新装载几个常见误解也一并说清。误解一Skill 触发是关键词匹配。不是它是模型基于 description 的语义判断。描述写得太短模型不知道什么场景该用把场景、领域、关键词都写出来模型才有匹配依据。调试方法就是实际让 Claude 去做匹配场景的任务看它有没有读 skill没读就调描述。误解二CLAUDE.md 越大越好。反了它会全量进每一次 turn 的 system prompt1KB 乘上几十 turn 就是几十 KB 的重复输入稀释模型注意力。正确策略是只放所有 turn 都需要的其他拆成 Skill。误解三MCP 工具比内置工具高级。没这事。启动阶段第 5 步里MCP 工具的 schema 跟内置工具一起被合并进同一份工具白名单模型看到的是一份长长的可调用工具列表不区分谁是内置谁是 MCP。差别只在执行端内置工具直接调本地 APIMCP 工具走 stdio 跟外部进程通信。唯一实际差别是延迟和稳定性MCP 调用要走一次进程间通信慢几十到几百毫秒第一次调用因为可能有冷启动会更明显MCP server 是独立进程可能崩溃Claude Code 会尝试重启但调用期间会失败一次。误解四Subagent 跟主 agent 共享上下文。完全不共享。Subagent 启动时是 fresh context只能看到主 agent 通过 Task 工具传过去的那段 prompt 字符串。还有一个不对称值得记住Skill、CLAUDE.md、Command 都是给模型看的提示词模型可以遵循也可以遗忘Subagent 也是另一个模型实例本质还是模型在做事MCP 是模型可以调用的工具但调不调还是模型决定。只有 Hook 不一样它由宿主进程直接执行不经过模型模型甚至不知道有 hook 的存在。所以“必须做的事”应该写成 Hook而不是写成 CLAUDE.md 里的“请你不要忘记”。6. 把执行流用起来从排障到长期编码执行流清楚之后你会发现排障其实就是在两个阶段里定位。启动期的问题看 CLAUDE.md 位置、Skill 索引、Hook 注册、MCP 进程单 turn 的问题看模型决策、PreToolUse、工具执行、PostToolUse、结果回传。每次改完配置记得重启 session因为启动期的内容都需要重新装载。如果你要长期用 Claude Code 做编码和 Agent 任务建议把模型接入层固定下来用 TaoToken 的 Coding Plan 做长期编码API Keys 在控制台生成接入文档里有完整的 base_url 和鉴权写法。验证模型本身是否正常可以直接用模型对话入口发一条消息看响应。排障和接入相关的细节去 API Keys 页面和接入文档里对照着看比在配置里瞎猜快得多。下一篇会专门讲 Skill 的三层结构frontmatter、SKILL.md 正文、附属文件scripts、references、assets。读完之后你会知道为什么官方的 PDF skill、Excel skill 那么有用它们不是换皮的提示词而是把领域知识加工具脚本绑成一个语义单元。
返回列表