SpyBara
Go Premium

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

This page contains 193 additions and 55 deletions.

2026
Fri 2 22:59

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

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

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

本頁面上的一些 hook 環境變數仍然使用 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 上進行工作負載金鑰設定。

將模型請求傳送至 Bedrock 或 Agent Platform

如果您的組織需要讓模型請求經過自己的 AWS 或 Google Cloud 帳戶,請為 runner 設定 Amazon Bedrock 或 Google Cloud 的 Agent Platform(前身為 Vertex AI)。之後該 runner 啟動的每個工作階段都會以您的雲端憑證,在您的雲端帳戶中呼叫模型。若未進行此設定,工作階段會將模型請求傳送至 Anthropic API。

Runner 仍會向 Anthropic 輪詢工作階段,且每個工作階段仍會將其事件串流傳送至 api.anthropic.com。事件串流包含提示詞、回應和工具結果。可用性與限制中的方案要求和 Zero Data Retention 排除條款仍然適用。

工作階段是路由至環境,而非路由至 runner,且重新排入佇列或恢復的工作階段可能會在不同的 runner 上執行。請以相同方式設定環境中的每個 runner。開始之前,請閱讀這些供應商上有何不同。

1

準備雲端帳戶和輸出規則

設定模型存取權、範圍嚴格限定的政策或角色,以及網路存取:

  • Amazon Bedrock:提交使用案例詳細資訊,然後建立 IAM 設定中的政策,將 bedrock:InvokeModel 和 bedrock:InvokeModelWithResponseStream 限制為您的工作階段所使用的推論設定檔及其背後的基礎模型
  • Agent Platform:啟用 API 並請求模型存取權,然後建立 IAM 設定所描述的自訂角色,僅包含 aiplatform.endpoints.predict
  • 輸出:在您的輸出規則中允許供應商的端點。請參閱網路需求。如果工作階段無法連線至這些端點,Claude Code 可能會持續重試數小時,工作階段才會顯示錯誤。
2

為工作階段提供範圍嚴格限定的憑證

將步驟 1 中的政策或角色附加至一個無法執行其他任何操作的身分。關於 Claude Code 接受的方法,請參閱設定 AWS 憑證和設定 GCP 憑證。

請根據以下 runner 行為檢查您選擇的方法:

  • 中繼資料端點:如果您完全拒絕工作階段存取雲端中繼資料端點,由其提供的憑證(例如執行個體設定檔)也不會傳達至 Claude Code。以檔案為基礎的 Web 身分,例如 Amazon EKS 上的 IAM Roles for Service Accounts (IRSA) 或 Workload Identity Federation 憑證檔案,則不依賴該端點。
  • 續期:工作階段的存續時間可能比憑證更長,因此請使用會自行續期的方法,例如以檔案為基礎的 Web 身分
  • 包裝腳本:Runner 會在每個工作階段啟動一次您的包裝腳本,因此它匯出的憑證不會續期。Claude Code 會從其環境讀取 AWS 憑證,因此如果您的包裝腳本已為其他工作匯出 AWS 憑證,Claude Code 可能會使用這些憑證來簽署模型請求。
3

在 runner 的環境中設定單一供應商的變數

在您設定 runner 其他環境變數的地方(例如容器規格或服務單元)設定恰好一個供應商的變數,然後重新啟動 runner。範例以 shell 匯出的形式呈現。若使用隨需 runner,請在您的 spawn-runner hook 所啟動的工作負載上設定這些變數。

請使用 --confine-repo-settings enforce 啟動這些 runner。它會拒絕在已提交設定遭其標記的儲存庫上執行工作階段,因此請先以預設的 warn 模式執行,並清除它在日誌中記錄的項目。

將區域替換為您自己的區域:

export CLAUDE_CODE_USE_BEDROCK=1
export AWS_REGION=us-east-1

關於 Claude Code 如何解析區域,請參閱設定 Claude Code。關於 Claude Code 針對您的區域使用哪個推論設定檔前綴,請參閱跨區域推論設定檔前綴。

