会话启动生命周期:首轮推理前的环境装配与指令注入
在 Claude Code 的运行时模型中,会话的启动并非简单的进程开启,而是一个多阶段的环境装配过程。SessionStart 钩子(Hook)是这一过程中的核心扩展点,它定义了模型在接收首轮用户输入前,本地环境如何完成上下文的预热与指令的注入。
一句话讲清楚
SessionStart 本质上是 首轮推理前的环境拦截协议。
它不是一个简单的“启动脚本”,而是一个运行时门控。当会话因初次启动、恢复(Resume)、清空(Clear)或上下文压缩(Compact)而重新初始化时,系统会触发此钩子。它的核心使命是:在模型看到用户的第一句话之前,允许本地逻辑向会话中注入额外的上下文、指令、甚至模拟用户的初始提问。它确保了 Claude 不是带着“白板”大脑进入对话,而是已经预装了项目规则和环境快照。
实现机制
整个启动生命周期的装配逻辑主要由 claude-code-opensource/src/utils/sessionStart.ts 中的 processSessionStartHooks 函数驱动。其执行流程遵循严格的先后顺序:
模式裁决(The Bare Gate): 系统首先检查是否处于
--bare(极简)模式。如果是,为了追求极致的启动响应,系统会物理切断所有钩子的加载与执行。这意味着在 Bare 模式下,没有任何预置逻辑能干扰模型的纯净状态。插件钩子加载(Plugin Loading): 在非受限模式下,系统会异步加载
~/.claude/plugins/目录下的第三方钩子。源码在这里做了健壮性处理:如果插件加载因语法错误或权限问题失败,系统仅会记录诊断日志并降级运行,绝不阻塞主会话的开启。核心执行流(Execution & Injection): 通过
executeSessionStartHooks并行触发所有已注册的钩子。在这个阶段,钩子可以对未来的会话产生四种维度的影响:- 消息注入:直接向消息序列中插入
HookResultMessage。 - 上下文补丁:通过
additionalContexts提供非对话形式的背景(如环境探测结果)。 - 首轮提问模拟:设置
pendingInitialUserMessage。这是一个非常巧妙的 side channel 设计,允许钩子在 REPL 循环正式开始前,代表用户发出“第一击”。 - 文件监听:返回
watchPaths,告知运行时需要重点监控哪些文件。
- 消息注入:直接向消息序列中插入
状态回填与副作用生效: 钩子运行的结果会被转化为
hook_additional_context类型的附件,并同步给fileChangedWatcher。随后,控制权才会正式交给main.tsx中的交互主循环。
使用时的关键约束
- 性能硬约束:源码中有一行非常严肃的注释警告:
do not add ANY "warmup" logic。这意味着SessionStart严禁添加任何高耗时的操作(如深度的网络请求或大规模全盘扫描),因为它直接阻断了用户看到输入框的首屏时间。 - 非交互性:在
SessionStart钩子运行期间,终端处于锁定状态,钩子无法通过终端与用户进行任何实时交互。 - 多场景触发:这一钩子不只在
claude命令敲下时。当你执行/compact进行上下文重组,或者/clear重置会话时,SessionStart都会被重新触发,以确保环境补丁的连续性。 - 安全沙箱:在受管钩子(Managed Hooks)模式下,所有来自第三方插件目录的
SessionStart逻辑都会被切断,仅允许内建逻辑生效。
相关主题
- 如果你关心会话结束时的收口逻辑,请看 会话结束生命周期:Stop 钩子与状态结算。
- 如果你想了解
--bare模式如何从物理上隔离这些流程,请看 Bare 模式:极致的执行隔离。 - 如果你想了解这些钩子是如何与官方文档中的 Hook 语法对应的,请看 Hook 系统架构解析。
源码锚点
claude-code-opensource/src/utils/sessionStart.ts—processSessionStartHooks主逻辑与装配顺序。
📄 src/utils/sessionStart.ts — `processSessionStartHooks` 主逻辑与装配顺序。
export async function processSessionStartHooks(
source: 'startup' | 'resume' | 'clear' | 'compact',
{
sessionId,
agentType,
model,
forceSyncExecution,
}: SessionStartHooksOptions = {},claude-code-opensource/src/utils/hooks.ts— 钩子执行引擎的底层并发实现。
📄 src/utils/hooks.ts — 钩子执行引擎的底层并发实现。
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
} from 'src/services/analytics/index.js'
import { logOTelEvent } from './telemetry/events.js'claude-code-opensource/src/main.tsx— 会话启动时触发钩子的顶层调用点。
📄 src/main.tsx — 会话启动时触发钩子的顶层调用点。
const getTeammateUtils = () => require('./utils/teammate.js') as typeof import('./utils/teammate.js');
const getTeammatePromptAddendum = () => require('./utils/swarm/teammatePromptAddendum.js') as typeof import('./utils/swarm/teammatePromptAddendum.js');
const getTeammateModeSnapshot = () => require('./utils/swarm/backends/teammateModeSnapshot.js') as typeof import('./utils/swarm/backends/teammateModeSnapshot.js');
/* eslint-enable @typescript-eslint/no-require-imports */
// Dead code elimination: conditional import for COORDINATOR_MODE
/* eslint-disable @typescript-eslint/no-require-imports */
const coordinatorModeModule = feature('COORDINATOR_MODE') ? require('./coordinator/coordinatorMode.js') as typeof import('./coordinator/coordinatorMode.js') : null;
/* eslint-enable @typescript-eslint/no-require-imports */
// Dead code elimination: conditional import for KAIROS (assistant mode)
/* eslint-disable @typescript-eslint/no-require-imports */
const assistantModule = feature('KAIROS') ? require('./assistant/index.js') as typeof import('./assistant/index.js') : null;
const kairosGate = feature('KAIROS') ? require('./assistant/gate.js') as typeof import('./assistant/gate.js') : null;
import { relative, resolve } from 'path';