跳转到内容
本站为社区维护的非官方中文翻译,与 Cloudflare, Inc. 无关联。本站不提供账户登录,请勿在任何非官方站点输入您的 Cloudflare 账户信息。官方文档:developers.cloudflare.com

在 sandbox 中运行 Claude Code

最后更新 查看 MarkdownAgent 设置

在 Linux sandbox 中运行 Claude Code ↗,根据提示编辑 GitHub 仓库,然后将其更改作为 diff 读取。Claude Code 通过 AI Gateway 调用 Anthropic 模型。你的 Worker 会为这些请求添加 gateway token,且 sandbox 只能访问你的 gateway 和 github.com。

前提条件

  • 来自 构建编程 Agent runner 的 sandbox-coding-agent 项目。该教程使用 Claude Code 构建 runner。在运行其他 Agent 之后,可使用本页切回 Claude Code,或更改其设置。
  • AI Gateway 中的 Anthropic 凭据,可以是 Unified Billing 额度,或作为 provider key 存储的 Anthropic API key。

将 Worker 切换到 Claude Code

  1. 替换 Dockerfile:

    Dockerfiledockerfile
    FROM node:24-trixie-slim
    
    RUN apt-get update \
        && apt-get install -y --no-install-recommends bash ca-certificates git ripgrep \
        && rm -rf /var/lib/apt/lists/*
    
    RUN npm install --global @anthropic-ai/[email protected]
    
    COPY --from=docker.io/cloudflare/sandbox:1.0.1 /usr/local/bin/sandbox-shim /usr/local/bin/sandbox-shim
    
    WORKDIR /workspace
    CMD ["sleep", "infinity"]

    Claude Code 软件包的安装脚本会放入其原生二进制文件。镜像会固定版本,因为 Claude Code 的标志和事件格式可能在版本之间变化。

  2. 在 wrangler.jsonc 中,将 MODEL 设为你的 gateway 可以提供的 Anthropic 模型 ID:

    {
        "vars": {
            "AI_GATEWAY_ACCOUNT_ID": "<ACCOUNT_ID>",
            "AI_GATEWAY_ID": "default",
            "MODEL": "claude-sonnet-5",
        },
    }
    [vars]
    AI_GATEWAY_ACCOUNT_ID = "<ACCOUNT_ID>"
    AI_GATEWAY_ID = "default"
    MODEL = "claude-sonnet-5"
  3. 在 src/outbound.ts 中,确保 Outbound entrypoint 在添加 gateway token 之前删除 x-api-key header:

    src/outbound.tsts
    const headers = new Headers(request.headers);
    headers.delete("x-api-key");
    headers.set("cf-aig-authorization", `Bearer ${this.env.AI_GATEWAY_TOKEN}`);

    Claude Code 会发送占位 API key。如果 x-api-key header 保留,AI Gateway 会将其转发给 Anthropic,模型请求会因 Invalid API key 失败。

  4. 在 src/sandbox.ts 中,将 ResultEvent 常量替换为 Claude Code 最终事件的 schema:

    src/sandbox.tsts
    const ResultEvent = z.object({
        type: z.literal("result"),
        subtype: z.string(),
        is_error: z.boolean(),
        result: z.string().optional(),
    });
  5. 替换 agentCommand() 和 agentEnv() 方法:

    src/sandbox.tsts
        private agentCommand(prompt: string): string[] {
            return [
                "claude",
                "--print",
                "--output-format",
                "stream-json",
                "--verbose",
                "--dangerously-skip-permissions",
                "--no-session-persistence",
                "--model",
                this.env.MODEL,
                "--",
                prompt,
            ];
        }
    
        private agentEnv(): Record<string, string> {
            return {
                ANTHROPIC_BASE_URL: `https://gateway.ai.cloudflare.com/v1/${this.env.AI_GATEWAY_ACCOUNT_ID}/${this.env.AI_GATEWAY_ID}/anthropic`,
                ANTHROPIC_API_KEY: "provided-by-worker",
                CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1",
                IS_SANDBOX: "1",
            };
        }
    • --print 在没有交互式终端的情况下运行 Claude Code。
    • --output-format stream-json --verbose 每行写入一个 JSON 事件,包括最终的 result 事件。
    • --dangerously-skip-permissions 允许 Claude Code 在不询问的情况下运行命令和编辑文件。container 会限制其命令可以访问的范围。Claude Code 要求 IS_SANDBOX=1 才能以 root 允许此行为。
    • ANTHROPIC_BASE_URL 将模型请求发送到 gateway 的 Anthropic 端点。
    • ANTHROPIC_API_KEY 是占位符,因为没有 API key 时 Claude Code 不会启动。
    • CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 会关闭更新检查、遥测和错误报告,因此 Claude Code 只会调用你的 gateway。

    如果仓库有 CLAUDE.md 文件,Claude Code 会读取它,因此该文件中的说明会应用于任务。

  6. 替换 outcome() 方法:

    src/sandbox.tsts
    export class AgentSandbox extends DurableObject<Env> {
        // ...
    
        private async outcome(exitCode: number): Promise<TaskStatus> {
            let result: z.infer<typeof ResultEvent> | undefined;
            const events = await this.files.readFile(eventsPath);
    
            for await (const line of readLines(events.body)) {
                const parsed = ResultEvent.safeParse(parseJson(line));
    
                if (parsed.success) {
                    result = parsed.data;
                }
            }
    
            if (result === undefined) {
                const stderr = (await this.readOptionalText(stderrPath)) ?? "";
                return {
                    state: "failed",
                    error: `claude exited with ${exitCode}: ${stderr.slice(-2000)}`,
                };
            }
    
            if (result.is_error) {
                return { state: "failed", error: result.result ?? result.subtype };
            }
    
            return { state: "succeeded", result: result.result ?? "" };
        }
    }

    即使模型请求失败,Claude Code 也会以成功状态退出,因此结果来自最终 result 事件的 is_error 字段,而不是退出码。Claude Code 会对失败的模型请求重试 10 次。被拒绝的 gateway token 大约三分钟后会以 Failed to authenticate. API Error: 401 Unauthorized 使任务失败。

  7. 部署你的 Worker:

    npx wrangler deploy

    正在运行的 container 会保留启动时使用的镜像,因此请为 Claude Code 使用新的 sandbox 名称。

运行任务

  1. 将仓库克隆到名为 claude-1 的 sandbox:

    curl "$WORKER_URL/sandboxes/claude-1/repository" \
        --json '{"url": "https://github.com/octocat/Hello-World"}'
  2. 启动任务:

    curl "$WORKER_URL/sandboxes/claude-1/task" \
        --data "Add a file NOTES.md with a one-sentence summary of this repository. Do not commit."
  3. 检查任务,直到其状态为 succeeded 或 failed:

    curl "$WORKER_URL/sandboxes/claude-1/task"

    succeeded 任务会包含 Claude Code 的最终回复。

  4. 读取更改:

    curl "$WORKER_URL/sandboxes/claude-1/diff"

    diff 会显示新的 NOTES.md 文件。

相关资源

这篇文档对您有帮助吗?