SpyBara
Go Premium

self-hosted-environments-configuration.md 2026-10-01 23:59 UTC to 2026-10-02 11:59 UTC

This page contains 83 additions and 38 deletions.

2026
Fri 2 13:00

在自託管環境中自訂工作階段

使用包裝指令碼在自託管環境工作階段中自訂每個工作階段的憑證、生命週期 hook 和按需執行器生成。

自託管環境在您自己的基礎設施上執行 Claude Code 雲端會話,由您部署的執行器程序執行。在沒有設定的情況下,該執行器會複製會話的儲存庫、生成 Claude Code,然後進行清理。本頁面適用於操作執行器的平台工程師:它涵蓋了當預設值不適用時的擴展點,從每個會話的認證佈建到完全替換簽出。包裝指令碼和掛鉤在執行器主機上作為可執行檔案執行,該主機是 Linux 或 macOS,本頁面上的範例假設使用 POSIX shell。

本頁面上的一些掛鉤環境變數仍然使用 pool,例如 CLAUDE_RUNNER_POOL_ID;CLI 旗標和環境變數名稱使用 environment,例如 --environment-secret-file。

包裝指令碼

當每個工作階段需要執行器無法自行完成的設定時,請使用包裝指令碼:佈建限定於工作階段建立者的短期憑證、匯出環境特定的祕密、準備語言工具鏈,或在子程序周圍套用資源限制。執行器每個工作階段啟動一次您的包裝指令碼,以取代 Claude Code 二進位檔案。透過 exec 進入 $CLAUDE_RUNNER_CLAUDE_BIN(執行器自己的二進位檔案)來結束包裝指令碼,以便訊號和退出碼正確傳播。

在啟動執行器時,將 --exec-path 或 SELF_HOSTED_RUNNER_EXEC_PATH 指向包裝指令碼:

claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret --exec-path /etc/claude/session-wrapper.sh

執行器在包裝指令碼的環境中設定以下內容:

變數 說明
CLAUDE_CODE_SESSION_ACCESS_TOKEN 工作階段 JWT,前綴為 sk-ant-cc-。其 act 聲明識別工作階段建立者,並在建立的使用介面有記錄時包含建立者的電子郵件。該值是生成時的 token;重新整理會透過子程序的 stdin 到達,因此包裝指令碼只會看到初始值。請參閱驗證工作階段身分。
CCR_SESSION_ACCOUNT_EMAIL 工作階段建立者的電子郵件,由執行器從 token 的 act.email 聲明中預先提取,未經簽章驗證。適合用於標籤,例如提交尾註。當電子郵件限制憑證發行時,請改為驗證 token 並從中讀取聲明;請參閱佈建限定於工作階段建立者的憑證。當 token 不包含建立者電子郵件時未設定。請視為個人可識別資訊。
CLAUDE_RUNNER_CLIENT_PLATFORM 建立工作階段的用戶端使用介面,例如 web_claude_ai、desktop_app、ios、claude_code_cli 或 scheduled_trigger。Anthropic 在工作階段建立時記錄該值一次,因此包裝指令碼和每個生命週期 hook 都會看到相同的值。僅將其用於採用分析和標籤,不要用作授權訊號。當工作階段沒有已記錄或可識別的使用介面時未設定,因此在 set -u 下請以 ${CLAUDE_RUNNER_CLIENT_PLATFORM:-} 參照它。需要 Claude Code v2.1.229 或更新版本。
CLAUDE_RUNNER_CLAUDE_BIN 執行器自己的 Claude Code 二進位檔案的絕對路徑。以 exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" 結束您的包裝指令碼,以移交給固定的二進位檔案,而無需硬編碼安裝路徑。
CLAUDE_CODE_REMOTE_SESSION_ID 標記形式為 cse_... 的工作階段 ID。這與生命週期 hook 以 CLAUDE_RUNNER_SESSION_ID(session_... 形式)看到的是相同工作階段;UUID 變數在兩者之間相符,將 cse_ 前綴替換為 session_ 會產生工作階段 URL 中顯示的 ID。
CLAUDE_CODE_REMOTE_SESSION_UUID 標準 UUID 形式的相同工作階段 ID,適用於以 UUID 為鍵的系統。
CLAUDE_SESSION_INGRESS_TOKEN_FILE 保存目前工作階段 JWT 的每個工作階段檔案的絕對路徑,在 token 重新整理時保持最新。Shell 子程序在下載使用者新增到工作階段的附件時從中讀取其 Authorization 標頭。exec 會自動保留該變數;重建子程序環境的包裝指令碼必須帶上該變數,否則附件下載會無聲地停止運作。
CLAUDE_CONFIG_DIR 每個工作階段的 Claude 設定目錄,在工作階段開始時從執行器在啟動時擷取的執行器主機設定快照中寫入;請參閱權限和工具核准。此處的寫入隔離於此工作階段。工作階段結束後,該目錄會保留在 <base-dir>/_sessions/ 下,除非您使用 --remove-session-state 啟動執行器;請參閱重複使用預先準備的簽出。
ANTHROPIC_BASE_URL 子程序將使用的 API 基礎 URL,由控制平面按工作階段傳遞,通常為 https://api.anthropic.com。不要覆寫它:工作階段的推理憑證是 Anthropic 發行的 OAuth token,其他提供者不接受,因此自託管環境中的推理無法路由到其他地方。
CLAUDE_CODE_OAUTH_TOKEN 子程序用於模型推理的短期 OAuth 存取 token,限定於模型推理和檔案上傳,生命週期約為 30 分鐘。執行器在過期前重新鑄造它,並透過子程序的 stdin 傳遞輪換,因此未保持 stdin 連接的包裝指令碼只會看到初始值。不要依賴您組織的 IP 允許清單來限制此 token 的使用:請將其視為持有人憑證,如果洩露,大約 30 分鐘內仍可使用,不要將其寫入日誌、寫入磁碟或在工作階段容器外轉發它。

包裝指令碼也會繼承子程序的其餘受管環境,包括任何伺服器提供的環境變數。exec 會自動傳播所有內容;如果您的包裝指令碼以另一種方式生成子程序,請轉發完整環境。

保持 stdin 和檔案描述符 3 連接

