使用 origin repo clone-fast 在 CI 中设置 CloneKit
Origin 目前处于早期 beta 版阶段。你可以创建仓库、使用 git 推送和拉取、从 GitHub 镜像、浏览和搜索代码、创建和合并 PR,并与你的 Cherri Code 团队共享。
欢迎将任何反馈发送至 [email protected],帮助我们把产品做得更好。
origin repo clone-fast 用于在 CI 作业中替换 git clone。它不会从零开始克隆,而是下载预构建的克隆包 (Cherri Code 为每个启用了 CloneKit 的代码仓库构建的打包快照) ,然后仅获取自该包构建以来新增的对象。本页介绍如何将短期有效的 Origin 令牌传递到 Runner 上,并在 Buildkite、GitHub Actions 以及其他 CI 系统中运行该命令。
工作原理
每次运行 origin repo clone-fast {owner}/{repo} [DIR] 都会执行以下操作:
- 使用
CURSOR_AUTH_TOKEN中的令牌从 Origin 获取 kit 清单。清单中列出了该 kit 的 artifacts 及其 tip 处的 commit。 - 从
gitcdn.origin.cursor.com下载 pack artifacts 并检查其大小。加上--verify时,还会在下载过程中校验其哈希值。 - 将它们安装到目标目录中,该目录必须为空。
- 补充克隆:获取 kit 构建之后新增的对象,并 checkout 到默认分支的当前 tip。加上
--no-top-up时,checkout 会停留在 kit 的 tip 处。--bare则创建一个没有 working tree 的 bare 代码仓库。
kit 路径成功时,命令会在标准输出打印 mode: clone-kit。若其中任一步骤失败,默认的 --fallback auto 会改为执行普通的 git clone,并打印 mode: git-clone。传入 --fallback never 则以状态码 1 退出。
在所有 CI 系统上,令牌与 checkout 的行为都是一致的:
- 令牌最长 15 分钟后过期,且无法刷新。 请在克隆前的一刻再签发。
- CLI 从
CURSOR_AUTH_TOKEN读取令牌。 请在运行origin的 shell 中导出该变量。 - 克隆完成后停在默认分支的 tip 上。 若要构建特定 commit,请随后运行
git fetch origin SHA和git checkout SHA。Origin 可按 SHA 提供任何可达的 commit。
前置条件
CloneKit 面向企业版方案提供,并需按代码仓库单独开启,无自助开关。请联系你的 Cherri Code 客户团队,为每个需要克隆的代码仓库开启该功能。运行 origin repo clone-fast --help 可查看该命令的选项。
- 在 runner 上安装 Origin CLI。执行
curl -fsSL https://downloads.cursor.com/origin/install.sh | sh安装,二进制文件将位于$HOME/.local/bin/origin。Linux runner 需在 x64 或 arm64 上具备 glibc;不支持 Alpine 及其他 musl 镜像。支持 macOS runner。参见 安装 Origin CLI。 - bash、git、jq、curl 7.55 或更高版本,以及 openssl 1.1.1 或更高版本。
clone-fast使用curl完成下载。 - 对以下主机的网络出站访问权限:
| 主机 | 用途 |
|---|---|
downloads.cursor.com | 安装 CLI |
origin.cursor.com | kit 清单与 git 操作 |
gitcdn.origin.cursor.com | kit 下载 |
api.cursor.com | 签发令牌以及来自 Origin App 的镜像同步请求 |
选择一条路径
| CI 系统 | 令牌来自哪里 | 章节 |
|---|---|---|
| Buildkite,托管或自托管 agents,并已将 Origin 连接为代码仓库提供方 | Buildkite Agent API,在 checkout 钩子中 | Buildkite |
| GitHub Actions | 你的 Origin App,在工作流步骤中 | GitHub Actions |
| GitLab CI、Jenkins、CircleCI 以及自托管 fleets | 你的 Origin App,在作业中 | 其他 CI 系统 |
Buildkite
Buildkite 会为你签发令牌。将 agent 的默认 git checkout 替换为 checkout 钩子,由该钩子向 Buildkite Agent API 请求令牌并运行 origin repo clone-fast。
你必须是 Buildkite 组织管理员才能连接 Origin,并且必须是 Origin 管理员才能安装 Buildkite 应用。
将 Origin 连接到 Buildkite
在 Buildkite 中,选择 Settings > Repository Providers > Add Provider > Origin,或在 New Pipeline 页面上选择 Connect Origin account。选择所有者和仓库,然后安装 Buildkite 应用。Buildkite 会请求代码仓库 contents 和 PR 的读取权限,以及 checks 的读写权限。详情参见 Buildkite 文档中的 Origin。
在 agent 上安装 Origin CLI
按照前置条件操作,并确保 PATH 中有 curl、git 和 jq。
保持 checkout 目录为空
如果 agent 在多次构建之间保留构建目录,请在钩子运行前清空 $BUILDKITE_BUILD_CHECKOUT_PATH。clone-fast 需要一个空目录,若发现其中有文件,则会回退到 git clone。
添加 checkout 钩子
在自托管 agent 上,将下面的脚本保存为 agent --hooks-path 目录中的 checkout。在 Buildkite 托管 agent 上,将其作为非 vendored 插件的 checkout 钩子分发,或者在该步骤上设置 checkout: { skip: true },并在该步骤的 command 中运行相同的命令。代码仓库钩子无法定义 checkout,因为此时代码仓库尚未检出。参见 Buildkite 文档中的 Agent hooks 和 Git checkout。
该钩子会请求一个作用域限定为流水线代码仓库的令牌,将其导出为 CURSOR_AUTH_TOKEN,用 clone-fast 克隆,通过 origin auth setup-git 注册 git credential helper,并检出 $BUILDKITE_COMMIT。agent 令牌通过 stdin 传给 curl,因此绝不会出现在命令行上:
#!/usr/bin/env bashset -euo pipefailbody=$(printf '{"repo_url":"%s"}' "$BUILDKITE_REPO")token=$(printf 'Authorization: Token %s\n' "$BUILDKITE_AGENT_ACCESS_TOKEN" \ | curl -fsS -X POST -H @- \ -H 'Content-Type: application/json' -H 'Accept: application/json' \ --data "$body" \ "${BUILDKITE_AGENT_ENDPOINT%/}/jobs/${BUILDKITE_JOB_ID}/cursor_origin_access_token" \ | jq -er '.token')# clone-fast 接受的是 owner/repo,而非 clone URL。repo=${BUILDKITE_REPO#https://origin.cursor.com/}repo=${repo#git/}repo=${repo%.git}export CURSOR_AUTH_TOKEN="$token"origin repo clone-fast "$repo" "$BUILDKITE_BUILD_CHECKOUT_PATH" --verifyorigin auth setup-gitcd "$BUILDKITE_BUILD_CHECKOUT_PATH"git fetch origin "$BUILDKITE_COMMIT"git checkout -q "$BUILDKITE_COMMIT"当 build 在没有 commit 的情况下启动时,BUILDKITE_COMMIT 的值为 HEAD。此时这两行 git 命令会拉取远端的 HEAD,并让 working tree 停留在 clone-fast 已检出的默认分支 tip 上。
- 原样传入
$BUILDKITE_REPO。repo_url必须与 pipeline 上注册的 repository URL 完全一致,包括.git在内。写法稍有出入就会返回 HTTP 400。 - 该令牌为只读,且仅作用于该 pipeline 的仓库。 它携带
repository:contents:read权限,15 分钟后过期。后续的作业,或在签发 15 分钟之后执行的 git 操作,都需要通过同一个 request 重新获取令牌。 - 遇到 503 时重试。 如果 Agent API 返回 HTTP 503,请按其
Retry-After请求头指定的时间等待后重试,Buildkite 自带的 credential helper 也是这样处理的。
GitHub Actions 和其他 CI providers
GitHub Actions 和其他 CI 系统会自行签发令牌。你只需创建一次 Origin App,将其安装到需要 克隆 的仓库上,并为每个作业提供该应用的私钥和两个 ID。作业会签名一个短期有效的应用 JWT,并用它换取安装令牌。完整的字段参考请见 App JWT 和 创建安装访问令牌。
创建 Origin App
你必须是 workspace admin 才能安装该应用。
注册应用
生成 Ed25519 密钥对
仅支持 Ed25519 密钥:
openssl genpkey -algorithm ED25519 -out origin-app-private.pemopenssl pkey -in origin-app-private.pem -pubout -out origin-app-public.pem创建应用并添加公钥
在 Origin 应用设置中创建应用,并将 origin-app-public.pem 的内容添加为签名密钥。每个应用最多可拥有 10 个当前有效的签名密钥。
复制 App ID
App ID 显示在该应用的页面上,以 app_ 开头。
安装应用
将应用安装到你的 owner
在同一设置中的应用安装页面上,将应用安装到拥有待 克隆 仓库的 owner 上,并选择这些仓库。
复制 installation id
installation id 位于该安装页面的 URL 中:/codebase/settings/apps/installations/{installationId}。installation id 以 i_ 开头。
若要以编程方式读取 installation id,可使用 应用 JWT 作为 bearer 调用 List App Installations。响应中包含每个安装的 id、target.slug、scopes 和 repoSelectionMode。
存储凭据
将私钥作为机密信息存储在 CI 系统中,并以环境变量 ORIGIN_APP_PRIVATE_KEY 的形式提供给作业,其内容为完整的 PEM 文本。将 App ID 和 installation id 存储为普通变量 ORIGIN_APP_ID 和 ORIGIN_INSTALLATION_ID。如果你的 CI 系统将机密信息挂载为文件,请先使用 ORIGIN_APP_PRIVATE_KEY=$(cat /path/to/origin-app-private.pem) 加载该密钥。
在作业中签发令牌
下面的函数会对应用 JWT 签名,并用它换取安装令牌。该 JWT 的 alg 为 EdDSA,iss 和 kid 设为 App ID,aud 设为 origin-apps,exp 设为 15 分钟之后。App JWT 参考文档建议有效期约为五分钟;由于安装令牌的有效期不会超过签发它的 JWT,本方案签发有效期 15 分钟的 JWT,让令牌能用满 15 分钟。请求申请的权限是 repository:contents:read,可满足 克隆、获取 和 拉取。push 则需要 repository:contents:write,且该安装必须已授予此 scope。在 Request body 中加入 "repositoryIds":[...],可将令牌的范围限定为该安装下的部分仓库。
私钥通过文件描述符传给 openssl,bearer 请求头通过 stdin 传给 curl,两者都不会出现在命令行中,以免被 runner 上的其他 process 读取。该函数直接设置并导出 CURSOR_AUTH_TOKEN,令牌不会落盘;若签发失败,在 set -e 下作业会随之终止。该函数需要 bash、openssl 1.1.1 或更高版本、curl 7.55 或更高版本,以及 jq:
origin_app_token() { b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; } now=$(date +%s) header=$(printf '{"alg":"EdDSA","kid":"%s","typ":"JWT"}' "$ORIGIN_APP_ID" | b64url) claims=$(printf '{"iss":"%s","aud":"origin-apps","iat":%d,"exp":%d}' \ "$ORIGIN_APP_ID" "$now" "$((now + 900))" | b64url) # openssl -rawin 需要可寻址(seekable)的输入;签名输入本身不含机密信息。 signing_input=$(mktemp) trap 'rm -f "$signing_input"' EXIT printf '%s.%s' "$header" "$claims" > "$signing_input" signature=$(openssl pkeyutl -sign -rawin -in "$signing_input" \ -inkey <(printf '%s\n' "$ORIGIN_APP_PRIVATE_KEY") | b64url) app_jwt="$header.$claims.$signature" CURSOR_AUTH_TOKEN=$(printf 'Authorization: Bearer %s\n' "$app_jwt" \ | curl -fsS -X POST -H @- -H 'Content-Type: application/json' \ --data '{"scopes":["repository:contents:read"]}' \ "https://api.cursor.com/v1/origin/app/installations/${ORIGIN_INSTALLATION_ID}/access_tokens" \ | jq -er '.token') export CURSOR_AUTH_TOKEN}在将仓库克隆到空目录之前立即调用它,然后注册 git credential helper,这样在 CURSOR_AUTH_TOKEN 保持导出状态时,后续的 git 命令就能完成认证。将 acme/widgets 替换为你的代码仓库,将 COMMIT_SHA 替换为 CI 系统为该次构建提供的 提交:
set -euo pipefailorigin_app_tokenorigin repo clone-fast acme/widgets . --verifyorigin auth setup-gitgit fetch origin "$COMMIT_SHA"git checkout -q "$COMMIT_SHA"GitHub Actions
GitHub Actions 采用 Origin App 方式:私钥存放在 Actions 机密信息中,两个 id 存放在代码仓库变量中。由于工作流由 GitHub 触发,代码仓库位于 GitHub 上,Origin 以镜像形式保存该仓库。工作流会请求 Origin 拉取本次构建的提交,用 clone-fast 克隆到空的 $GITHUB_WORKSPACE,然后检出该提交。整个过程不会调用 GitHub,因此无需任何 GITHUB_TOKEN 权限,也不使用 actions/checkout。
添加机密信息
在你的代码仓库中,依次选择 Settings > Secrets and variables > Actions,然后添加机密信息 ORIGIN_APP_PRIVATE_KEY,内容为完整的私钥 PEM。
添加变量
在同一页面添加代码仓库变量 ORIGIN_APP_ID 和 ORIGIN_INSTALLATION_ID。
添加工作流
将下面的工作流保存为 .github/workflows/ci.yml,将 ORIGIN_REPO 设置为该镜像在 Origin 上的 {owner}/{repo},并在克隆步骤之后添加你的构建步骤。
该工作流会安装 CLI、签发令牌、等待 Origin 镜像该提交,然后进行克隆:
name: cion: push: branches: [main] pull_request:permissions: {}jobs: build: runs-on: ubuntu-latest defaults: run: shell: bash steps: - name: Install the Origin CLI run: | curl -fsSL https://downloads.cursor.com/origin/install.sh | sh echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Clone from Origin with clone-fast env: ORIGIN_APP_ID: ${{ vars.ORIGIN_APP_ID }} ORIGIN_INSTALLATION_ID: ${{ vars.ORIGIN_INSTALLATION_ID }} ORIGIN_APP_PRIVATE_KEY: ${{ secrets.ORIGIN_APP_PRIVATE_KEY }} ORIGIN_REPO: acme/widgets BUILD_BRANCH: ${{ github.head_ref || github.ref_name }} BUILD_SHA: ${{ github.event.pull_request.head.sha || github.sha }} run: | set -euo pipefail origin_app_token() { b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; } now=$(date +%s) header=$(printf '{"alg":"EdDSA","kid":"%s","typ":"JWT"}' "$ORIGIN_APP_ID" | b64url) claims=$(printf '{"iss":"%s","aud":"origin-apps","iat":%d,"exp":%d}' \ "$ORIGIN_APP_ID" "$now" "$((now + 900))" | b64url) signing_input=$(mktemp) trap 'rm -f "$signing_input"' EXIT printf '%s.%s' "$header" "$claims" > "$signing_input" signature=$(openssl pkeyutl -sign -rawin -in "$signing_input" \ -inkey <(printf '%s\n' "$ORIGIN_APP_PRIVATE_KEY") | b64url) app_jwt="$header.$claims.$signature" echo "::add-mask::$app_jwt" CURSOR_AUTH_TOKEN=$(printf 'Authorization: Bearer %s\n' "$app_jwt" \ | curl -fsS -X POST -H @- -H 'Content-Type: application/json' \ --data '{"scopes":["repository:contents:read"]}' \ "https://api.cursor.com/v1/origin/app/installations/${ORIGIN_INSTALLATION_ID}/access_tokens" \ | jq -er '.token') echo "::add-mask::$CURSOR_AUTH_TOKEN" export CURSOR_AUTH_TOKEN } origin_app_token # Origin 镜像 GitHub 会有延迟。等待该提交同步,最多约两分钟。 body=$(printf '{"ref":"refs/heads/%s","sha":"%s","wait":true}' "$BUILD_BRANCH" "$BUILD_SHA") printf 'Authorization: Bearer %s\n' "$CURSOR_AUTH_TOKEN" \ | curl -fsS -X POST -H @- -H 'Content-Type: application/json' --data "$body" \ "https://api.cursor.com/v1/origin/repos/${ORIGIN_REPO}:syncMirror" \ | jq -e '.synced' > /dev/null \ || { echo "Origin has not mirrored $BUILD_SHA from $BUILD_BRANCH yet" >&2; exit 1; } origin repo clone-fast "$ORIGIN_REPO" . --verify origin auth setup-git git fetch origin "$BUILD_SHA" git checkout -q "$BUILD_SHA"- 在
pull_request事件中,工作流构建的是 PR 的 head。github.sha是仅存在于 GitHub 上的合并提交,因此BUILD_SHA和BUILD_BRANCH改用 PR 的 head 提交和分支。 - 同步请求即 Sync Mirror。 它接受安装令牌,需要
repository:contents:read权限,并在约两分钟的等待预算后返回200且"synced": true,或返回202且"synced": false。如果 Origin 是权威来源而 GitHub 是镜像,请删除该同步请求:提交已经在 Origin 上,且 Origin 会拒绝针对不从上游来源拉取的仓库的同步请求。 - 两种凭据都会被掩码。
::add-mask::会在作业日志中隐藏应用 JWT 和安装令牌。 - 后续步骤需重新签发。 后续需要 Origin git 访问权限的步骤要再次定义并调用该函数。令牌绝不会写入
$GITHUB_ENV或$GITHUB_OUTPUT,因为它们是磁盘上的文件。 - 来自 fork 的 PR 不会被镜像。 它们的 head 分支不在你的仓库中。请从 GitHub 构建这些 PR。
其他 CI 系统
GitLab CI、Jenkins、CircleCI 以及自托管 fleet 可直接走 Origin App 这条路径。按照存储凭据中的说明保存私钥和两个 id,然后在执行 origin repo clone-fast 前立即运行在作业中签发 token 中的函数,并 获取 并 check out 你的 CI 系统为该构建提供的 提交。
验证首次运行
首次作业请使用 --fallback never 运行。这样一来,若 kit 缺失,作业会以退出状态 1 失败,而不是悄然执行缓慢的 git clone。在 kit 路径成功之前,请一直保留该 flag。
运行成功时,stderr 会打印 clone-kit: manifest=...、每个 artifact 一行的下载信息、一个 clone-kit timings: 块,以及 clone-kit: ready DIR (head SHA),随后 stdout 输出 mode: clone-kit,并以 0 退出。timings 块会将本次运行拆分为清单、download、verify 和 checkout 几个阶段。若要衡量提速效果,可将其总耗时与在同一 runner 上对同一代码仓库执行普通 git clone 的耗时进行对比。
当 kit 路径未能完成时,stderr 会显示一行机器可读信息:clone-kit-result: status=fallback phase=PHASE 或 clone-kit-result: status=failed phase=PHASE,其后紧跟原因说明。在默认的 --fallback auto 下,stdout 随后会显示 mode: git-clone。
疑难排查
每个条目均以作业日志中显示的那一行开头。
清单获取返回 404
clone-kit-result: status=fallback phase=manifest 并伴随 HTTP 404。该代码仓库尚无克隆包;请联系你的 Cherri Code 账户团队开启 CloneKit。如果消息中的 URL 包含两个 https://,说明该命令收到的是 clone URL 而不是 {owner}/{repo}。
清单获取返回 401
clone-kit manifest fetch failed: HTTP 401。Origin 拒绝了该令牌:它已过期、从未有效,或其安装已被移除。请在克隆前立即签发新的令牌。在默认的 --fallback auto 下,回退的 git clone 会以相同状态码失败并返回退出码 128,因此日志中会同时出现这两个错误。
清单获取返回 403
clone-kit manifest fetch failed: HTTP 403。该安装或 Buildkite 流水线令牌未覆盖此代码仓库。请重新安装该应用,或重新选择其覆盖的仓库。与 401 一样,回退的 git clone 会以相同状态码失败并返回退出码 128。
Git 提示需要输入用户名
fatal: could not read Username for 'https://origin.cursor.com'。可能是命令行界面版本过旧,或未在运行 git 的 shell 中导出 CURSOR_AUTH_TOKEN。执行 origin update,或导出该变量。
克隆在认证阶段回退
clone-kit-result: status=fallback phase=auth 并伴随 Not authenticated。未将 CURSOR_AUTH_TOKEN 导出到运行 origin 的进程中。在执行 origin 命令前,在同一 shell 中导出该变量。
签发返回 401 Issuer is not authorized
令牌 endpoint 返回 401 {"code":16,"message":"Issuer is not authorized"}。这说明 iss claim 并非已注册的 App ID,或该 app 未注册对应的 signing key。请确认 ORIGIN_APP_ID 与该 app 一致,且该 app 已列出该 signing key。
origin auth status 报告令牌有效但克隆失败
origin auth status 是根据 CURSOR_AUTH_TOKEN 中的过期声明得出 Token: valid 或 Token: expired 的。它并不会向 Origin 确认其是否接受该令牌,因此 valid 仅表示该令牌尚未过期。命令行界面会在过期声明所示时间前五分钟就将令牌视为已过期。origin 命令会在发出任何请求之前拒绝已过期的令牌,而 clone-fast 会记录 clone-kit-result: status=fallback phase=auth,并附带 The injected CURSOR_AUTH_TOKEN session has expired。请签发新的令牌并重试。
如果 kit 路径仍然失败,请将包含 clone-kit-result 行的作业日志、代码仓库以及 origin --version 的输出发送给你的 Cherri Code 账户团队。