셀프호스티드 환경 레퍼런스
기준일: 2026-10-23
공식 기준: Self-hosted environments reference
개요
셀프호스티드 환경에서 실행하는 두 프로세스의 레퍼런스입니다. 러너(호스트에서 클라우드 세션 실행)와 선택 사항인 autoscaling orchestrator(세션이 큐에 쌓이면 러너 시작). 둘 다 Linux/macOS 호스트 기준(/workspace, ~/.claude 기본값). 설치된 버전의 정확한 목록은 claude self-hosted-runner --help로 확인하세요.
메트릭 시리즈와 일부 API 필드는 환경을 아직 pool로 부릅니다. 환경 ID는 pool_id(ccpool_... 형태)이고 CLI 플래그/환경 변수는 environment로 표기합니다(deprecated pool 표기도 동작).
핵심 개념
| 항목 | 요약 |
|---|---|
| 플래그 vs 환경 변수 | 둘 다 설정되면 플래그 우선 |
| 시간 단위 | CLI는 분 또는 초, 짝이 되는 환경 변수는 항상 밀리초(_MS). --exit-if-unused-min 10 = SELF_HOSTED_RUNNER_IDLE_SHUTDOWN_MS=600000. Helm의 SELF_HOSTED_RUNNER_STARTUP_TIMEOUT_MS: "15"는 15밀리초이지 기본 15분이 아님 |
| 상한 | 대부분의 시간 플래그는 32비트 타이머 한계(약 24.85일) 내로 제한. --*-min은 10080분(7일), --drain-grace-sec는 604800초(7일), --drain-wait-sec는 86400초(24시간). --session-stop-grace-sec, --post-session-hook-timeout-sec는 상한 없음. 초과 시 플래그는 시작 실패, 환경 변수는 한계로 clamp |
| health/metrics | 러너와 orchestrator 모두 health 포트(기본 8080)에서 /healthz, /metrics 제공 |
상세
러너 CLI 플래그
| 플래그 | 환경 변수 | 기본값 | 설명 |
|---|---|---|---|
--api-url <url> |
없음 | https://api.anthropic.com |
API base URL. 테스트용 |
--base-dir <path> |
SELF_HOSTED_RUNNER_BASE_DIR |
/workspace, Windows는 없음 |
checkout·세션별 작업 디렉터리. 쓰기 권한 필요, 시작 시 생성하고 불가하면 cannot create or write to base directory로 종료(v2.1.225 이전에는 첫 세션 시작 시 생성). Windows는 기본값이 없어 플래그/변수 필수. 환경 내 모든 러너가 같은 값 사용 |
--capacity <n> |
없음 | 1 |
최대 동시 세션 수(모두 같은 locked owner). 환경 내 모든 러너 동일 값 |
--client-label <label> |
SELF_HOSTED_RUNNER_CLIENT_LABEL |
호스트명 | 등록 라벨, claude_code_self_hosted_runner_info의 client_label. 2.1.248+ |
--configure-git |
SELF_HOSTED_RUNNER_CONFIGURE_GIT=1 |
off | 시작 시 전역 git identity, Anthropic 커밋 서명, push negotiation(2.1.257+), Co-authored-by: 트레일러 hook 설치 |
--confine-repo-settings <mode> |
SELF_HOSTED_RUNNER_CONFINE_REPO_SETTINGS |
warn |
저장소 커밋 설정이 세션 워크스페이스 밖 읽기/쓰기 grant, 환경 변수, sandbox.enabled: false/disableAllHooks 같은 sandbox·hook 자세 override를 시도하는지 검사하는 guard. warn은 로그 후 시작, enforce는 세션 거부, off는 비활성 |
--debug-token-dir <path> |
SELF_HOSTED_RUNNER_DEBUG_TOKEN_DIR |
unset | 라이브 토큰을 디스크에 기록(디버그 전용, 프로덕션 금지) |
--defer-shutdown-max-min <n> |
SELF_HOSTED_RUNNER_DEFER_SHUTDOWN_MAX_MS |
0 |
첫 SIGTERM/SIGINT에서 드레인 대신 붙은 세션을 계속 서빙하다 N분 뒤 남은 것을 해제하고 종료. 설정 전 호스트 stop timeout 상향. 0은 비활성. 2.1.238+ |
--drain-grace-sec <n> |
SELF_HOSTED_RUNNER_DRAIN_GRACE_MS |
0 |
종료 시그널/retire 시각 전, 활성 세션이 끝난 뒤 러너가 언제 종료할지. 0은 즉시 종료, 양수는 잠긴 owner 큐를 그 초만큼 재폴링(세션별 컨테이너 격리 약화). --defer-shutdown-max-min으로 연기한 첫 시그널 이후엔 세션이 없으면 값과 무관하게 즉시 종료 |
--drain-marker-file <path> |
SELF_HOSTED_RUNNER_DRAIN_MARKER_FILE |
unset | 호스트가 SIGTERM 전에 graceful drain을 알리려고 쓰는 마커 파일. 드레인 시작 시 존재하면 종료를 host drain으로 보고(텔레메트리 전용, 드레인 동작은 동일). 세션이 쓸 수 없는 로컬 파일시스템 경로. 2.1.271+ |
--drain-wait-sec <n> |
SELF_HOSTED_RUNNER_DRAIN_WAIT_MS |
0 |
드레인 시작 후 각 세션의 진행 중 턴·백그라운드 태스크를 최대 N초 기다린 뒤 자식 종료. 방금 끝난 백그라운드 태스크는 결과를 읽는 후속 턴이 시작될 때까지(최대 SELF_HOSTED_RUNNER_BG_RESULT_GRACE_MS) 실행 중으로 계산. stop timeout도 함께 상향 |
--environment-secret-file <path> |
SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET |
필수 | 환경 secret 파일 경로(orchestrator가 띄운 러너는 1회용 work-order JWT). 환경 변수는 파일 경로가 아니라 secret 값 자체. 구 --pool-secret-file / SELF_HOSTED_RUNNER_POOL_SECRET은 deprecation 안내와 함께 동작, 2.1.216 미만 preview 빌드는 구 이름만 인식 |
--exec-path <path> |
SELF_HOSTED_RUNNER_EXEC_PATH |
자체 바이너리 | 세션마다 spawn할 바이너리/wrapper script |
--exit-if-unused-min <n> |
SELF_HOSTED_RUNNER_IDLE_SHUTDOWN_MS |
0 |
할당된 작업 없이 N분 폴링하면 종료(autoscaler scale-down). 0은 비활성 |
--git-host-rewrite <from>=<to> |
없음 | unset | clone 전 https://<from>/...를 https://<to>/...로 재작성(split-horizon DNS). 반복 가능, 플래그 전용 |
--git-ssh-rewrite <host> |
없음 | unset | clone 전 https://<host>/...를 git@<host>:...로 재작성. 반복 가능, 플래그 전용 |
--health-port <port> |
SELF_HOSTED_RUNNER_HEALTH_PORT |
8080 |
/healthz, /metrics 포트. 0이면 비활성 |
--hooks-dir <path> |
SELF_HOSTED_RUNNER_HOOKS_DIR |
unset | lifecycle hook 스크립트 디렉터리 |
--host-config-snapshot <mode> |
SELF_HOSTED_RUNNER_HOST_CONFIG_SNAPSHOT |
disk |
세션 시드용 호스트 config 디렉터리 시작 스냅샷 위치. disk는 --base-dir 아래 러너 소유 디렉터리에 복사하고 세션 시작마다 메모리 digest로 검증(변조되면 세션 실패 + 재시작까지 세션 거부). memory는 힙에 64 MiB 상한(초과 시 호스트 config 없이 시작 + 알림). 디스크에 쓸 수 없으면 로그 후 그 실행은 memory. 2.1.271+ |
--kill-session-after-min <n> |
SELF_HOSTED_RUNNER_MAX_LIFETIME_MS |
0 |
세션 wall-clock 상한(멈춘 세션 안전장치). 2.1.260+는 한도 도달 시 해제(다음 메시지에 재개)하고 SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS 후에도 러너에 있으면 종료. 이전에는 한도에서 종료. 0은 비활성 |
--lock-to-account <id> |
SELF_HOSTED_RUNNER_LOCK_TO_ACCOUNT |
unset | 첫 세션 대신 시작 시 특정 계정(이메일 또는 환경 조직의 user_... ID)에 사전 고정. 고정 러너는 계정이 없는 Claude Tag 채널 세션을 받지 않음 |
--log-file <path> |
SELF_HOSTED_RUNNER_LOG_FILE |
unset | stdout/stderr에 더해 파일로 미러링(권한 0600). self-hosted-runner doctor의 로컬 tail에 필요 |
--log-level <level> |
없음 | info |
info 또는 debug |
--post-session-hook-timeout-sec <n> |
SELF_HOSTED_RUNNER_POST_SESSION_HOOK_TIMEOUT_MS |
60 |
러너 종료 포함 모든 세션 종료의 post-session hook 예산 |
--proxy-authorization-command <command> |
SELF_HOSTED_RUNNER_PROXY_AUTHORIZATION_COMMAND |
unset | 프록시 연결마다 실행, trim된 stdout이 Proxy-Authorization 값. HTTPS_PROXY/HTTP_PROXY 필요, --proxy-authorization-file과 동시 사용 불가. 2.1.238+ |
--proxy-authorization-file <path> |
SELF_HOSTED_RUNNER_PROXY_AUTHORIZATION_FILE |
unset | 연결마다 읽는 파일(다른 프로세스가 제자리 갱신하는 토큰). 요건 동일. 2.1.238+ |
--push-outcome-on-release |
SELF_HOSTED_RUNNER_PUSH_OUTCOME_ON_RELEASE |
off | 드레인·유휴 해제 같은 러너 주도 세션 종료 시 workspace 삭제 전에 추적 outcome 브랜치를 origin에 push(best-effort, 종료 예산 +30초, 재개에는 git 2.29+). 활성화 전 claude/* ref push 권한 제한. checkout hook 저장소는 push되지 않음 |
--release-idle-session-min <n> |
SELF_HOSTED_RUNNER_SESSION_IDLE_MS |
0 |
턴이 끝났거나 사용자 동작을 기다린 뒤 N분 비활성이면 세션 슬롯 해제. 턴 진행 중(끝나지 않는 백그라운드 태스크, 실행 중 도구 호출 내부 승인 포함)은 유휴가 아니므로 --kill-session-after-min과 함께 사용. 해제로 활성 세션이 없어지면 --drain-grace-sec가 정하는 정상 종료 경로. 0은 비활성 |
--remove-session-state [bool] |
SELF_HOSTED_RUNNER_REMOVE_SESSION_STATE |
off | 세션 종료 시 <base-dir>/_sessions/ 아래 세션별 디렉터리 삭제(best-effort, 결과 무관). 켜면 실패·중단된 세션의 디버그 로그도 디스크에 남지 않음. 2.1.268+ |
--retire-at <epoch-seconds> |
SELF_HOSTED_RUNNER_RETIRE_AT |
unset | 절대 Unix 초 시각에 러너 retire(알려진 시각에 kill하는 인프라용). 2001년 이전/5138년 이후 값은 플래그에서 거부, 환경 변수에서 무시 |
--server-auto-mode-lists <mode> |
SELF_HOSTED_RUNNER_SERVER_AUTO_MODE_LISTS |
no-allow |
컨트롤 플레인이 세션과 함께 보내는 auto mode 분류기 규칙 목록 중 세션에 닿게 할 것: all, no-allow, none. 잘못된 값이면 시작 중단. 2.1.295+ |
--session-stop-grace-sec <n> |
SELF_HOSTED_RUNNER_SESSION_STOP_GRACE_MS |
5 |
세션 종료 후 Claude 프로세스가 정상 종료하기를 기다린 뒤 강제 kill. 자식의 SessionEnd hook이 더 필요하면 상향 |
--startup-timeout-min <n> |
SELF_HOSTED_RUNNER_STARTUP_TIMEOUT_MS |
15 |
자식이 activity channel로 초기화 신호를 N분 내에 보내지 않으면 슬롯 해제. spawn(clone 이후)부터 세고, 초기화·상태 보고 전에 자식이 내는 user/assistant 메시지마다 다시 시작(v2.1.299 이전에는 첫 메시지에서 적용 중지). 초기화 이후 해제는 --release-idle-session-min. 0은 비활성 |
--trust-workspace [bool] |
SELF_HOSTED_RUNNER_TRUST_WORKSPACE |
on | 세션 저장소 경로에 대한 persisted trust를 시드해 저장소 커밋 permissions.allow, additionalDirectories가 적용되게 함. false면 저장소 권한 grant를 버리고 호스트 config settings.json에서 allow 규칙을 구성(저장소 커밋 sandbox.*는 어느 쪽이든 적용되어 repo-settings guard가 무관하게 검사) |
--use-anthropic-git-proxy |
CLAUDE_RUNNER_USE_GIT_PROXY=1 |
off | github.com 저장소를 Anthropic git proxy로 clone. --capacity 1과 git 2.32+ 필요(아니면 시작 거부). rewrite 플래그를 대체 |
최근 변경: 2.1.307에서 checkout hook은 기본 30분 후 중단(세션 setup 실패) -> --checkout-hook-timeout-min 상향 또는 0. 2.1.309: 첫 프롬프트(예: /code-review)가 서브에이전트에서 계속 작업 중이면 --startup-timeout-min이 세션을 멈추지 않도록 수정(멈춘 프로세스는 여전히 중지).
auto mode 규칙 목록
--server-auto-mode-lists는 러너 밖에서 오는 auto mode 분류기 규칙 중 무엇이 러너 세션에 닿을지 정합니다. Anthropic 컨트롤 플레인이 세션과 함께 규칙 목록을 보내 러너에 적용을 요청할 수 있고, 일부 항목은 조직 관리자가 쓴 규칙일 수 있습니다. 목록은 environment, soft_deny, allow.
environment: 항목이 분류기를 더 허용하게도 덜 허용하게도 만들 수 있음soft_deny: 사용자가 명시적으로 요청했거나allow예외가 적용되지 않는 한 동작을 차단allow:soft_deny항목에 대한 예외
값별 적용:
no-allow(기본):environment,soft_deny적용,allow보류.environment항목이 여전히 더 허용하게 만들 수 있어 모든 완화를 배제하지는 않음all: 세 목록 전부 적용none: 아무것도 적용 안 함. 이 목록의 모든 완화를 배제하려면none(soft_deny제한도 사라짐)
러너 설정으로 컨트롤 플레인이 적용을 요청하게 만들 수는 없고, 요청하지 않으면 값과 무관하게 세션은 목록을 받지 않습니다. --log-level debug로 시작하면 세션마다 the server asked this runner to apply 또는 the server did not ask this runner to apply the auto mode lists it sends를 로그합니다.
Orchestrator CLI 플래그
self-hosted-runner orchestrator는 on-demand 러너를 spawn하며 --api-url, --environment-secret-file, --hooks-dir, --health-port, --log-level을 러너와 같은 기본값·환경 변수로 받되 --hooks-dir은 필수이며 spawn-runner hook이 들어 있어야 합니다. 자체 플래그:
| 플래그 | 기본값 | 설명 |
|---|---|---|
--hook-concurrency <n> |
4 |
병렬 spawn-runner hook 최대 수, 폴링당 claim하는 spawn 요청 수의 상한 |
--hook-timeout <sec> |
60 |
이 시간 후 hook 프로세스 트리 종료. 타임아웃 + 5초 kill grace가 --expected-spawn-seconds 미만이어야 하며 시작 시 강제 |
--expected-spawn-seconds <sec> |
120 |
spawn 요청 수신부터 러너 등록까지 예상 p99(플랫폼 용량 대기 포함). 서버 허용 범위 10~3600. 매 폴링에 서버 측 lease로 전송, 그 안에 러너가 등록하지 않으면 새 order ID로 재제안. 모든 레플리카가 같은 값 |
--min-idle <n> |
0 |
standby 러너를 미리 spawn해 유휴 슬롯을 최소 N개 유지. 0은 사전 워밍 비활성. 잉여 standby가 스스로 회수되도록 러너 --exit-if-unused-min과 함께 |
--debug-dir <path> |
unset | 각 spawn 요청의 work order와 hook stderr를 디스크에 기록(디버그 전용, 프로덕션 금지) |
2.1.299에서 --shutdown-drain-sec이 추가되었습니다. SIGTERM 시 폴링을 멈추고 진행 중인 폴과 실행 중인 spawn hook이 끝나도록 그 초만큼 기다립니다.
SCM connector 플래그
SCM connector는 사용할 수 없으므로 이 절의 플래그는 설정하지 마세요. --scm-connector-host를 설정하면 연결이 열리지 않고 orchestrator가 재시도를 계속하지만 세션이 큐에 쌓이면 러너는 계속 시작됩니다. 커넥터는 orchestrator에서 Anthropic 컨트롤 플레인으로의 상시 WebSocket이며, 내부망에서만 닿는 GitHub Enterprise Server 호스트를 호스티드 pre-session 흐름(저장소 선택기, 브랜치/ref 해석기)이 쓰게 하려는 설계입니다.
| 플래그 | 기본값 | 설명 |
|---|---|---|
--scm-connector-host <host[:port]> |
unset | 요청을 전달할 GHE 호스트명. 포트 기본 443 |
--scm-connector-id <n> |
--scm-connector-host와 함께 필수 |
조직의 GHE 연결 숫자 ID |
--scm-connector-provider <slug> |
ghe |
provider 경로 세그먼트, ^[a-z0-9-]{1,32}$ |
--scm-connector-ca-file <path> |
unset | GHE TLS 연결용 추가 CA 번들(PEM) |
--scm-connector-host-rewrite <from>=<to_host:to_port> |
unset | E2E 테스트 전용: Host 헤더와 TLS SNI를 --scm-connector-host로 유지한 채 TCP 연결을 리디렉션 |
연결 시도마다 기존 환경 secret을 보내며 지수 백오프(30초 + jitter 상한)로 자동 재시도합니다.
환경 변수 전용 설정
| 환경 변수 | 기본값 | 설명 |
|---|---|---|
SELF_HOSTED_RUNNER_BG_RESULT_GRACE_MS |
30000 |
백그라운드 태스크가 끝났지만 결과를 읽는 후속 턴이 아직 시작 안 한 동안 세션을 busy로 간주하는 시간. 드레인·유휴 해제·--retire-at에 적용. 0/사용 불가 값은 기본값으로 복귀(끌 수 없음). 2.1.228+ |
SELF_HOSTED_RUNNER_HOST_CONFIG_DIR |
~/.claude |
러너 시작 스냅샷으로 캡처해 각 세션 CLAUDE_CONFIG_DIR에 시드하는 디렉터리(디스크 변경은 러너 재시작 후 적용). 설정하면 MCP 시딩용 .claude.json 읽기 위치도 이동(기본값으로 설정해도 이동), 빈 디렉터리를 지정하면 시딩 비활성 |
SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS |
900000 |
--kill-session-after-min 한도 도달 후 진행 중 턴 종료/해제 완료를 기다린 뒤 세션을 종료하기까지 시간 |
SELF_HOSTED_RUNNER_POST_TURN_SETTLE_MS |
7000 |
--drain-wait-sec 드레인에서 턴이 끝난 뒤 세션 프로세스가 턴 종료를 Anthropic에 보고하는 동안 busy로 계산하는 최대 시간. 0/사용 불가 값은 기본값으로 복귀. 2.1.275+ |
SELF_HOSTED_RUNNER_SIGKILL_GRACE_MS |
30000 |
uninterruptible I/O에 걸린 자식에 OS가 SIGKILL을 전달하길 기다린 뒤 러너가 스스로 종료하는 시간. 하한은 --post-session-hook-timeout-sec + 15초(--push-outcome-on-release면 +30초), 기본에서 실효 최소 75초 |
CLAUDE_RUNNER_FETCH_DEPTH |
50 |
새 clone의 git fetch depth. 양의 정수, 또는 full/0(전체). workspace에 이미 있는 저장소는 기존 depth 유지 |
CLAUDE_RUNNER_FETCH_SERVER_PROGRESS_CAP_MS |
600000 |
git 서버의 자체 진행 수치가 올라가는 동안(대형 저장소 pack 준비 등) fetch가 첫 데이터를 기다릴 수 있는 시간(시도당). 0/off면 데이터 없이 2분 후 중단. 그 외 정수는 120000~`1800000`으로 clamp. 2.1.295+ |
CLAUDE_RUNNER_SKIP_GIT_VERIFY |
unset | 1이면 checkout hook 실행 후 .git 존재 확인을 건너뜀(hook이 non-git 소스를 구성할 때) |
FORCE_AUTOUPDATE_PLUGINS |
unset | 1이면 바이너리를 고정해도 플러그인 마켓플레이스 auto-update 허용 |
CLAUDE_CODE_DISABLE_ARTIFACT |
unset | 1이면 조직 admin 설정과 무관하게 세션의 Artifact tool 비활성, *.frame.claudeusercontent.com egress 요구 제거 |
텔레메트리
세션 자식은 끄지 않는 한 운영 텔레메트리를 Anthropic에 보냅니다(코드·저장소 내용은 보내지 않음). 텔레메트리 변수는 러너 프로세스에 설정하며 서버가 제공하는 환경 변수를 적용한 뒤 러너가 다시 단언하므로 운영자 설정이 항상 우선합니다.
셀프호스티드 전용 컨트롤은 CLAUDE_CODE_BYOC_ENABLE_DATADOG=1(Datadog 운영 메트릭 opt-in, 기본 off)입니다. 일반 Claude Code 텔레메트리 컨트롤 DISABLE_TELEMETRY, DO_NOT_TRACK, DISABLE_ERROR_REPORTING, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC는 환경 변수 레퍼런스대로 세션 자식에 적용됩니다. DISABLE_GROWTHBOOK=1은 feature flag fetch만 끄며 DISABLE_TELEMETRY도 설정하지 않으면 텔레메트리는 유지됩니다. CLAUDE_CODE_ENABLE_TELEMETRY는 별개로 내 collector로의 OpenTelemetry export를 켜며 Anthropic 분석을 제어하지 않습니다.
Health 엔드포인트
러너는 health 포트에서 GET /healthz를 제공하며 프로세스가 살아 있으면 poll loop 상태와 무관하게 200 OK입니다(HTTP 프로브는 죽은 프로세스만 감지). JSON 본문:
{
"status": "ok",
"runner_id": "ccrunner_...",
"active_sessions": 2,
"last_poll_at": "2026-03-31T18:04:11.220Z",
"last_poll_age_ms": 842
}
last_poll_age_ms를 커스텀 프로브의 liveness 신호로 쓰세요(무한히 커지면 poll loop가 멈춘 것). last_poll_at/last_poll_age_ms는 첫 폴 완료 전까지 null.
orchestrator의 /healthz는 항상 200이며 본문의 connected가 최근 폴 성공 여부를, queue_counts가 상태별 spawn 큐 수를 보고합니다. connected가 false인 이유는 최상위 last_error(성공한 폴이 null로 초기화)에서 확인합니다. 준비성/알림은 상태 코드가 아니라 connected로 판단하세요. SCM connector가 설정되면 본문에 scm_connector_connected와 scm_connector 객체(connected, last_connected_at, last_error, reconnects, requests_forwarded)도 포함되고 --scm-connector-host가 없으면 둘 다 null입니다.
Prometheus 메트릭
각 러너는 /healthz와 같은 포트의 GET /metrics로 노출합니다.
| 시리즈 | 비고 |
|---|---|
claude_code_self_hosted_runner_info{runner_id,version,client_label} |
항상 1, 플릿 인벤토리·버전 드리프트 감지 |
claude_code_self_hosted_runner_capacity |
설정된 --capacity |
claude_code_self_hosted_runner_active_sessions |
현재 실행 중 세션 |
claude_code_self_hosted_runner_locked_account{email} |
러너가 사용자에 고정되고 act.email 클레임이 든 세션 토큰이 발급된 뒤에만 존재. Claude Tag 에이전트에 고정된 러너는 시리즈 없음. 라벨 값은 계정 이메일이므로 메트릭 저장소를 넓게 읽을 수 있으면 scrape 시 metric_relabel_configs로 drop/hash |
claude_code_self_hosted_runner_last_poll_age_seconds |
마지막 성공 폴 이후 초. 60 초과 시 알림 |
claude_code_self_hosted_runner_poll_errors_total{error_kind} |
PollWork 실패 누적(transport, timeout, 5xx, 429, 4xx). 다섯 시리즈가 프로세스 시작부터 존재, rate(...[5m]) > 0에 알림 |
claude_code_self_hosted_runner_sessions_started_total{client_platform} |
러너 수명 동안 spawn한 세션 자식 수(web_claude_ai, ios, android, desktop_app, claude_code_cli, 서버가 안 보냈으면 unknown). Slack 세션은 claude_in_slack 또는 claude-in-slack이라 {client_platform=~"claude[-_]in[-_]slack"} 정규식 셀렉터. 플릿 합계는 sum() |
claude_code_self_hosted_runner_sessions_completed_total{client_platform} |
정상 종료한 세션(단순 정상 exit보다 넓음, 아래 카운터 의미 참고) |
claude_code_self_hosted_runner_sessions_failed_total{client_platform} |
실패로 끝난 세션 |
claude_code_self_hosted_runner_sessions_interrupted_total{client_platform} |
세션 결과가 아니라 운영상 이유로 러너가 종료한 세션 |
claude_code_self_hosted_runner_initializing_sessions |
할당부터 자식 init 이벤트까지 init 단계인 세션 수 |
claude_code_self_hosted_runner_session_init_duration_seconds |
세션 init 시간 히스토그램 |
claude_code_self_hosted_runner_session_init_errors_total |
init 전 실패한 세션(checkout hook 실패, git 준비, 토큰 문제, init 전 자식 crash) |
claude_code_self_hosted_runner_session_start_hook_errors_total |
오류 결과를 보고한 SessionStart hook(실패한 실행당 1) |
claude_code_self_hosted_runner_session_idle_seconds{session_id,client_platform} |
세션별 유휴 이후 초(미응답 권한 프롬프트에 걸린 세션 종료에 유용) |
orchestrator는 자신의 /healthz와 같은 포트의 GET /metrics로 다음 시리즈를 제공합니다.
| 시리즈 | 비고 |
|---|---|
claude_code_self_hosted_orchestrator_info{version,pool_id,orchestrator_uuid,hostname} |
항상 1 |
claude_code_self_hosted_orchestrator_connected |
최근 폴 성공이면 1, 실패 종류와 무관하게 실패하면 0 |
claude_code_self_hosted_orchestrator_last_poll_age_seconds |
마지막 폴 시도(성공/실패) 이후 초(러너의 같은 이름 메트릭은 마지막 성공 이후). connected와 함께 사용. 폴 루프가 hook 실행을 기다리므로 60이 아니라 --hook-timeout + 여유(기본 약 90초) 초과 시 알림 |
claude_code_self_hosted_orchestrator_poll_errors_total{error_kind} |
PollSpawnHints 실패 누적(transport, timeout, 5xx, 429, 4xx) |
claude_code_self_hosted_orchestrator_queue_pending_sessions |
지금 claim 가능한 spawn 요청 |
claude_code_self_hosted_orchestrator_queue_backing_off_sessions |
재시도 가능한 hook 실패 후 백오프 중인 spawn 요청 |
claude_code_self_hosted_orchestrator_queue_circuit_broken_sessions |
spawn이 차단된 세션. 사용자가 새 메시지를 보내거나 Owner가 환경 Activity 탭에서 재시도할 때까지 차단 유지, 원인을 고쳐도 0보다 클 수 있음. 0 초과 시 알림 |
claude_code_self_hosted_orchestrator_pool_pending_sessions |
이 환경에서 러너를 기다리는 전체 세션. 환경 전체 집계로 모든 orchestrator 인스턴스에서 같으므로 인스턴스 간 SUM이 아니라 MAX |
claude_code_self_hosted_orchestrator_pool_active_sessions |
이 환경의 살아 있는 러너에 할당된 세션(MAX 사용) |
claude_code_self_hosted_orchestrator_spawn_hooks_total{result} |
spawn-runner hook 결과 누적: ok, retryable, non_retryable. 러너의 세션 자식이 아니라 hook 호출 수라 sessions_started_total과 비교 불가(capacity 1 초과, warm pool, 같은 세션의 재spawn으로 어긋남) |
claude_code_self_hosted_orchestrator_spawn_hook_duration_seconds |
hook 소요 히스토그램 |
claude_code_self_hosted_orchestrator_warm_hints_dispatched_total |
프로세스 시작 이후 보낸 standby spawn 요청 |
claude_code_self_hosted_orchestrator_session_queue_wait_seconds |
큐에서 orchestrator가 spawn을 claim하기까지 대기한 초 히스토그램(p50/p99 큐 대기 알림용, 사전 워밍 spawn은 샘플링 안 됨) |
claude_code_self_hosted_orchestrator_clock_skew_seconds |
로컬-서버 시계 차(진단용, 측정 후 존재) |
claude_code_self_hosted_orchestrator_scm_connector_connected |
SCM connector WebSocket이 열려 있으면 1, dial/백오프 중 0. --scm-connector-host가 없으면 없음 |
claude_code_self_hosted_orchestrator_scm_connector_requests_forwarded_total |
설정된 SCM 호스트로 프록시한 HTTP 요청 누적. 호스트가 없으면 없음 |
오토스케일링은 스케일링 방식에 맞는 시리즈를 고르고 scaler에 넣기 전에 게이트합니다.
- 큐 깊이 스케일링:
queue_pending_sessions가 아니라claude_code_self_hosted_orchestrator_pool_pending_sessions를 HPA/KEDA에 입력 - 용량 스케일링: 러너
active_sessions대capacity비율 connected로 게이트: 인스턴스별로claude_code_self_hosted_orchestrator_connected == 1로 필터해 연결이 끊긴 레플리카의 오래된 값이 scaler에 들어가지 않게 함
전체 폴 장애(모든 레플리카 연결 끊김)에서 게이트된 쿼리는 데이터를 반환하지 않습니다. HPA는 메트릭 누락 시 현재 레플리카 수를 유지하지만 KEDA Prometheus scaler는 기본 ignoreNullValues: "true"에서 빈 결과를 0으로 읽어 scale-in하므로 ScaledObject에 ignoreNullValues: "false"를 설정하고 필요하면 fallback 레플리카 하한을 둡니다.
러너와 orchestrator를 모두 다루는 Prometheus Operator PodMonitor 예시입니다(Kubernetes 레시피의 app.kubernetes.io/part-of: claude-code-self-hosted-runner 라벨과 이름 있는 health 포트 기준, 네임스페이스는 배포에 맞게 조정).
# Example Prometheus Operator PodMonitor for the Claude Code self-hosted
# runner + orchestrator. Adjust the namespace and label selectors to match
# your deployment. Both the runner and the orchestrator serve /metrics on
# their --health-port (default 8080).
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: claude-code-self-hosted-runner
namespace: monitoring
spec:
namespaceSelector:
matchNames:
- claude-runners
selector:
matchExpressions:
# Matches the runner Deployment from the Kubernetes recipe, plus any
# on-demand runner Jobs and orchestrator pods you label the same way
# and give a named 'health' containerPort.
- key: app.kubernetes.io/part-of
operator: In
values: [claude-code-self-hosted-runner]
podMetricsEndpoints:
- port: health
path: /metrics
interval: 30s
알림 규칙 예시(임계값은 플릿 규모에 맞게 조정):
# Example Prometheus alert rules for the Claude Code self-hosted runner
# + orchestrator. Tune thresholds for your fleet size and SLOs.
groups:
- name: claude-code-self-hosted-runner
rules:
- alert: ClaudeRunnerPollStale
expr: claude_code_self_hosted_runner_last_poll_age_seconds > 60
for: 2m
labels: {severity: warning}
annotations:
summary: "Runner {{ $labels.pod }} has not polled in >60s"
- alert: ClaudeRunnerVersionDrift
expr: count(count by (version) (claude_code_self_hosted_runner_info)) > 1
for: 30m
labels: {severity: info}
annotations:
summary: "Runners are running mixed versions"
- alert: ClaudeRunnerInitErrorsHigh
expr: increase(claude_code_self_hosted_runner_session_init_errors_total[10m]) > 3
for: 5m
labels: {severity: warning}
annotations:
summary: "Runner {{ $labels.pod }}: >3 session init failures in 10m (checkout hook / git / token / pre-init crash)"
- alert: ClaudeRunnerPollErrors
expr: sum by (pod) (rate(claude_code_self_hosted_runner_poll_errors_total[5m])) > 0
for: 2m
labels: {severity: warning}
annotations:
summary: "Runner {{ $labels.pod }}: PollWork failing ({{ $value | humanize }}/s over 5m)"
- alert: ClaudeRunnerSessionStartHookErrors
expr: increase(claude_code_self_hosted_runner_session_start_hook_errors_total[10m]) > 3
for: 5m
labels: {severity: warning}
annotations:
summary: "Runner {{ $labels.pod }}: >3 SessionStart hook failures in 10m"
- name: claude-code-self-hosted-orchestrator
rules:
- alert: ClaudeOrchestratorDisconnected
expr: claude_code_self_hosted_orchestrator_connected == 0
for: 2m
labels: {severity: critical}
annotations:
summary: "Orchestrator {{ $labels.pod }} cannot reach the Anthropic control plane"
- alert: ClaudeOrchestratorPollStale
expr: claude_code_self_hosted_orchestrator_last_poll_age_seconds > 90
for: 2m
labels: {severity: warning}
annotations:
summary: "Orchestrator {{ $labels.pod }} has not polled in >90s (poll loop waits on hook execution)"
- alert: ClaudeOrchestratorCircuitBroken
expr: claude_code_self_hosted_orchestrator_queue_circuit_broken_sessions > 0
for: 1m
labels: {severity: critical}
annotations:
summary: "Sessions blocked from spawning: {{ $value }}. Read each one's error in the Activity tab, fix the cause, then select Retry"
- alert: ClaudeOrchestratorPollErrors
expr: sum by (pod) (rate(claude_code_self_hosted_orchestrator_poll_errors_total[5m])) > 0
for: 2m
labels: {severity: warning}
annotations:
summary: "Orchestrator {{ $labels.pod }}: PollSpawnHints failing ({{ $value | humanize }}/s over 5m)"
- alert: ClaudeOrchestratorSpawnHookFailing
expr: sum by (pod) (increase(claude_code_self_hosted_orchestrator_spawn_hooks_total{result!="ok"}[5m])) > 3
for: 5m
labels: {severity: warning}
annotations:
summary: "Orchestrator {{ $labels.pod }}: >3 spawn-runner hook failures in 5m"
세션 자식 메트릭 pass-through
각 세션은 자체 OpenTelemetry 메트릭을 가진 자식 프로세스에서 돌며 --capacity 1 초과에서는 러너가 자식 메트릭 노출 방식을 재작성합니다. 러너 호스트에 OTEL_METRICS_EXPORTER=prometheus, 세션 환경(wrapper script 또는 세션이 상속하는 러너 환경)에 CLAUDE_CODE_ENABLE_TELEMETRY=1을 설정하면 각 자식의 counter/gauge 계측이 러너 자신의 /metrics에 러너 시리즈와 함께 재노출됩니다. 러너가 자식 exporter를 health 포트의 loopback 전용 수신기로 OTLP push하도록 재작성하고 각 시리즈에 session_id, client_platform 라벨을 붙이며 세션 종료 시 해당 시리즈를 제거합니다. 히스토그램은 통과하지 않고 러너 자체 접두사와 충돌하는 자식 메트릭 이름은 drop됩니다. 기본 --capacity 1에서는 재작성이 적용되지 않고 자식이 평소처럼 포트 9464에 자체 Prometheus 엔드포인트를 바인드합니다.
세션 메트릭을 claude_code_self_hosted_runner_session_idle_seconds 옆에 두려면 러너의 session_id로 조인합니다.
--capacity1 초과: 직접 조인. 러너/metrics의 세션 시리즈가 같은session_id(session_...형태)를 가짐--capacity 1: 변환한ccr_session_id로 조인. 포트 9464의 각 Claude Code 메트릭은 기본으로cse_...형태 세션 ID의ccr_session_id라벨을 가지며 새 라벨에서cse_접두사를session_로 바꾸면 러너의session_id. 메트릭 자체의session_id는 Claude Code 자체session.id로 어떤 러너 시리즈와도 맞지 않음
세션 lifecycle 카운터 의미
sessions_started_total, sessions_completed_total, sessions_failed_total, sessions_interrupted_total은 세션이 어떻게 끝났는지로 분류합니다. spawn된 모든 세션 자식은 spawn 시 sessions_started_total을 올리고 종료 시 나머지 셋 중 정확히 하나가 증가하므로 started - 나머지 셋의 합 = 현재 실행 중 세션 수입니다.
completed: 정상 종료. 자식이 코드0으로 스스로 종료, 자식이 연결된 동안 세션이 archive/delete됨, 러너가 슬롯을 깨끗이 반환(유휴 타임아웃·retire 시각·--kill-session-after-min한도의 해제, 시작 타임아웃, 자식 종료 전 폴 루프가 감지한 서버 측 deassign)failed: 자식이 0이 아닌 코드로 스스로 종료(crash 또는 spawn 후 setup 실패)interrupted: 세션 성공도 러너 결함도 아닌 운영상 이유로 러너가 자식을 종료(드레인,--kill-session-after-min한도 뒤SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MSwindow 종료 후에도 러너에 남은 세션 종료 등). Kubernetes rolling restart의SIGTERM이 드레인의 예
v2.1.260 이전에는 한도에 도달한 모든 세션을 종료하고 sessions_interrupted_total에 집계했습니다. post-session hook의 CLAUDE_RUNNER_EXIT_REASON은 깨끗한 handoff를 다르게 분류합니다. hook은 러너가 자식을 멈췄으므로 release, 시작 타임아웃, 서버 deassign, 폴이 먼저 감지한 archive/delete를 interrupted로 보고하지만 카운터는 슬롯을 깨끗이 반환했으므로 completed로 기록합니다. hook 영수증을 sessions_completed_total과 직접 대조하면 완료가 과소 집계되므로 세션별 보장은 hook, 집계 비율은 카운터를 쓰세요.
원샷 환경(--capacity 1 + 기본 --drain-grace-sec 0)에서는 러너 프로세스가 세션 하나가 끝난 직후 종료합니다. completed/failed/interrupted는 세션 종료 시(종료 직전)에만 증가해 15~60초 간격 scrape가 러너 시리즈가 사라지기 전에 증가분을 잡기 어렵고(종료 카운터), started는 spawn 시 증가해 세션 수명 동안 보이지만 원샷 환경에서는 누적이 아니라 "현재 실행 중 세션"에 가깝게 읽힙니다. 목적별로 대신 쓸 시리즈:
| 목표 | 사용 |
|---|---|
| 처리량 | claude_code_self_hosted_orchestrator_spawn_hooks_total{result="ok"} (장수 orchestrator의 카운터로 rate()에서 의미 있음. hook 호출 수라 사전 워밍·같은 세션 재spawn은 세션 수와 어긋남) |
| 사용률 | sum(claude_code_self_hosted_runner_active_sessions) 대 sum(claude_code_self_hosted_runner_capacity) (러너 수명과 무관하게 모든 scrape에서 유효한 gauge) |
| 백로그 | 큐 깊이는 claude_code_self_hosted_orchestrator_pool_pending_sessions, claude_code_self_hosted_orchestrator_queue_circuit_broken_sessions(0 초과 시 알림) |
| 실패 | claude_code_self_hosted_runner_sessions_failed_total, best effort(spawn 후 실제 crash는 증가, --drain-grace-sec > 0으로 러너가 세션보다 오래 사는 경우 rate() 의미 있음, 원샷은 scrape window 문제가 같아 0이 아닌 값은 조사 대상). checkout hook 실패·git 준비·토큰 문제 같은 spawn 전 실패는 session_init_errors_total에만 나타남 |
orchestrator_* 행은 on-demand orchestrator를 쓰는 환경에만 있습니다. 러너가 세션보다 오래 사는 고정 플릿(--drain-grace-sec > 0)의 처리량은 sum(rate(claude_code_self_hosted_runner_sessions_started_total[5m]))를 쓰고 원샷 플릿에서는 이 시리즈도 같은 scrape window 문제가 있어 큐 대기 세션 수에 의존합니다. 백로그는 Cloud environments 관리 페이지의 환경 Activity 탭에서 확인합니다(러너는 큐 깊이 시리즈를 내보내지 않음). 세션별 결과 보고는 post-session hook을 쓰세요. VM preemption 같은 갑작스런 러너 종료를 제외하고 자식 프로세스가 spawn된 모든 세션 종료에 실행됩니다.