子程序的 stdin 是執行器的控制通道。token 輪換和工作階段結束訊號會在其上到達。執行器也會在檔案描述符 3 上開啟一個管道,並從中讀取子程序的活動訊號以驅動閒置和啟動逾時。單純的 exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" 會自動保留兩者。

如果您的包裝指令碼使用單獨的 & 在背景中執行子程序,它會切斷子程序的 stdin:工作階段看起來健康,直到初始 OAuth token 大約 30 分鐘的生命週期過期,然後每個 API 呼叫都會失敗,並出現 401 authentication_error。如果您的包裝指令碼必須在背景中執行子程序,例如為了讓拆卸 trap 保持作用,請將 stdin 儲存在檔案描述符 4 或更高的編號上,並明確重新連接它:

exec 4<&0
"$CLAUDE_RUNNER_CLAUDE_BIN" "$@" <&4 4<&- &
CHILD=$!
trap 'teardown' EXIT
wait "$CHILD"

不要在包裝指令碼中關閉或重複使用檔案描述符 3。重新導向子程序的 stdout 和 stderr 是可以的。

傳遞系統提示詞旗標

Anthropic 控制平面為工作階段傳送的系統提示詞和附加系統提示詞,會以檔案路徑而非內嵌文字的形式傳到您的包裝指令碼。執行器會將每個提示詞寫入工作階段設定目錄 CLAUDE_CONFIG_DIR 中的檔案,並在您的包裝指令碼接收的引數中傳遞其路徑,形式為 --system-prompt-file <path> 或 --append-system-prompt-file <path>。

Claude Code v2.1.281 或更新版本上的執行器會以檔案形式傳遞提示詞。在 v2.1.281 之前,執行器以 --system-prompt <text> 和 --append-system-prompt <text> 傳遞它們。

在您的包裝指令碼或 command hook 中,請依下列方式處理這些旗標:

  • 原樣傳遞它們:以 exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" 結束包裝指令碼,這會將檔案旗標連同其他所有引數一起轉發。不要捨棄或改寫它們。如果工作階段遺失了提示詞檔案旗標,它將在缺少控制平面為其傳送之指令的情況下執行。
  • 在 v2.1.281 或更新版本的執行器上,您附加的檔案旗標會取代伺服器的旗標,而不會與其疊加:每個提示詞檔案旗標只接受單一值,且 Claude Code 會保留最後一次出現的值,因此如果您在 "$@" 之後附加 --append-system-prompt-file <path>,您檔案的內容會取代伺服器的附加指令。若要在伺服器的指令之上新增指令,請將它們放入執行器映像的 CLAUDE.md 中,執行器會將其植入每個工作階段的使用者層級設定。

佈建限定於工作階段建立者的憑證

使用 decode-token 子命令從工作階段 JWT 讀取聲明。它依序從引數、CLAUDE_CODE_SESSION_ACCESS_TOKEN 或 stdin 讀取 token;請參閱驗證工作階段內的 token 以了解它檢查的內容。下面的範例解碼建立者身分,將其交換為短期 AWS 憑證,並透過 exec 進入 Claude Code:

#!/bin/bash
# Key on the stable Anthropic user ID and require a human creator.
CREATOR_SUB=$("$CLAUDE_RUNNER_CLAUDE_BIN" self-hosted-runner decode-token \
  | jq -re '.act.sub // "" | select(startswith("user:"))') \
  || { echo "decode-token: verification failed or no human creator" >&2; exit 1; }

creds=$(your-sts-helper assume-role --subject "$CREATOR_SUB") \
  || { echo "credential exchange failed" >&2; exit 1; }
eval "$creds"

exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@"

在提取的聲明限制授權決定時,使用 jq -re 而不是 jq -r,以便缺少的聲明以非零狀態退出,而不是將字面字串 null 傳遞到下游。由組織服務身分(例如機器人和 agent 工作階段)建立的工作階段帶有 agent: 主體而不是 user:,因此此範例會拒絕它們;如果您的環境服務這些工作階段,請明確決定包裝指令碼是否為它們回退到預設憑證,而不是退出。當您的憑證交換改為需要電子郵件時,請讀取 .act.email 並處理其缺失的情況:token 只在建立的使用介面有記錄時才帶有它,而 CLI 分派的工作階段可能缺少它。有關完整的聲明參考以及從執行器外部服務進行的驗證,請參閱驗證工作階段身分。

生命週期掛鉤

生命週期掛鉤用您自己的指令碼替換執行器每個會話管道的階段。使用 --hooks-dir <path> 或 SELF_HOSTED_RUNNER_HOOKS_DIR 將執行器指向掛鉤目錄。執行器尋找具有眾所周知名稱的可執行檔案;任何不存在的掛鉤都會回退到內建行為,因此您只需編寫所需的掛鉤。掛鉤以執行器自己的權限執行,會話子程序共享該 UID,因此請將掛鉤目錄掛載為唯讀,或將其烘焙到映像中,以便會話代碼無法修改它;請參閱強化部分。

這些掛鉤不同於Claude Code 掛鉤,後者在會話內執行;生命週期掛鉤在執行器上執行,圍繞會話。

checkout

每個儲存庫執行一次,取代執行器內建的複製與擷取。使用此 hook 從直讀式鏡像複製、從封存檔植入工作樹,或套用每個工作階段的 git 身分驗證。執行器會設定下列變數,也可能設定表格未列出的其他 CLAUDE_RUNNER_ 變數:

變數 說明
CLAUDE_RUNNER_REPO_URL 要複製的儲存庫 URL,在應用任何 --git-host-rewrite 和 --git-ssh-rewrite 之後
CLAUDE_RUNNER_REPO_REF 要簽出的修訂版本:分支、標籤或提交 SHA,如會話要求的那樣。空表示儲存庫的預設分支。
CLAUDE_RUNNER_CHECKOUT_PATH 工作樹必須留下的絕對路徑
CLAUDE_RUNNER_SESSION_ID 標記形式為 session_... 的會話 ID,用於記錄和相關性
CLAUDE_RUNNER_SESSION_UUID 規範 UUID 形式的相同會話 ID
CLAUDE_RUNNER_API_BASE_URL 用於會話範圍呼叫的 Anthropic API 基礎 URL
CLAUDE_RUNNER_CLIENT_PLATFORM 建立會話的用戶端表面,例如 web_claude_ai、desktop_app 或 ios。當會話沒有記錄或識別的表面時未設定。
CLAUDE_CODE_SESSION_ACCESS_TOKEN 會話存取權杖,用於會話範圍的 API 呼叫
GIT_CONFIG_COUNT、GIT_CONFIG_KEY_n、GIT_CONFIG_VALUE_n 執行器為您的 hook 所執行之 git 固定的 Git 設定。生命週期 hook 內的 Git 設定說明了這些設定。需要 Claude Code v2.1.280 或更新版本。