4

檢查變數是否已傳達至工作階段

您在主機上自己的 shell 是不同的程序,因此請從工作階段內部檢查。在該環境中啟動一個工作階段,並請 Claude 執行此命令:

env | grep -E 'CLAUDE_CODE_USE_(BEDROCK|VERTEX)'

若有一行將 CLAUDE_CODE_USE_BEDROCK 或 CLAUDE_CODE_USE_VERTEX 設為 1,表示該變數已傳達至工作階段。如果兩者都出現,Claude Code 會使用 Amazon Bedrock。沒有輸出則表示兩者皆未傳達。

此命令顯示的是設定,而非流量。若要確認請求本身,請在您雲端帳戶自己的指標或請求日誌中查看。如果第一則訊息反而失敗,請參閱 Amazon Bedrock 或 Agent Platform 的疑難排解。

與 Anthropic API 上的工作階段有何不同

將模型請求傳送至 Amazon Bedrock 或 Google Cloud 的 Agent Platform 的工作階段,與 Anthropic API 上的工作階段有以下差異:

  • 來自 claude.ai 的政策:伺服器管理設定不會傳達至這些工作階段。Owner 在 Claude Code 管理設定中設定的組織政策也不會傳達,因此 Claude Code 不會在工作階段內強制執行這些政策。請將您所依賴的規則放入 runner 映像檔的受管設定檔案中。
  • 檔案:使用者在 claude.ai 或行動裝置或桌面應用程式中附加至工作階段的檔案不會傳達至工作階段,且 Claude 無法使用 SendUserFile 工具回傳檔案。請改將輸入檔案放在儲存庫中或 runner 上。
  • 模型選擇:Anthropic 的控制平面會傳送每個工作階段的模型,當工作階段在沒有指定模型的情況下啟動時,Claude Code 會使用該供應商的預設模型。Runner 會從其傳遞給工作階段的環境中移除 ANTHROPIC_MODEL 和 ANTHROPIC_DEFAULT_MODEL。供應商頁面的範例會設定 ANTHROPIC_MODEL,但在 runner 的環境中,這兩個變數都沒有任何作用。Amazon Bedrock 和 Agent Platform 的「固定模型版本」中的各模型系列變數確實會傳達至工作階段。這些變數決定的是 opus 等別名解析為哪個模型,而非完整模型 ID 解析為哪個模型。
  • 您的帳戶未提供的模型:工作階段可能會在某則訊息上失敗,並出現指出該模型名稱的錯誤。請啟用您的開發人員可以選擇的模型、「固定模型版本」中所述的背景模型,以及自動模式所使用的分類器模型。在 Amazon Bedrock 上,請在您的政策中允許其中每一個模型。
  • 網路搜尋和快速模式:網路搜尋在 Amazon Bedrock 上無法使用,而快速模式在兩個供應商上都無法使用。關於其他因供應商而異的功能,請參閱因供應商而異的 CLI 功能。

MCP 伺服器

若要讓 MCP 伺服器在每個工作階段中都可用,請在映像建置時使用與桌面安裝相同的 claude mcp add 命令加入它們。如果您的 runner 是裸程序而非容器,請以 runner 的使用者身分在主機上執行相同的命令,然後重新啟動 runner:它只會在啟動時讀取一次主機設定。--scope user 旗標是必要的;預設的 local 範圍會寫入以各目錄為鍵的位置,而 runner 不會將其植入工作階段。例如,在您的 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

