在测试运行器上安装捕获 hook
读回通过 Claude Code Stop hook工作:当 Claude 完成一个回合时,hook 在其 stdin JSON 中接收最终助手消息作为last_assistant_message,并将其附加到 $E2E_REPLY_DIR/<session_id>.txt。以与commit-nudge Stop hook相同的方式安装它,在运行器主机的 ~/.claude/ 上,运行器将其种子化到每个会话中。
保存 hook 文件
在运行器主机上保存以下两个文件:- 设置块:合并到运行器主机上的
~/.claude/settings.json - 脚本:保存为运行器主机上的
~/.claude/hooks/e2e-stop-hook-capture.sh并使其可执行
启动运行器之前
hook 有以下要求:- 在启动运行器之前安装它。运行器在启动时对
~/.claude/进行快照,因此添加到运行中的运行器的 hook 仅在重新启动后才生效。 - 将
E2E_REPLY_DIR导出到运行器进程。当变量未设置或目录不存在时,hook 是无操作的,因此在启动运行器的任何地方设置它,例如 systemd 单元、pod 规范或 CI 步骤。下面的测试脚本也需要它。
E2E_REPLY_DIR 存在时,它会将每个会话的最终回复写入磁盘,这在一次性 CI 运行器上是无害的,但不应该进入生产环境运行器镜像,其中变量可能会被意外设置。
运行测试循环
--environment 和 --ref 分派标志需要在运行脚本的机器上使用 Claude Code v2.1.224 或更高版本,这与运行器本身的下限相同。安装 hook 并在此主机上启动运行器后,测试脚本:
- 使用
claude -p "<prompt>" --environment <environment-id> --output-format json在测试环境上创建会话。请从 git 检出中运行该命令,以便 CLI 可以从origin远程自动检测仓库。可选的--ref <branch>将会话的检出基于命名的 ref 而不是本地 HEAD。该命令退出时不会等待 Claude 的回复。其输出会告知脚本结果:- 会话已创建:一行 JSON,例如
{"ok":true,"session_id":"session_...","title":"...","url":"...","pool_id":"..."} - 会话创建失败:输出
{"ok":false,"error":"..."}这一行,并且命令以状态 1 退出 - 某些较早发生的错误,例如您的组织无法使用云端会话或缺少提示词:错误输出到 stderr,不输出 JSON 行,并且命令以状态 1 退出
- 会话已创建:一行 JSON,例如
- 等待回复出现在
$E2E_REPLY_DIR/<session_id>.txt中,由运行器上的 Stop hook 在回合完成后写入。 - 使用
claude -p "<message>" --cloud <session_id> --output-format json发送后续消息(请参阅向运行中的会话发送后续消息),它将用户事件发布到现有会话并退出。 - 以与步骤 2 相同的方式等待后续回复。
--environment 分派行为
Claude Code 创建会话,打印会话 ID 和指向它的链接,然后退出。
该标志优先于 remote.defaultEnvironmentId 设置。它不支持 --output-format stream-json,不能与恢复、附加到或预配置会话的标志组合,例如 --resume、--continue、--teleport、--session-id 或 --init-only。--cloud 在使用会话 ID 或 URL 时被拒绝,在非交互式运行中当它带有描述时也被拒绝。裸 --cloud 被视为不存在。从终端,您可以将任务作为 --cloud 描述而不是位置提示传递。
示例脚本
示例脚本与测试运行器在同一台机器上运行。运行之前,请先准备好该机器:- 仓库检出:从您希望会话在其中工作的仓库的 git 检出运行该脚本。
- 运行器:在此主机上启动运行器,安装捕获 hook 并导出
E2E_REPLY_DIR。 - 登录:按照从 CI 进行身份验证中的说明,在运行该脚本的机器上使用 claude.ai 账户登录。
- 环境 ID:将
CLAUDE_TEST_ENVIRONMENT_ID设置为您的测试环境的ccpool_...ID,该 ID 显示在管理页面上的环境详细信息对话框中,或由创建环境调用返回。
$CLAUDE_TEST_ENVIRONMENT_ID 运行完整循环,并对每个回复中的哨兵短语进行断言。
TURN1/TURN2 提示和 EXPECT1/EXPECT2 哨兵替换为任何练习您的设置的内容,例如要求 Claude 运行您的一个自定义 MCP 工具并对其输出进行断言。
远程测试运行器
如果您的测试运行器在单独的基础设施上,例如您的 CI 作业无法共享文件系统的持久 Kubernetes 集群,请将 Stop hook 中的文件写入交换为 POST 到您的驱动程序侦听的端点:从 CI 进行身份验证
claude -p ... --environment 和 claude -p ... --cloud 都使用 claude.ai OAuth 令牌进行身份验证;API 密钥(例如 sk-ant-xxxxx)对于任何一个调用都不被接受。两种方法使令牌在 CI 中可用。
长期 CI 主机
在执行脚本的机器上使用专用自动化用户帐户交互式运行一次claude auth login。Claude Code 在 macOS 上将令牌存储在 OS 密钥链中,或在 Linux 和 Windows 上存储在 ~/.claude/.credentials.json 中。在 macOS 主机上,其密钥链无法写入(如 SSH 会话中的典型情况,其中登录密钥链保持锁定),Claude Code 也将令牌存储在 ~/.claude/.credentials.json 中。请参阅凭证管理。
CLI 在每次调用时自动刷新短期访问令牌,但基础刷新令牌授予从初始登录起限制为 30 天,因此每 30 天在该主机上交互式重新运行一次 claude auth login。
临时 CI 运行器
目前没有针对此的长期 CI 令牌。授予云会话控制的范围user:sessions:claude_code 在服务器端限制为 30 天,因此 claude setup-token(它铸造一年推理令牌)不涵盖它。环境秘密也不被接受,因为它仅授权运行器向环境注册,而不是创建会话。
要在临时运行器上配置存储的登录,请设置 CLAUDE_CODE_OAUTH_REFRESH_TOKEN 和 CLAUDE_CODE_OAUTH_SCOPES,以便 claude auth login 交换令牌而不需要浏览器;相同的 30 天上限适用于刷新授予。如果您需要不受人类帐户约束的机器身份路径,请联系您的 Anthropic 帐户团队。
创建专用测试环境
以编程方式创建和删除环境,以便每个 CI 运行都获得一个干净的环境;您的 CI 作业启动的运行器注册到新环境中。下面的创建和删除调用是 claude.ai 上的云环境管理页面使用的相同端点,它们需要anthropic-beta: ccr-byoc-2025-07-29 标头。
铸造管理令牌
$ADMIN_TOKEN 是持有 Owner 角色的帐户的 claude.ai OAuth 访问令牌,以与从 CI 进行身份验证相同的方式铸造:
- 铸造它:使用持有 Owner 角色的帐户运行
claude auth login,然后从长期 CI 主机说 Claude Code 存储它的任何地方读取当前访问令牌。 - 每次运行时读取新鲜的:CLI 轮换访问令牌,相同的 30 天刷新授予上限适用,因此不要存储副本。
- 通过 stdin 传递它:如示例所示,以便令牌永远不会进入 curl 的参数列表或您的构建日志。
创建环境
捕获响应而不回显它:pool_secret 是一个长期凭证,可以将运行器注册到环境中,因此将其存储为掩蔽 CI 秘密并仅打印环境 ID。保持令牌不在进程列表中的 -H @- 形式需要 curl 7.55 或更高版本;较旧的 curl 将 @- 视为文字标头并在没有授权的情况下发送请求。
403 permission_error,读取 self-hosted runners are disabled by your organization's policy。
在此主机上启动运行器,使用 SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET=$ENVIRONMENT_SECRET,加上捕获 hook 和 E2E_REPLY_DIR,根据在测试运行器上安装捕获 hook,然后运行测试脚本。