指令碼必須在 CLAUDE_RUNNER_CHECKOUT_PATH 留下一個工作樹,簽出在要求的修訂版本。分離的 HEAD 是可以的;執行器在頂部建立會話的工作分支。執行器之後驗證路徑包含 .git;如果您的掛鉤具體化非 git 來源(例如 Perforce 或解包的 tarball),請在執行器的環境中設定 CLAUDE_RUNNER_SKIP_GIT_VERIFY=1 以跳過該檢查。基於 Git 的流程(例如工作分支建立和推送結果)需要 git 簽出,因此使用 post-session 掛鉤從非 git 樹匯出結果。

執行器不會將 git 認證傳遞給掛鉤。相反,從會話的身份鑄造每個會話的複製認證:根據驗證來自您的服務的權杖中所述,使用標準 JWT 庫針對 CLAUDE_RUNNER_API_BASE_URL 下的 JWKS 端點驗證 CLAUDE_CODE_SESSION_ACCESS_TOKEN,然後讓您的認證服務為權杖的 act 聲明中的身份發行短期複製認證。CLAUDE_RUNNER_CLAUDE_BIN 未在簽出掛鉤環境中設定,因此 decode-token 子命令在此不可用。回退到主機已有的任何 git 驗證(例如 SSH 代理、認證助手或 .netrc)也是一個選項。

當掛鉤以非零狀態退出,或以 0 退出而沒有在後面留下可用的簽出時,執行器執行的操作取決於儲存庫:

  • 會話推送結果的儲存庫:執行器失敗會話,在非零退出時將指令碼的 stderr 尾部呈現給使用者。
  • 會話只從中讀取的儲存庫,例如新增到執行中會話的儲存庫:執行器記錄帶有失敗詳細資訊的 [runner:warn] 行,向會話發佈 Skipped 步驟,移除掛鉤在簽出路徑留下的任何內容,並繼續處理其餘儲存庫。當執行器無法立即移除路徑時,它會在會話結束時重試移除。如果跳過使會話完全沒有儲存庫,執行器仍然會失敗會話。

在 v2.1.228 之前,執行器在任何儲存庫的掛鉤失敗時失敗會話,因此掛鉤無法提供的唯讀儲存庫在會話在每個新執行器上恢復時再次失敗會話。

執行器在會話結束後移除簽出路徑。

post-session

在 Claude Code 子程序退出後、執行器拆卸工作區之前,每個會話執行一次。此掛鉤是您保存未提交工作的唯一機會:在 --capacity 高於 1 時,執行器在掛鉤返回後立即刪除每個會話的工作樹,在 --capacity 1 時重複使用的規範複製在下一個會話開始時硬重設,因此未提交的追蹤變更在任何一條路徑上都不會存活。典型用途是推送未提交變更的快照分支、存檔日誌或向您自己的系統發出會話結束事件。

掛鉤在每個會話結束時觸發,其中生成了子程序,無論原因如何;下面的 CLAUDE_RUNNER_EXIT_REASON 值列舉了這些情況。當執行器突然終止時(例如 VM 搶佔或電源故障)無法觸發;如果您需要針對突然終止的保證,請改為使用 Claude Code PostToolUse 掛鉤從會話內定期快照。執行器設定:

變數 說明
CLAUDE_RUNNER_SESSION_ID 標記形式為 session_... 的會話 ID
CLAUDE_RUNNER_SESSION_UUID 規範 UUID 形式的相同會話 ID
CLAUDE_RUNNER_EXIT_REASON 會話如何結束;請參閱表格下方的值
CLAUDE_RUNNER_WORKSPACE_PATHS 會話工作樹的冒號分隔絕對路徑。零儲存庫會話為空。
CLAUDE_RUNNER_DEBUG_LOG_PATH 會話的偵錯日誌的路徑,在掛鉤執行時仍在磁碟上
CLAUDE_RUNNER_API_BASE_URL 用於會話範圍呼叫的 Anthropic API 基礎 URL
CLAUDE_RUNNER_CLIENT_PLATFORM 建立會話的用戶端表面,例如 web_claude_ai、desktop_app 或 ios。當會話沒有記錄或識別的表面時未設定。需要 Claude Code v2.1.229 或更新版本。
CLAUDE_CODE_SESSION_ACCESS_TOKEN 會話存取權杖,用於會話範圍的 API 呼叫
GIT_CONFIG_COUNT、GIT_CONFIG_KEY_n、GIT_CONFIG_VALUE_n 執行器為您的 hook 所執行之 git 固定的 Git 設定。生命週期 hook 內的 Git 設定說明了這些設定。需要 Claude Code v2.1.280 或更新版本。

CLAUDE_RUNNER_EXIT_REASON 採用四個值之一:

  • completed:會話乾淨地結束。Claude Code 程序正常退出,或會話在仍在執行時被存檔或刪除。
  • failed:Claude Code 程序崩潰,或在它啟動後設定失敗。
  • interrupted:執行器停止了會話。它釋放會話以釋放插槽、會話在啟動時逾時、伺服器將會話移出此執行器、執行器正在排水,或會話超過其 --kill-session-after-min 限制。
  • abandoned:保留給另一個執行器聲稱的會話。掛鉤目前在該情況下不觸發。

會話生命週期計數器將釋放、啟動逾時和伺服器移動計為 completed 而不是 interrupted,因為執行器乾淨地交回了插槽。如果您將掛鉤收據與計數器進行比較,請預期該差異。

掛鉤的結束狀態永遠不會影響會話結果;失敗被記錄並忽略。執行器在每個會話結束(包括執行器關閉)時等待最多 --post-session-hook-timeout-sec(預設 60 秒)。此範例將未提交的工作保存到救援分支:

#!/usr/bin/env bash
set -u
IFS=':'
# -c overrides beat repo-local settings, blocking session-written fsmonitor,
# hook-path, and gpg-program config from executing code with the hook's
# privileges. -c commit.gpgsign=false also leaves these rescue commits
# unsigned under --configure-git.
# Repo-local credential.helper and pushurl still apply, and on a runner
# before v2.1.280 so does core.sshCommand; if the hook holds credentials
# the session didn't, see the note below the script.
g() { git -c core.fsmonitor=false -c core.hooksPath=/dev/null \
        -c commit.gpgsign=false "$@"; }
for ws in $CLAUDE_RUNNER_WORKSPACE_PATHS; do
  cd "$ws" 2>/dev/null || continue
  [ -z "$(g status --porcelain 2>/dev/null)" ] && continue
  g add -A
  g commit -q -m "runner snapshot: $CLAUDE_RUNNER_SESSION_ID ($CLAUDE_RUNNER_EXIT_REASON)" || continue
  g push -q origin "HEAD:refs/heads/rescue/$CLAUDE_RUNNER_SESSION_ID" || true
done

hook 會使用執行器主機上其自身環境中可用的任何 git 憑證進行推送。在映像中不含憑證的做法下,包括內建複製經由 Anthropic git 代理伺服器進行時,都不會有任何憑證,因此請在推送前於 hook 內產生短期推送憑證:將 hook 在 CLAUDE_CODE_SESSION_ACCESS_TOKEN 中收到的工作階段 token 與您自己的 token 服務交換,並依照驗證工作階段身分所述進行驗證。當 hook 持有工作階段所沒有的憑證時,請將 origin 替換為由操作人員提供的 URL,並傳遞 -c credential.helper= 加上您自己的輔助程式。生命週期 hook 內的 Git 設定說明了工作階段寫入的設定仍可能影響哪些部分。

執行器釋放會話時的掛鉤計時

已釋放的會話可以在另一個執行器上恢復。在 v2.1.236 或更新版本的執行器上,會話在釋放時執行的操作決定了它是否可以在此掛鉤完成之前在另一個執行器上恢復:

  • 在轉換後閒置,或在啟動時逾時:執行器停止子程序並執行此掛鉤至完成。只有這樣它才會釋放會話。在掛鉤執行時發送的使用者訊息無法在掛鉤完成之前在另一個執行器上恢復會話。
  • 等待使用者回答提示,例如權限提示:執行器首先釋放會話,然後執行此掛鉤。在掛鉤執行時發送的使用者訊息可以在掛鉤完成之前在另一個執行器上恢復會話。

這適用於執行器釋放會話的任何時間:在閒置逾時、在 --retire-at 時間,以及 在 v2.1.260 或更新版本的執行器上,在會話的 --kill-session-after-min 限制。轉換已結束且僅持有背景任務的會話在此計為閒置。在 v2.1.236 之前,執行器在兩種情況下都首先釋放會話,然後執行此掛鉤。

在 SIGTERM 排水期間,執行器持有會話租約直到掛鉤完成;請參閱關閉計時。

生命週期 hook 內的 Git 設定

checkout 與 post-session hook 執行時,其環境中帶有工作階段的存取 token,而它們執行的 git 會讀取工作階段可寫入的設定檔,例如 ~/.gitconfig 與簽出中的 .git/config。在任一 hook 執行前,執行器會在 hook 的環境中設定 git 設定(包括下列各項),形式為 GIT_CONFIG_COUNT/GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n 配對以及 git 環境變數。Git 會將這些設定的優先順序排在所有設定檔之上,且它們只套用於您的 hook 所執行的 git,不套用於工作階段本身的 git。啟動時,執行器會印出一行 [runner:git] lifecycle hooks:,顯示目前生效的 hook 路徑、允許的協定、gpg 程式與簽署模式。需要 Claude Code v2.1.280 或更新版本。

  • Git hook:除非您提供值,否則 core.hooksPath 為 /dev/null,因此 git 會略過儲存庫 .git/hooks 中的 hook,以及 ~/.gitconfig 所指定的任何 hook 目錄。若要提供值,請在執行器的環境中將 core.hooksPath 匯出為 GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n 配對。執行器也會從系統 git 設定讀取 core.hooksPath,但僅在執行器的使用者無法寫入該檔案、其指定的目錄或其中的 hook 檔案時才使用。當執行器忽略某個值時,啟動時會有一行 [runner:warn] 指出該值與原因。
  • 檔案系統監視器:core.fsmonitor 為空值,因此您 hook 中的 git 不會執行設定檔所指定的監視器程式。
  • 遠端協定:GIT_ALLOW_PROTOCOL 為 https:http:ssh。使用本機路徑、file:// URL 或 git:// URL 的複製、擷取或推送,會以 fatal: transport 'file' not allowed 或 fatal: transport 'git' not allowed 失敗。
  • SSH 命令與憑證提示:您 hook 中的 git 會忽略設定檔中的 core.sshCommand 與 core.askPass。若要使用您自己的 SSH 命令,請在執行器的環境中設定 GIT_SSH_COMMAND。若要使用憑證提示程式,請在該處設定 GIT_ASKPASS。工作階段會繼承執行器的環境,因此這兩個變數也會傳到工作階段本身的 git。請勿在其中任一變數中放入憑證。
  • gpg 程式:gpg.program、gpg.openpgp.program、gpg.x509.program 與 gpg.ssh.program 是執行器設定的路徑,絕不會取自設定檔中的值。
  • 提交簽署:使用 --configure-git 時,您從 hook 建立的提交會以工作階段的身分簽署。未使用此旗標時,commit.gpgsign 與 tag.gpgsign 為 false。

若要變更其中某項設定,請使用執行器的環境,或在 hook 內使用 git -c 選項:

  • 設定配對:您在執行器環境中匯出的 GIT_CONFIG_KEY_n/GIT_CONFIG_VALUE_n 配對,會取代執行器對相同鍵的值。請從 0 開始為配對編號,並將 GIT_CONFIG_COUNT 設為配對數量。當計數所宣告的最後一個配對不存在時,執行器會忽略您所有的配對,並在啟動時記錄一行 [runner:warn]。
  • Git 環境變數:執行器會保留您在其環境中設定的 GIT_ALLOW_PROTOCOL、GIT_SSH_COMMAND 與 GIT_ASKPASS。
  • git -c 選項:hook 內的 git -c 選項會覆寫 GIT_CONFIG_KEY_n 配對,無論是執行器的還是您的。它不會變更 GIT_ALLOW_PROTOCOL、GIT_SSH_COMMAND 或 GIT_ASKPASS,因為 git 會先於任何設定讀取這些變數。