runner 會在啟動時對主機的設定建立一次快照。快照會擷取主機 .claude.json 中的 mcpServers 鍵(該檔案位於 ~/.claude/ 旁邊,而非其內部),且 runner 只會將該鍵植入每個工作階段的隔離設定中;帳戶狀態和專案歷史記錄會被捨棄。若要確認伺服器已傳達到工作階段,請在該環境上啟動一個工作階段,並請 Claude 列出其 MCP 工具;對於任何 type 無法辨識的已擷取項目,runner 也會在啟動時記錄警告並捨棄該項目,讓您能看出該伺服器為何未出現在工作階段中。當設定了 SELF_HOSTED_RUNNER_HOST_CONFIG_DIR 時,runner 會改為從該目錄讀取 .claude.json,因此將該變數指向空目錄也會停用 MCP 植入。

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

  • 位於標準系統路徑的企業範圍受管 MCP 檔案:Linux runner 主機上為 /etc/claude-code/managed-mcp.json,macOS 主機上為 /Library/Application Support/ClaudeCode/managed-mcp.json。適用於僅允許載入管理員所列伺服器的鎖定機群。優先順序規則請參閱使用 managed-mcp.json 進行獨占控制。當此檔案存在於 runner 主機上時,Claude Code 會略過 Anthropic 控制平面傳遞給工作階段的 MCP 伺服器(包括 claude.ai 連接器),並在工作階段子程序的 stderr 上以警告列出它們,runner 會以 debug 日誌層級記錄這些警告。在 v2.1.229 之前,這些工作階段會在啟動時以 You cannot dynamically configure MCP servers when an enterprise MCP config is present 結束。
  • runner 主機上受管設定中的 managedMcpServers 鍵:提供 HTTP 和 SSE 伺服器而不取得獨占控制,因此來自其他來源的伺服器仍會載入。需要 Claude Code v2.1.259 或更新版本。
  • <repo>/.mcp.json:專案範圍。將此檔案提交到儲存庫;其伺服器在雲端工作階段中會自動核准。

當您的組織啟用連接器傳遞時,Anthropic 的控制平面會透過伺服器提供的 MCP 設定,將您在 claude.ai 上設定的連接器傳遞給以互動方式建立的工作階段,並經由 api.anthropic.com 路由。以程式化方式建立的工作階段(例如 CLI 派送)不會接收連接器傳遞;請改為透過本節列出的任何其他來源為它們提供 MCP 伺服器。子程序的 OAuth token 不具備直接擷取連接器的範圍,因此子程序本身不會嘗試該擷取;傳遞是由伺服器驅動的。

settings.json 不包含 MCP 伺服器定義,且設定 schema 中沒有頂層的 mcpServers 欄位。在受管設定中,請改用 managedMcpServers 鍵來提供伺服器。

工作階段會繼承 runner 的環境,因此請在該處設定 ENABLE_TOOL_SEARCH,以控制 runner 產生的每個工作階段的 MCP 工具搜尋;各個值的說明請參閱 MCP 頁面。

關閉內建工作階段工具

Anthropic 的控制平面會將其自有的 MCP 伺服器(名為 Claude Code Remote)附加到雲端工作階段。Claude 使用該伺服器的工具來排程 routine、啟動並引導其他雲端工作階段、附加更多儲存庫,以及追蹤 pull request 活動。

若要關閉整個伺服器,請在您的設定中新增伺服器層級的拒絕規則。控制平面會依工作階段的建立方式,以三個名稱之一註冊該伺服器。Claude Code 會完全比對規則中的名稱(包括大小寫),因此請如下所示為每個名稱各寫一條規則:

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

指名整個伺服器的規則也會涵蓋該伺服器日後新增的工具。若要關閉單一工具並保留其餘工具,請在每條規則後附加兩個底線和工具名稱,例如 mcp__Claude_Code_Remote__add_repo。若要完全阻止伺服器連線,而不只是移除其工具,請改為將這三個名稱(不含 mcp__ 前綴)作為 serverName 項目加入 deniedMcpServers 下。

將規則放在伺服器受管設定中,即可在不變更 runner 的情況下套用到工作階段,或放在 runner 上的 ~/.claude/settings.json 中。在將模型請求傳送至 Bedrock 或 Agent Platform 的 runner 上,請使用該檔案,因為伺服器受管設定不會套用到這些工作階段。權限與工具核准說明了 runner 上的設定如何套用到工作階段。

若要確認規則已生效,請在該環境上啟動一個工作階段,並請 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 旗標、環境變數和度量
  • 驗證會話身份:驗證來自執行器外部服務的會話權杖