Skip to main content

Command Palette

Search for a command to run...

源站

使用 origin repo clone-fast 在 CI 中设置 CloneKit

origin repo clone-fast 用于在 CI 作业中替换 git clone。它不会从零开始克隆,而是下载预构建的克隆包 (Cherri Code 为每个启用了 CloneKit 的代码仓库构建的打包快照) ,然后仅获取自该包构建以来新增的对象。本页介绍如何将短期有效的 Origin 令牌传递到 Runner 上,并在 Buildkite、GitHub Actions 以及其他 CI 系统中运行该命令。

工作原理

每次运行 origin repo clone-fast {owner}/{repo} [DIR] 都会执行以下操作:

  1. 使用 CURSOR_AUTH_TOKEN 中的令牌从 Origin 获取 kit 清单。清单中列出了该 kit 的 artifacts 及其 tip 处的 commit。
  2. 从 gitcdn.origin.cursor.com 下载 pack artifacts 并检查其大小。加上 --verify 时,还会在下载过程中校验其哈希值。
  3. 将它们安装到目标目录中,该目录必须为空。
  4. 补充克隆:获取 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。

前置条件

  • 在 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.comkit 清单与 git 操作
gitcdn.origin.cursor.comkit 下载
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 应用。

1

将 Origin 连接到 Buildkite

在 Buildkite 中,选择 Settings > Repository Providers > Add Provider > Origin,或在 New Pipeline 页面上选择 Connect Origin account。选择所有者和仓库,然后安装 Buildkite 应用。Buildkite 会请求代码仓库 contents 和 PR 的读取权限,以及 checks 的读写权限。详情参见 Buildkite 文档中的 Origin。

2

在 agent 上安装 Origin CLI

按照前置条件操作,并确保 PATH 中有 curl、git 和 jq。

3

保持 checkout 目录为空

如果 agent 在多次构建之间保留构建目录,请在钩子运行前清空 $BUILDKITE_BUILD_CHECKOUT_PATH。clone-fast 需要一个空目录,若发现其中有文件,则会回退到 git clone。

4

添加 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 才能安装该应用。

注册应用

1

生成 Ed25519 密钥对

仅支持 Ed25519 密钥:

openssl genpkey -algorithm ED25519 -out origin-app-private.pemopenssl pkey -in origin-app-private.pem -pubout -out origin-app-public.pem
2

创建应用并添加公钥

在 Origin 应用设置中创建应用,并将 origin-app-public.pem 的内容添加为签名密钥。每个应用最多可拥有 10 个当前有效的签名密钥。

3

复制 App ID

App ID 显示在该应用的页面上,以 app_ 开头。

安装应用

1

将应用安装到你的 owner

在同一设置中的应用安装页面上,将应用安装到拥有待 克隆 仓库的 owner 上,并选择这些仓库。

2

复制 installation id

installation id 位于该安装页面的 URL 中:/codebase/settings/apps/installations/{installationId}。installation id 以 i_ 开头。

存储凭据

将私钥作为机密信息存储在 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。

1

添加机密信息

在你的代码仓库中,依次选择 Settings > Secrets and variables > Actions,然后添加机密信息 ORIGIN_APP_PRIVATE_KEY,内容为完整的私钥 PEM。

2

添加变量

在同一页面添加代码仓库变量 ORIGIN_APP_ID 和 ORIGIN_INSTALLATION_ID。

3

添加工作流

将下面的工作流保存为 .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 账户团队。