您 hook 中的 git 仍會從所有設定檔(包括工作階段可寫入的設定檔)讀取執行器未設定的每項設定,例如憑證輔助程式、url.*.insteadOf 重寫與篩選驅動程式。這些檔案之一所指定的憑證輔助程式或篩選驅動程式,會以您 hook 的權限作為程式執行,而這些檔案中的設定仍可改變您 hook 推送的目的地,包括推送到您在命令列上傳入的 URL。

在 v2.1.280 之前,執行器不會設定上述任何設定,且在 --configure-git 下,從 hook 建立的提交會失敗,除非 hook 傳入 -c commit.gpgsign=false。

command

在簽出後每個會話執行一次,代替內建的子程序生成。掛鉤接收與包裝指令碼相同的環境,應該以相同的方式 exec 進入 "$CLAUDE_RUNNER_CLAUDE_BIN"。使用 command 掛鉤將所有自訂保留在一個掛鉤目錄中;當包裝指令碼在其他地方時使用 --exec-path。如果也設定了 --exec-path,旗標優先,command 掛鉤被忽略。

始終 exec 執行器自己的二進位檔案,而不是 PATH 解析的 claude;否則您會破壞版本固定。

隨需啟動的執行器

您可以不使用固定的執行器群組,而是每個工作階段啟動一個執行器。協調器是一個獨立的、無狀態的子命令,它會輪詢 Anthropic 以取得啟動請求,每個沒有可用執行器的已排隊工作階段一個請求,並為每個請求執行您的 spawn-runner hook。您的 hook 會將工作負載提交到您的平台:Kubernetes Job、EC2 執行個體、Nomad dispatch。

隨需啟動的執行器改善了認證衛生。在固定群組上,環境祕密存在於每個執行器主機上,這與執行使用者工作階段的主機相同。使用協調器,環境祕密只保留在協調器主機上,該主機永遠不會執行使用者程式碼;每個啟動的執行器都會收到一個一次性工作單,它只註冊一個執行器,然後過期。

若要啟動協調器,請傳遞環境祕密和包含可執行 spawn-runner 指令碼的 hooks 目錄:

claude self-hosted-runner orchestrator \
  --environment-secret-file /etc/claude/environment-secret \
  --hooks-dir /etc/claude/hooks

協調器在輪詢之間不保留任何狀態,因此您可以針對同一環境執行兩個或多個副本以實現可用性。每個啟動請求由伺服器端的恰好一個副本聲稱。所有副本必須使用相同的 --expected-spawn-seconds 值;請參閱 hook 合約。

spawn-runner hook

協調器為每個啟動請求執行一次 ${hooks-dir}/spawn-runner。hook 必須非同步提交工作,不等待執行器啟動,並在 --hook-timeout 內返回,預設為 60 秒。hook 接收:

變數 說明
CLAUDE_RUNNER_WORK_ORDER_FILE 包含已簽署工作單 JWT 的暫存檔案路徑,新執行器使用此 JWT 進行註冊。hook 退出後刪除。不要記錄檔案的內容。
CLAUDE_RUNNER_ORDER_ID 不透明的冪等性金鑰,每個啟動請求唯一,對 Kubernetes 資源名稱安全。將其用作您的佈建程式的去重金鑰。
CLAUDE_RUNNER_SESSION_ID 此請求所針對的工作階段。它在每次重新請求工作階段時重複,因此將其用於記錄和路由,而不是作為去重金鑰。對於預熱請求為空,預熱請求會在設定 --min-idle 時在任何特定工作階段之前啟動待命執行器,因此不要假設變數已設定。
CLAUDE_RUNNER_SESSION_UUID 相同的工作階段 ID,採用規範 UUID 形式。對於預熱請求為空。
CLAUDE_RUNNER_ATTEMPT 此工作階段已有多少個啟動請求。對於預熱請求為 0。
CLAUDE_RUNNER_ORDER_SERVER_TIME 來自輪詢回應的 HTTP Date 標頭的伺服器時間。當 hook 驗證工作單 JWT 的 exp 時,請與此值進行比較,而不是本地時鐘,以容許時間偏差。當閘道省略標頭時為空。
CLAUDE_RUNNER_POOL_ID 新執行器應加入的環境的 ID,採用 ccpool_... 形式
CLAUDE_RUNNER_ACCOUNT_ID 排隊工作階段的帳戶的標記 ID,用於按帳戶路由、配額或退款。不可用時為空,Claude Tag 頻道工作階段始終為空,這些工作階段沒有帳戶排隊。
CLAUDE_RUNNER_ACCOUNT_EMAIL 排隊工作階段的帳戶的電子郵件。不可用時為空。將電子郵件視為個人可識別資訊,不要記錄它。
CLAUDE_RUNNER_PRIMARY_REPO_URL 工作階段的第一個 git 來源的 URL,用於路由到預先準備了該儲存庫的執行器。工作階段沒有 git 來源時為空。
CLAUDE_RUNNER_PRIMARY_REPO_REVISION 工作階段的第一個 git 來源的修訂版本:分支、SHA 或標籤。未指定時為空。
CLAUDE_RUNNER_REPO_SOURCES 所有工作階段的 git 來源的 {url, revision} 的 JSON 陣列,用於在次要儲存庫上路由的 hook。沒有來源時為空。
CLAUDE_RUNNER_CORRELATION_ID 在工作階段建立時提供的相關 ID,回顯以便 hook 可以將此工作單對應到建立工作階段的請求。工作階段沒有時為空。
CLAUDE_RUNNER_CLIENT_PLATFORM 建立工作階段的用戶端表面,例如 web_claude_ai、desktop_app、ios 或 scheduled_trigger,用於採用分析。當工作階段沒有記錄或識別的表面時未設定,對於預熱請求也未設定;使用 [ -n "${CLAUDE_RUNNER_CLIENT_PLATFORM:-}" ] 檢查它,在 set -u 下保持安全。

啟動的執行器使用工作單代替環境祕密進行註冊:

  • 使用工作單啟動它:將 --environment-secret-file 指向包含工作單 JWT 的檔案,或將 SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET 設定為 JWT 值。
  • 在 hook 退出前複製 JWT:協調器在 hook 退出後刪除工作單檔案,因此將 JWT 複製到您提交的工作負載中,例如啟動的 Job 上的 Kubernetes Secret,而不是透過檔案路徑。
  • 在啟動的執行器上使用 --capacity 1:工作階段綁定的工作單恰好註冊一個綁定到該工作階段的執行器,因此更高的容量會新增永遠不會接收工作的插槽,執行器在啟動時會記錄警告。
  • 預熱工作單註冊未綁定:待命執行器未綁定到工作階段,並像固定群組執行器一樣聲稱已排隊的工作。

合約有四個佈建程式無關的規則:

  1. 在 CLAUDE_RUNNER_ORDER_ID 上保持冪等性。 重新傳遞相同的請求最多必須啟動一個執行器。從 ID 衍生確定性資源名稱,並讓您的平台拒絕重複項。不要改為在 CLAUDE_RUNNER_SESSION_ID 上進行金鑰設定。每次重新請求工作階段都會使用相同的工作階段 ID 和新的訂單 ID,因此按工作階段 ID 命名或去重的工作負載會為該工作階段建立一次,之後永遠不會再建立。
  2. 不要重試工作負載。 一個訂單 ID 最多意味著建立一個工作負載。如果執行器永遠不註冊,Anthropic 會在 --expected-spawn-seconds 後使用新的訂單 ID 重新請求。
  3. 使用退出代碼合約。 退出 0 表示已提交。退出 1 表示可重試的失敗;工作階段退避並被重新提供。退出 2 或更高表示不可重試;工作階段被阻止再次啟動,直到 Owner 在環境的 Activity 標籤中選擇 Retry。在非零退出時,hook 的 stderr 的尾部會作為失敗原因出現在那裡,因此將可操作的錯誤寫入 stderr,永遠不要寫入祕密。對於預熱請求,沒有工作階段失敗:協調器只在本地記錄非零退出,伺服器在租約後重新請求啟動。
  4. 將 --expected-spawn-seconds 設定為至少您的 p99 啟動時間。 這是伺服器端租約。所有協調器副本必須使用相同的值。

hook 寫入 stdout 或 stderr 的所有內容都會出現在協調器的日誌中,認證會自動編輯。如果工作階段保持排隊,請檢查協調器的 /healthz 主體以取得佇列計數,然後在 Cloud environments 管理頁面 上開啟您環境的 Activity 標籤:在那裡展開失敗的工作階段以查看其啟動錯誤,並選擇 Retry 以重新請求它。

保持排隊且在 Activity 標籤中沒有啟動錯誤的工作階段可能意味著 hook 是在工作階段 ID 上進行金鑰設定的。若要確認,請檢查您的平台是否有該工作階段的第一個啟動請求的工作負載,以及沒有重新請求的工作負載。如果是這樣,請改為在 CLAUDE_RUNNER_ORDER_ID 上進行工作負載金鑰設定。

MCP 伺服器

要在每個會話中提供 MCP 伺服器,請在映像構建時使用與桌面安裝上使用的相同 claude mcp add 命令添加它們。如果您的執行器是裸程序而不是容器,請在主機上以執行器的使用者身份執行相同的命令,然後重新啟動執行器:它在啟動時讀取主機設定一次。--scope user 旗標是必需的;預設本地範圍寫入執行器不植入會話的每個目錄鍵下。例如,在您的 Dockerfile 中:

RUN claude mcp add --scope user sidecar -- /usr/local/bin/mcp-sidecar
RUN claude mcp add --scope user --transport http internal http://mcp-gateway.svc.cluster.local:8080

執行器在啟動時快照主機的設定一次。快照從主機的 .claude.json 擷取 mcpServers 鍵,該鍵位於 ~/.claude/ 旁邊而不是內部,執行器僅將該鍵植入每個會話的隔離設定;帳戶狀態和專案歷史被丟棄。要確認伺服器到達會話,請在環境上啟動會話並要求 Claude 列出其 MCP 工具;執行器也會為任何擷取的條目記錄啟動警告,其 type 它無法識別並丟棄該條目,因此您可以看到為什麼該伺服器在會話中缺失。當設定 SELF_HOSTED_RUNNER_HOST_CONFIG_DIR 時,執行器改為從該目錄讀取 .claude.json,因此將變數指向空目錄也會禁用 MCP 植入。

Claude Code 也從其他來源載入 MCP 伺服器:

  • 企業範圍受管 MCP 檔案在其標準系統路徑:Linux 執行器主機上的 /etc/claude-code/managed-mcp.json,macOS 主機上的 /Library/Application Support/ClaudeCode/managed-mcp.json。將其用於鎖定的艦隊,其中只有管理員列出的伺服器可能載入。請參閱使用 managed-mcp.json 的獨佔控制以了解優先順序規則。當此檔案在執行器主機上時,Claude Code 跳過 Anthropic 的控制平面傳遞給會話的 MCP 伺服器,包括 claude.ai 連接器,並在會話子程序的 stderr 上命名它們,執行器在 debug 日誌級別記錄。在 v2.1.229 之前,這些會話在啟動時以 You cannot dynamically configure MCP servers when an enterprise MCP config is present 退出。
  • 執行器主機上受管設定中的 managedMcpServers 鍵:提供 HTTP 和 SSE 伺服器而不取得獨佔控制,因此來自其他來源的伺服器仍然載入。需要 Claude Code v2.1.259 或更新版本。
  • <repo>/.mcp.json:專案範圍。將檔案提交到儲存庫;其伺服器在雲端會話中自動核准。

當為您的組織啟用連接器傳遞時,Anthropic 的控制平面將您在 claude.ai 上設定的連接器傳遞給透過伺服器提供的 MCP 設定路由的互動建立的會話,通過 api.anthropic.com。以程式設計方式建立的會話(例如 CLI 分派)不接收連接器傳遞;改為透過本部分列出的其他來源之一為它們提供 MCP 伺服器。子程序的 OAuth 權杖不帶有直接擷取連接器的範圍,因此子程序不會自行嘗試該擷取;傳遞是伺服器驅動的。

settings.json 不帶有 MCP 伺服器定義,設定架構中沒有頂級 mcpServers 欄位。在受管設定中,使用 managedMcpServers 鍵提供伺服器。

會話繼承執行器的環境,因此在那裡設定 ENABLE_TOOL_SEARCH 以控制執行器生成的每個會話的 MCP 工具搜尋;MCP 頁面涵蓋了這些值。

關閉內建會話工具

Anthropic 的控制平面將其自己的 MCP 伺服器(名為 Claude Code Remote)附加到雲端會話。Claude 使用伺服器的工具來排程例行程序、啟動和引導其他雲端會話、附加更多儲存庫,以及追蹤拉取請求活動。

要關閉整個伺服器,請在您的設定中添加伺服器級別拒絕規則。控制平面根據會話的建立方式在三個名稱之一下註冊伺服器。Claude Code 在規則中完全匹配名稱,包括大小寫,因此請按如下所示為每個名稱寫一次規則:

{
  "permissions": {
    "deny": [
      "mcp__Claude_Code_Remote",
      "mcp__claude-code-remote",
      "mcp__bf7c680d-5fdc-5ef4-b4a0-abadb619bf0a"
    ]
  }
}

命名整個伺服器的規則也涵蓋伺服器稍後獲得的工具。要關閉一個工具並保留其餘工具,請在每個規則後附加兩個以上的下劃線和工具名稱,如 mcp__Claude_Code_Remote__add_repo。要阻止伺服器連接而不是移除其工具,請改為在 deniedMcpServers 下添加三個名稱(不帶 mcp__ 前綴)作為 serverName 條目。

將規則放在伺服器管理的設定中以到達每個會話而無需更改執行器,或在執行器上的 ~/.claude/settings.json 中。權限和工具核准說明執行器上的設定如何到達會話。

要確認規則生效,請在環境上啟動會話並要求 Claude 列出其 MCP 工具。Claude Code 從 Claude 的上下文中移除被拒絕的工具,因此被拒絕的工具在其答案中缺失。

提示會話推送其工作

Anthropic 託管的會話執行 Stop 掛鉤,Claude Code 掛鉤在 Claude 完成回應時執行,提示 Claude 提交並推送其工作。執行器不安裝一個。沒有它,以未提交變更結束的會話只在執行器的磁碟上留下該工作,claude.ai/code 中的建立 PR 按鈕保持非活動狀態,直到分支存在於遠端。

下面的參考實現有兩個部分。將設定塊合併到執行器主機上的 ~/.claude/settings.json 中,執行器將其植入每個會話,並將指令碼保存為執行器主機上的 ~/.claude/hooks/stop-hook-nudge.sh 並使其可執行:

{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "timeout": 10,
            "command": "\"$CLAUDE_CONFIG_DIR/hooks/stop-hook-nudge.sh\""
          }
        ]
      }
    ]
  }
}
#!/bin/sh
# Stop-hook reference implementation for self-hosted runners.
#
# Nudges Claude once per turn if the project directory has uncommitted
# changes OR unpushed commits, so work isn't lost when an idle session
# is released and so the "Create PR" button on claude.ai/code lights up.
#
# Runner-level (no repo changes): drop this file at ~/.claude/hooks/ on
# the runner host and merge the accompanying Stop-hook settings block
# into ~/.claude/settings.json — the runner seeds both into every session.
# Repo-level alternative: commit to <repo>/.claude/hooks/ and change the
# settings.json command path to $CLAUDE_PROJECT_DIR/.claude/hooks/.
#
# stdin: hook JSON payload (see https://code.claude.com/docs/en/hooks)
# stdout: {"decision":"block","reason":"..."} to nudge, or nothing to allow stop.

# Re-entry guard: the harness sets stop_hook_active=true when re-invoking
# the Stop hook after a block. Bail so we only nudge once per turn. The
# harness emits compact JSON (no space after the colon), which this
# pattern relies on; use jq if you need a whitespace-tolerant check.
in=$(cat)
case "$in" in *'"stop_hook_active":true'*) exit 0 ;; esac

d="$CLAUDE_PROJECT_DIR"

# Not a git repo → nothing to nudge.
git -C "$d" rev-parse --git-dir >/dev/null 2>&1 || exit 0

# No remote → "push to the remote" is unsatisfiable; bail.
[ -z "$(git -C "$d" remote 2>/dev/null)" ] && exit 0

# Uncommitted changes (staged, unstaged, or untracked). Exclude .claude/
# entirely — operator-seeded settings and CLI-written runtime state
# (scheduler lock, worktrees, routine state) live there and neither is
# "uncommitted work" the model needs to push.
s=$(git -C "$d" status --porcelain -- . ':(exclude).claude/' 2>/dev/null)
if [ -n "$s" ]; then
  printf '{"decision":"block","reason":"There are uncommitted changes in the repository. Please commit and push these changes to the remote branch."}'
  exit 0
fi

# Unpushed commits. Count commits on HEAD not reachable from any
# remote-tracking ref or FETCH_HEAD. This works uniformly for:
#   - init+fetch checkouts (runner default: only FETCH_HEAD exists)
#   - clone-based checkouts (origin/* exist)
#   - the runner default: the child starts on the session's outcome
#     branch, which the runner creates after checkout
#   - detached HEAD, when a custom setup skips that branch creation
# With no reference point at all (never fetched), stay silent rather
# than false-positive on a read-only turn.
base=""
git -C "$d" rev-parse --verify -q FETCH_HEAD >/dev/null && base="FETCH_HEAD"
if [ -z "$base" ] && [ -z "$(git -C "$d" for-each-ref --count=1 refs/remotes/origin 2>/dev/null)" ]; then
  exit 0
fi
# shellcheck disable=SC2086  # $base is either "" or "FETCH_HEAD", intentional word-split
unpushed=$(git -C "$d" rev-list HEAD --not $base --remotes=origin --count 2>/dev/null) || unpushed=0
if [ "$unpushed" -gt 0 ]; then
  branch=$(git -C "$d" symbolic-ref --short -q HEAD)
  if [ -n "$branch" ]; then
    # $branch is attacker-influenced — git-check-ref-format(1) allows `"`
    # in ref names. `\` is forbidden (rule 10) but escaped anyway as cheap
    # defense-in-depth.
    # Escape JSON metacharacters before interpolating into the hand-built
    # payload so a branch like x","continue":false can't inject keys into
    # the hook-output JSON the harness parses. $unpushed is safe — the
    # -gt guard above rejects anything that isn't a plain integer.
    branch_esc=$(printf '%s' "$branch" | sed 's/\\/\\\\/g; s/"/\\"/g')
    printf '{"decision":"block","reason":"There are %s unpushed commit(s) on branch '\''%s'\''. Please push these changes to the remote repository."}' "$unpushed" "$branch_esc"
  else
    printf '{"decision":"block","reason":"There are %s unpushed commit(s) on a detached HEAD. Please create a branch and push it to the remote repository."}' "$unpushed"
  fi
  exit 0
fi

exit 0

掛鉤在會話結束前提示 Claude 提交並推送,當目錄不是 git 儲存庫或沒有遠端時保持沉默。

權限和工具核准

自託管工作階段沒有連接的終端機,因此未回應的權限提示會使回合停滯,直到使用者在 UI 中回應。Anthropic 的控制平面會隨工作 payload 傳送每個工作階段的工具清單和權限規則;預設設定會預先核准例行的工具呼叫(包括 Bash),而雲端工作階段無論模式為何都會預先核准檔案編輯。未被任何規則預先核准的呼叫會透過工作階段 UI 提示。

若要無論控制平面傳送什麼都將提示降到最低,請從您的包裝指令碼或 command hook 固定自動模式。自動模式讓工作階段無需例行權限提示即可執行:一個獨立的分類器模型會在操作執行前進行審查,並阻擋其拒絕的操作,而明確的詢問規則仍會強制提示;權限模式頁面說明了分類器檢查的內容。執行器在呼叫包裝指令碼前會附加伺服器計算的旗標,而對於單值旗標(例如 --permission-mode),解析器會採用最後一次出現的值,因此您在 "$@" 之後附加的旗標會覆寫伺服器傳送的值:

#!/bin/bash
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --permission-mode auto

若要改為預先核准特定工具,請附加 --allowed-tools 及您的規則,例如 --allowed-tools "Bash(bazel *) Bash(yarn *) mcp__internal__*"。清單旗標(例如 --allowed-tools 和 --disallowed-tools)會在多次出現時累加,而非覆寫,因此您的規則會套用在控制平面傳送的任何規則之上。若要縮小範圍,請附加 --disallowed-tools,即使其他規則允許某工具,它也會拒絕該工具。

如何組合每個工作階段的設定

執行器為每個工作階段提供其專屬的設定目錄,並以執行器在啟動時擷取一次的主機 ~/.claude/ 快照植入:您執行器映像中的 settings.json、CLAUDE.md、hook、agent、命令和 skill 會作為使用者層級基準套用至每個工作階段。如果您在執行中的主機上變更設定,變更僅在您重新啟動執行器後生效。

設定 SELF_HOSTED_RUNNER_HOST_CONFIG_DIR 以從不同路徑植入,或將其指向空目錄以停用植入。

儲存庫提交的 .claude/settings.json 會作為專案設定疊加於其上。工作階段也會從執行器映像中的標準系統路徑讀取 managed-settings.json。其設定鍵是否與伺服器受管設定一併套用,取決於 Claude Code 如何組合受管來源:預設情況下,當您的組織傳遞任何伺服器受管設定鍵時,工作階段會忽略執行器映像的檔案,但 Claude Code 從每個管理來源讀取的設定鍵除外,例如 env 區塊、沙箱鎖定、沙箱二進位檔路徑和 forceRemoteSettingsRefresh。請參閱設定優先順序。

當 Anthropic 的控制平面為工作階段提供 Claude Code hook 時,執行器會將其與您自己的設定並存安裝,而非覆寫您的設定。需要 Claude Code v2.1.229 或更新版本。

  • 放置位置:執行器會將每個提供的 hook 指令碼寫入工作階段設定目錄中保留的 hooks/.ccr-launcher/ 子目錄,並在一個獨立的設定檔中註冊這些指令碼,再透過 --settings 將該檔案傳遞給工作階段,而植入的 settings.json 和您位於 hooks/<name> 的指令碼則保持不變。執行器會為每個工作階段重新建立該保留子目錄,且不會將主機上 ~/.claude/hooks/.ccr-launcher/ 的內容植入工作階段。
  • 撰寫者:控制平面以其自身部署中的固定常數填入這些指令碼,絕不來自個別工作階段或第三方輸入。
  • 仍受何者管控:透過 --settings 傳遞的 hook 會進入一般的合併 hook 設定,而非受管層級,因此您的受管設定仍然適用。disableAllHooks 會停用它們,且它們不屬於 allowManagedHooksOnly 保持載入的類別。

在 Claude Tag 工作階段以外,自託管環境中的工作階段預設會關閉自動記憶。若有應跨工作階段延續的指令,請使用執行器映像或儲存庫中的 CLAUDE.md。

執行器對主機 ~/.claude/ 的快照不包含 projects/ 目錄。自動記憶的預設儲存位置就位於該目錄下。如果您將記憶檔案放在那裡,執行器不會將其植入工作階段,這些檔案也不會開啟自動記憶。

儲存庫提交的權限規則

請勿在儲存庫提交的 permissions.allow 中放入單獨的 "Edit"、"Write" 或 "NotebookEdit" 項目。單獨的檔案工具規則會不論路徑地比對該工具,授予主機上任何位置的寫入權,而不僅限於工作區,因此執行器的寫入範圍限制防護會對該工作階段發出警示;若使用 --confine-repo-settings enforce,它會拒絕啟動該工作階段,而非記錄後繼續。請參閱強化部分。

儲存庫完全不需要檔案工具規則:雲端工作階段無論模式為何都會預先核准檔案編輯。如果您確實提交了規則,請將其範圍限定於工作區,例如 "Edit(/**)";單一前導斜線是相對於專案根目錄,也就是工作階段的工作區。單獨的檔案工具規則可以放在操作員的主機層級 settings.json 中,因為該檔案並非由儲存庫提交。

defaultMode 為 auto 的設定僅在映像層級或使用者層級設定檔中才會生效,因此簽出的儲存庫無法自行授予自動模式。有關雲端工作階段接受的模式及完整規則語法,請參閱權限模式。

接下來

  • 參考:每個 CLI 旗標、環境變數和度量
  • 驗證會話身份:驗證來自執行器外部服務的會話權杖