자체 호스팅 환경은 Team 및 Enterprise 플랜에서 공개 베타 상태입니다. 가용성 및 제한사항에서 활성화 경로를 다룹니다. 이 페이지는 프로덕션에서 플릿을 실행하는 것을 다룹니다. 첫 번째 러너 및 세션은 빠른 시작을 참조하세요.
배포 강화
자체 호스팅 러너는 환경에 세션을 전달할 수 있는 모든 사람을 대신하여 인프라에서 임의의 모델 지향 코드를 실행합니다. 이는 Anthropic 조직의 모든 멤버이며, Owner가 환경으로 라우팅한 범위에서 Claude Tag 채널 세션을 시작할 수 있는 모든 사람입니다. 환경을 프로덕션 시스템에 연결하기 전에 각 항목을 검토하세요:-
임시 세션별 컨테이너:
--capacity 1과 기본값--drain-grace-sec 0을 사용하여 각 러너 프로세스를 프로세스가 종료될 때 삭제되는 새로운 컨테이너 또는 VM에서 실행하므로 각 컨테이너는 정확히 하나의 세션을 제공합니다. 더 높은 용량이거나 양수 드레인 유예가 있으면 하나의 컨테이너가 동일한 잠긴 소유자의 여러 세션을 제공합니다. 러너 수명 주기를 참조하세요. 러너 재시작 간에 파일 시스템을 재사용하지 마세요. 의도적인 사전 준비된 체크아웃 설정을 제외하고, 소유자 간에는 절대 재사용하지 마세요.- 러너가 세션을 중지할 때, 셸 명령이 종료된 후에도 계속 실행 중인 프로세스(예: 데몬화된 서비스)에는 신호를 보내지 않습니다. 컨테이너 또는 VM을 삭제하면 해당 프로세스가 종료됩니다.
-
이미지에 광범위한 자격 증명 없음: 장기 SSH 키, 클라우드 공급자 자격 증명, 또는 세션이 필요한 것보다 더 많은 권한을 부여하는 개인 액세스 토큰을 포함하지 마세요. 세션 중에 사용되는 자격 증명(예: 푸시 또는 API 토큰)을 래퍼 스크립트에서 세션별로 발급하세요. 초기 클론은 래퍼가 실행되기 전에 발생하므로
checkout수명 주기 훅으로 처리하거나, 세션의 모든 저장소가 github.com에 있는 경우--use-anthropic-git-proxy로 처리하세요. 두 가지 모두 git 구성을 참조하세요. -
호스트의 GitHub 자격 증명을 세션에서 분리: Claude는 세션이 읽을 수 있는 모든 GitHub 자격 증명을 해당 자격 증명이 부여하는 모든 액세스 권한으로 사용할 수 있습니다. 러너 호스트 자체의 광범위한 범위를 가진 GitHub 자격 증명을 세션이 읽을 수 있는 곳에 두지 마세요. 이러한 자격 증명은 개인 액세스 토큰,
gh auth login이 계정에 대해 저장하는 토큰, 또는 러너 환경의GH_TOKEN일 수 있습니다.- Anthropic 관리형 git 사용 시: 이러한 자격 증명이 있으면 Claude는 Anthropic 관리형 git을 거치지 않고 GitHub에 직접 접근합니다.
- Anthropic 관리형 git 미사용 시: 이미지에 git 구성 포함에서 설명하는 만큼 범위를 엄격하게 제한하면 클론 자격 증명을 이미지에 유지할 수 있습니다.
- 세션 실행 호스트에서 환경 시크릿 분리: 환경 시크릿은 러너를 등록하고 환경에 대기 중인 모든 세션을 선택할 수 있습니다. 고정 플릿에서는 모든 러너 호스트에 있으며, 모든 세션의 코드가 시크릿 파일을 읽을 수 있습니다. 온디맨드 러너를 선호하세요. 여기서 시크릿은 사용자 코드를 실행하지 않는 오케스트레이터 호스트에 유지되며, 각 러너는 정확히 하나의 러너를 등록하는 단일 사용 작업 주문을 받습니다. 고정 플릿에서는 환경 시크릿 파일을 모든 세션이 읽을 수 있는 것으로 취급하고 의심되는 세션 손상 후 시크릿을 회전하세요.
- 기본 거부 네트워크 이그레스: 모든 환경에서 네트워크 경계에서 러너 및 세션 컨테이너 아웃바운드 트래픽을 제한하세요. 기본 거부 이그레스에서 허용할 항목과 이유를 다룹니다.
- 최소 권한 호스트 IAM: 러너 호스트에 연결된 컴퓨팅 ID(예: 인스턴스 프로필 또는 노드 서비스 계정)는 러너 자체가 필요한 것만 부여해야 합니다. 세션은 호스트의 ID를 상속하는 대신 래퍼 스크립트를 통해 자신의 자격증명을 얻어야 합니다.
-
세션에서 클라우드 메타데이터 엔드포인트 차단: 세션을 호스트 ID에서 벗어나게 유지하려면 클라우드 메타데이터 엔드포인트에 대한 액세스를 차단해야 하며, 서브넷 수준 이그레스 정책은 링크 로컬 메타데이터 트래픽을 가로채지 않으므로 컨테이너 자체에서 차단하세요:
- 홉 제한이 1인 IMDSv2
- 메타데이터 은폐가 있는 GKE Workload Identity
- 세션 컨테이너의 네트워크 네임스페이스에서
169.254.169.254에 대한 명시적 거부
-
러너별 파일 시스템 격리: 각 러너 프로세스는 호스트의 다른 프로세스가 읽거나 쓸 수 없는 자신의 작업 디렉토리를 가집니다.
--hooks-dir, 래퍼 스크립트, 호스트의~/.claude/를 이미지에 내장되거나 읽기 전용으로 마운트된 세션에 읽기 전용으로 만드세요. -
전달에는 환경별 액세스 제어가 없음: Anthropic 조직의 모든 멤버는 모든 환경에 세션을 전달할 수 있습니다. Owner가 Claude Tag 채널을 환경으로 라우팅하면, Claude Tag 액세스 설정이 허용하는 모든 사람이 거기서 실행되는 채널 세션을 시작할 수 있습니다. 기본적으로 Claude 계정이 있는지 여부와 관계없이 연결된 Slack 워크스페이스의 모든 사람입니다. 모든 러너 호스트를 코드 실행에 도달할 수 있는 것으로 취급하세요. 환경에 전달할 수 있는 모든 사람이 읽을 수 있는 데이터와 자격증명만 러너 호스트에 배치하세요.
--lock-to-account는 주어진 호스트가 실행하는 계정의 세션을 제한하지만, 환경에 전달할 수 있는 사람을 좁히지는 않습니다. 자체 호스팅 환경을 유일한 선택 옵션으로 만들려면 Owner가 전체 조직에 대해 클라우드 환경 페이지에서 Anthropic 호스팅 환경을 숨길 수 있습니다. -
리포지토리 설정 가드 적용:
--confine-repo-settings로 가드 모드를 선택하세요. 기본값warn은 위반을 기록하고 여전히 세션을 생성하고,enforce는 세션을 거부하며,off는 스캔을 비활성화합니다. 러너는 각 리포지토리의 커밋된 설정을 스캔합니다:- 해당 세션의 자신의 워크스페이스 외부에서 해결되는 권한:
additionalDirectories항목,permissions.allow의Edit,Write, 또는NotebookEdit규칙, 또는sandbox.filesystem.allowWrite또는allowRead항목 - 비어있지 않은
env블록 sandbox.enabled: false와 같은 운영자 태세 재정의
--trust-workspace와 관계없이 실행되며, 리포지토리 훅,.mcp.json, 또는 Bash 규칙을 다루지 않습니다. 권한 및 도구 승인에서 이러한 권한이 어디에 속하는지 참조하세요. - 해당 세션의 자신의 워크스페이스 외부에서 해결되는 권한:
네트워크 요구 사항
runner와 runner가 생성하는 세션 자식 프로세스는 아래 호스트로 아웃바운드 연결을 합니다. 세션 컨테이너의 egress를 이러한 호스트와 세션이 접근해야 하는 특정 내부 서비스로 제한하십시오. 방법과 이유는 기본 거부 egress에서 다룹니다. 다음 호스트는 항상 필요합니다.--use-anthropic-git-proxy를 사용하는 runner는 github.com git 트래픽을 api.anthropic.com을 통해 라우팅하므로 github.com에 대한 git 호스트 경로가 필요하지 않습니다. 단, --push-outcome-on-release를 설정하거나 post-session 훅에서 푸시하는 경우에는 여전히 해당 경로가 필요합니다.
다음 호스트의 필요 여부는 구성에 따라 다릅니다.
다음 호스트는 runner 또는 세션 트래픽을 위해 허용 목록에 추가할 필요가 없습니다.
statsig.anthropic.com,*.sentry.io,claude.ai,platform.claude.com: 일부 이전 엔터프라이즈 네트워크 체크리스트에 이 호스트들이 나와 있지만, runner는 이 호스트에 접근하지 않습니다. 기능 플래그 가져오기는api.anthropic.com으로 이루어지며, runner는 대화형 OAuth가 아닌 환경 시크릿으로 인증합니다.mcp-proxy.anthropic.com: 셀프 호스팅 세션은 이 호스트를 사용하지 않습니다. 조직에 커넥터 전달이 활성화되어 있으면 조직의 claude.ai 커넥터는api.anthropic.com을 통해 세션에 도달합니다. MCP 서버를 참조하십시오.
claude.ai에 접근하므로, 세션 컨테이너의 egress를 넓히는 대신 egress가 이를 허용하는 호스트에서 실행하십시오.
- 한 줄 설치 프로그램: 설치 시
claude.ai에서install.sh를 가져옵니다. - 대화형
claude auth login:claude.ai,claude.com,platform.claude.com을 통해 로그인합니다. 안내형 설정,doctor의 로그인 모드, CI dispatch가 이를 사용합니다. 로그인에 사용하는 브라우저는hcaptcha.com,*.hcaptcha.com,challenges.cloudflare.com에서 claude.ai 로그인 페이지의 브라우저 검사도 로드합니다. 조직에서 싱글 사인온을 사용하는 경우 해당 브라우저가sso.claude.com을 거쳐 리디렉션될 수도 있습니다.
기본 거부 egress
runner 및 세션 컨테이너는 아웃바운드 트래픽이 네트워크 요구 사항 표의 호스트, 사용 중인 git 호스트, 그리고 세션이 접근해야 하는 특정 내부 서비스로 제한된 네트워크 세그먼트 또는 네임스페이스에 배포하십시오. 제품은 이를 검증하거나 강제할 수 없으므로, 모든 환경에서 자체 네트워크 경계에 이를 적용하십시오. 세션 코드는 모델이 지시하며 임의의 호스트에 연결을 시도할 수 있습니다. 네트워크 계층의 기본 거부 egress는 이러한 시도가 도달할 수 있는 범위를 제한합니다. 이는 권한 모드와 관계없이 적용됩니다. 기본 사전 승인 도구 세트에 이미Bash가 포함되어 있으므로, 자동 모드가 아니더라도 셸 egress는 프롬프트 없이 실행됩니다.
각 세션이 내보내는 텔레메트리와 이를 끄는 방법에 대한 자세한 내용은 텔레메트리를 참조하십시오.
egress 프록시에 인증하기
일부 기업용 egress 프록시는 모든 연결에Proxy-Authorization 헤더를 요구합니다. 해당 헤더의 토큰은 교체 주기가 너무 빨라 HTTPS_PROXY에 설정하는 프록시 URL에 직접 기록할 수 없는 경우가 많습니다. 평소처럼 HTTPS_PROXY 또는 HTTP_PROXY를 프록시 URL로 설정한 다음, --proxy-authorization-command 또는 --proxy-authorization-file을 설정하여 runner가 헤더 값을 읽을 위치를 지정하십시오. 두 플래그 모두 Claude Code v2.1.238 이상이 필요합니다.
Proxy-Authorization 값의 출처 선택
Proxy-Authorization 토큰을 생성하는 방식에 맞는 플래그를 선택하십시오.
--proxy-authorization-command <command>: 필요할 때 생성하는 토큰에 사용합니다. runner는 셸 명령을 실행하고, 앞뒤 공백을 제거한 stdout을 헤더 값으로 사용합니다(예:Bearer <token>).--proxy-authorization-file <path>: 다른 프로세스가 제자리에서 교체하는 토큰에 사용합니다. runner는 파일을 읽고, 앞뒤 공백을 제거한 내용을 헤더 값으로 사용합니다.
runner가 시작을 거부하는 구성
각 플래그에는 환경 변수 형식도 있으며, runner CLI 플래그 레퍼런스에서 해당 플래그 옆에 나열되어 있습니다. runner는 프록시나 컨트롤 플레인에 접속하기 전에 플래그와 해당 환경 변수를 확인하며, 다음 세 가지 경우에 시작을 거부합니다.- 두 플래그가 모두 설정된 경우: 한 플래그와 다른 플래그의 환경 변수를 함께 설정해도 두 플래그를 모두 설정한 것으로 간주됩니다.
- 프록시 URL이 없는 경우:
HTTPS_PROXY와HTTP_PROXY중 어느 것에도http://또는https://URL이 없습니다. runner는 두 변수를 대문자와 소문자 모두로 읽으며,ALL_PROXY는 참조하지 않습니다. - orchestrator 하위 명령에 플래그를 전달한 경우:
self-hosted-runner orchestrator는 이 플래그나 해당 환경 변수를 허용하지 않습니다. 대신 orchestrator가 시작하는 각 runner에 플래그를 전달하십시오.
프록시 인증 플래그가 설정되어 있을 때 runner가 변경하는 사항
두 플래그 중 하나가 설정되면 runner는 자체 리스너를 시작하고, runner 자체, 수명 주기 훅, 세션의 프록시 트래픽을 해당 리스너를 통해 보냅니다. 리스너는 프록시로 가는 경로에서Proxy-Authorization 헤더를 추가합니다.
- 리스너: 리스너는
127.0.0.1의 포워드 프록시입니다. runner는 컨트롤 플레인에 등록하기 전에 리스너를 시작하며, 리스너를 시작할 수 없으면 시작 시 종료됩니다. - 프록시 변수: runner는 설정된
HTTPS_PROXY및HTTP_PROXY를 리스너를 가리키도록 다시 씁니다. 다시 쓴 값은 runner 자체, 수명 주기 훅, runner가 실행하는 모든 세션에 전달됩니다. - 토큰 교체: 교체된 토큰은 재시작 없이 적용됩니다. 리스너가 프록시로 여는 각 연결마다 runner는 명령을 다시 실행하거나 파일을 다시 읽고, 그 결과를 헤더로 추가합니다.
- 세션 환경: 세션은 리스너를 통해서만 프록시에 도달합니다. 각 세션의 환경에서 runner는
ALL_PROXY를 제거하고, 설정하지 않은 모든 표기의HTTPS_PROXY또는HTTP_PROXY를 제거하며,NO_PROXY를 runner 자체의 값으로 고정합니다. - 로그: runner는 헤더 값을 로그에 기록하지 않습니다.
git 구성
러너는 리포지토리 체크아웃을 관리하지만 기본적으로 git ID 또는 자격증명을 구성하지 않습니다. 러너의 이미지와 프로세스 환경을 제어하므로 git 구성을 제어합니다. 두 가지 접근 방식 중 하나를 선택하세요:- 러너가 git을 구성하도록 허용:
--configure-git으로 러너를 시작하여 Anthropic 호스팅 세션이 사용하는 동일한 ID 및 커밋 서명 구성을 작성하도록 합니다. - 이미지에 git 구성 제공: ID 및 푸시 자격증명을 직접 설정하세요(예: 자신의 봇 ID로 커밋하기 위해).
--use-anthropic-git-proxy로 러너를 시작하거나 CLAUDE_RUNNER_USE_GIT_PROXY=1을 설정하여 러너의 세션에 대한 git을 Anthropic이 제공하도록 요청할 수도 있습니다.
러너 호스트의 Git 버전 하한: --configure-git SSH 커밋 서명에는 Git 2.34 이상이 필요하고, --use-anthropic-git-proxy에는 2.32 이상이 필요하며, --push-outcome-on-release로 푸시된 분기에서 세션을 재개하려면 2.29 이상이 필요합니다. 세 가지를 모두 생략하고 git ID를 직접 관리하면 Git 2.24로 충분합니다.
러너가 git을 구성하도록 허용
--configure-git으로 러너를 시작하거나 SELF_HOSTED_RUNNER_CONFIGURE_GIT=1을 설정하여 시작 시 전역 git 구성을 작성하도록 합니다:
user.name = Claude및user.email = noreply@anthropic.com, Anthropic 호스팅 세션과 일치- SSH 형식 커밋 및 태그 서명, 러너 관리 shim을 통해 라우팅되어 세션의 자신의 자격증명을 사용하여 Anthropic의 서명 서비스를 통해 각 커밋에 서명합니다. 서명은 Anthropic의 게시된 SSH 서명 키에 대해 GitHub에서 확인할 수 있습니다.
push.negotiate = true, git이 푸시를 패킹하기 전에 git 호스트에 이미 가지고 있는 커밋을 요청하도록 합니다. Claude Code v2.1.257 이상이 필요합니다.core.hooksPath는 러너 관리 훅 디렉토리를 가리킵니다. 해당commit-msg및prepare-commit-msg훅은 각 커밋에 세션 작성자를 위한Co-authored-by:트레일러를 추가합니다. 트레일러는CCR_SESSION_ACCOUNT_EMAIL의 이메일로 만들어지며 해당 변수가 설정되지 않으면 생략됩니다. 이미지가 이미core.hooksPath를 설정했고 러너가 Anthropic 관리 git을 사용하지 않는 경우, 러너는 설정을 그대로 두고 이러한 훅 설치를 건너뛰며[runner:git]경고를 출력합니다.
checkout 또는 post-session 수명 주기 훅에서 만드는 커밋도 Co-authored-by: 트레일러 없이 세션으로 서명됩니다. 수명 주기 훅 내부의 git 구성에서 러너가 해당 훅 내부에서 고정하는 git 설정을 설명합니다.
--configure-git 사용 여부와 관계없이 Claude Code는 Claude에게 커밋 메시지 끝에 Claude-Session: <url> 트레일러를, 풀 리퀘스트 설명 끝에 세션의 URL을 추가하도록 지시합니다. 둘 다 생략하려면 러너 호스트의 ~/.claude/settings.json에서 attribution.sessionUrl을 false로 설정한 다음 러너를 다시 시작하세요.
이미지에 git 구성 제공
git ID는 모든 커밋에 필요합니다. Dockerfile에서 시스템 전체로 설정하여 러너 프로세스가 실행되는 사용자와 관계없이 구성이 적용되도록 합니다:git commit이 Please tell me who you are로 실패하고 세션이 진행할 수 없습니다. 대신 자신의 봇 ID를 사용할 수 있습니다. 러너는 이러한 값을 재정의하지 않습니다.
공유 러너 이미지에 장기 또는 광범위 푸시 자격 증명을 굽지 마세요: 이미지의 자격 증명은 이미지가 실행하는 모든 세션에서 사용 가능하며, 이를 시작한 사람과 관계없습니다. 대신 래퍼 스크립트에서 세션 JWT에서 디코딩된 세션 작성자의 ID를 사용하여 세션별로 단기, 최소 범위 토큰을 발급하세요. 이를 임시 세션별 컨테이너와 쌍으로 만드세요. --capacity 1이 필요하므로 어떤 자격 증명도 이를 발급한 세션보다 오래 살지 않습니다. 강화 섹션을 참조하세요.
이미지 수준에서 푸시 자격증명을 구성해야 하는 경우(예: 읽기 전용 배포 키의 경우) git 호스트가 허용하는 만큼 좁게 범위를 지정하세요:
- 한 리포지토리로 제한되고
url.<base>.insteadOf다시 쓰기가 있는 SSH 배포 키 - 최소 범위 토큰을 반환하는
credential.helper - 좁게 범위가 지정된 키를 가리키는
GIT_SSH_COMMAND
- 러너는
GIT_TERMINAL_PROMPT=0을 설정하므로 git은 사용자 이름이나 암호를 요청하지 않습니다. - 러너는
BatchMode=yes로 SSH를 실행하며, 설정한 경우GIT_SSH_COMMAND에 추가되므로 SSH는 암호 또는 호스트 확인을 요청하지 않습니다. - 러너는
GCM_INTERACTIVE=never을 설정하므로 Git Credential Manager는 서명 대화를 열지 않습니다. - 러너는
core.askPass를 지우므로 askpass 헬퍼를 사용하는 경우GIT_ASKPASS환경 변수를 통해 설정하세요.
GIT_SSH_COMMAND 또는 GIT_ASKPASS에서 이름을 지정하는 모든 프로그램을 세션이 쓸 수 없는 곳에 보관하세요. 강화 체크리스트가 훅 디렉토리 및 래퍼 스크립트에 대해 요청하는 방식과 같습니다. 해당 프로그램의 명령줄에 있는 모든 키 또는 파일도 마찬가지입니다. 러너의 자신의 git은 클론 또는 페치할 때 해당 프로그램을 실행합니다.
체크아웃 디렉토리가 러너 프로세스와 다른 uid로 소유된 경우 git은 이에 대해 작동하기를 거부합니다. safe.directory를 추가하세요:
Anthropic git 프록시 사용
Anthropic 관리 git이라고도 하는 Anthropic git 프록시를 사용하면 러너 이미지에는 세션 자체를 위한 SSH 키, 자격 증명 헬퍼,.netrc 또는 기타 git 자격 증명이 필요하지 않습니다. 대신 러너는 Anthropic에 세션의 git을 제공하도록 요청합니다. Anthropic이 제공하는 사용자 세션의 경우 러너의 클론과 세션 자체의 페치 및 푸시는 Anthropic을 거치며, Anthropic은 세션 작성자를 위해 저장된 GitHub OAuth 토큰을 사용합니다. 봇 및 에이전트 세션은 Anthropic이 세션의 git을 제공하는 방식에서 다룹니다.
git 프록시는 켜지 않는 한 꺼져 있습니다. 자체 자격 증명으로 git 호스트에 도달하는 러너에는 필요하지 않으며, 해당 러너의 git은 모든 git 호스트에서 작동합니다.
그 대가로 git 프록시는 러너가 지원하는 범위를 제한하고 러너에 필요한 사항을 변경합니다:
- github.com 전용: Anthropic은 세션의 모든 저장소가 github.com에 있는 경우에만 세션을 제공하며, git 프록시는 아직 GitHub Enterprise Server를 지원하지 않습니다. git 프록시를 사용하는 러너에서 다른 git 호스트의 저장소가 있는 세션은 시작에 실패합니다.
- 세션의 저장소에 대한 자격 증명만 제공: Anthropic은 세션에 포함된 저장소에 대해서만 git 자격 증명을 제공하며, 같은 git 호스트의 다른 저장소에는 제공하지 않습니다. 비공개 서브모듈, 패키지 관리자가 git으로 가져오는 의존성, 또는 다른 저장소에 있는 플러그인 마켓플레이스는 Anthropic으로부터 자격 증명을 받지 못합니다. 세션을 만드는 사람들에게 세션을 만들 때 세션에 필요한 모든 저장소를 추가하도록 요청하세요. 클라우드 예약 작업의 각 실행은 해당 작업의 저장소를 포함하는 세션이므로, 각 작업의 소유자에게 실행에 필요한 모든 저장소를 추가하도록 요청하세요.
- 브랜치 푸시만 가능: 브랜치를 삭제하는 푸시는 실패하며, 태그와 같은 다른 종류의 ref로의 푸시도 실패합니다. 푸시가 업데이트할 수 있는 브랜치는 GitHub 프록시를 참조하세요.
- 연결된 GitHub 계정: 저장소가 있는 사용자 세션을 만드는 각 사람에게 GitHub를 연결하도록 요청하세요. 본인의 연결이 없으면 해당 세션은 시작되지 않습니다.
-
--capacity 1: git 프록시는 러너 프로세스당 하나의 세션이 필요하므로 병렬 처리를 위해 더 많은 복제본을 실행하세요. Anthropic git 프록시 켜기에서 요구 사항을 나열합니다. - 전역 git 구성 대체: 러너는 자신이 실행되는 사용자의 전역 git 구성을 삭제하고 대체합니다. 전용 사용자로 또는 컨테이너에서 실행하세요.
-
호스트 푸시를 위한 호스트 자격 증명: 러너의
--push-outcome-on-release푸시와post-session훅이 수행하는 모든 푸시는 여전히 러너 호스트 자체의 git 자격 증명과github.com으로의 네트워크 경로를 사용합니다. 해당 자격 증명은 전역 git 구성에 두지 말고, 제공하는 방법은 이미지에 git 구성 제공을 참조하세요. - 세션별 결정: Anthropic은 러너의 각 세션에 대해 git을 제공할지 결정하며, 제공하지 않는 세션은 시작에 실패합니다. git 프록시를 사용하는 러너에서 세션이 시작에 실패하는 경우에서 원인을 다룹니다.
safe.directory와 같이 비밀이 아닌 git 설정은 시스템 git 구성에 보관하세요.
Anthropic git 프록시 켜기
--use-anthropic-git-proxy로 러너를 시작하기 전에 러너 호스트가 다음 각 요구 사항을 충족하는지 확인하세요. 용량 또는 git 요구 사항이 충족되지 않으면 러너는 시작을 거부합니다:
- Claude Code v2.1.267 이상: 이전 버전은 플래그를 수락하지만 Anthropic에 git 제공 요청을 보고하거나
Registering as opted in라인을 출력하지 않으므로 Anthropic은 해당 세션을 제공하지 않습니다. - 기본값인
--capacity 1: 각 러너 프로세스는 한 번에 하나의 세션을 처리하므로 병렬 처리를 위해 더 많은 복제본을 실행하세요. - Git 2.32 이상: 더 오래된 git은 러너가 git 프록시를 위해 설정하는 세션별 git 구성을 무시합니다.
--use-anthropic-git-proxy를 추가하거나 러너의 환경에서 CLAUDE_RUNNER_USE_GIT_PROXY=1을 설정하세요. 러너 호스트의 셸에서 실행하는 다음 명령은 git 프록시를 켠 상태로 빠른 시작의 러너를 시작합니다:
Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy)를 출력합니다. 그런 다음 Anthropic은 해당 러너의 각 세션에 대해 git을 제공할지 결정합니다. 제공하는 각 세션에 대해 러너는 governed git ACTIVE를 포함하는 [runner:session] 라인을 로그에 기록합니다. 대신 세션이 시작에 실패하면 git 프록시를 사용하는 러너에서 세션이 시작에 실패하는 경우를 참조하세요.
Anthropic이 세션의 git을 제공하는 방식
Anthropic이 제공하는 세션의 경우 러너의 클론과 세션 자체의 페치 및 푸시는 Anthropic을 거치며, 세션 자체의 단기 토큰으로 인증됩니다:- 사용자 세션: Anthropic은 세션 작성자를 위해 저장된 GitHub OAuth 토큰을 사용합니다.
- 봇 및 에이전트 세션: Anthropic은 조직의 GitHub App 설치 토큰을 사용합니다.
- URL 다시 쓰기:
--git-host-rewrite및--git-ssh-rewrite는 git 프록시가 제공하는 저장소에 효과가 없습니다.
git 프록시를 사용하는 러너에서 세션이 시작에 실패하는 경우
--use-anthropic-git-proxy로 시작한 러너에서는 Anthropic이 세션의 git을 제공하지 않으면 세션이 시작에 실패합니다. 러너의 로그에서 /git_proxy/를 포함하는 api.anthropic.com 주소를 언급하는 git 오류를 찾으세요.
Claude Code v2.1.267 이상의 러너는 각 세션에 대해 Anthropic이 세션의 git을 제공하는 경우 governed git ACTIVE를 포함하는 [runner:session] 라인을, 제공하지 않는 경우 the server withheld Anthropic-managed git for this session을 포함하는 [runner:warn] 라인 하나를 로그에 기록합니다. 다음 경우 중에서 보고 있는 라인을 찾으세요:
governed git ACTIVE와withheld라인 모두 없음: Claude Code v2.1.267 이전의 러너는 두 라인 모두 기록하지 않으며, Anthropic은 해당 세션을 제공하지 않습니다. 버전 고정에 따라 러너를 v2.1.267 이상으로 업데이트하세요.withheld라인: Anthropic이 세션을 제공하지 않았습니다. 이전에 git 프록시로 작동하던 러너도 사용자 측의 변경 없이 이런 방식으로 실패할 수 있습니다.- 저장소가 github.com에 없음: GitHub Enterprise Server와 같은 다른 git 호스트에 저장소가 하나라도 있는 세션은 해당 github.com 저장소를 포함하여 제공되지 않습니다. 해당 환경의 러너에 대해 Anthropic git 프록시를 끄세요.
- 모든 저장소가 github.com에 있음:
withheld라인의 세션 ID와 함께 Anthropic 계정 팀에 실패를 보고하세요. Anthropic은 자체 측에서 이유를 기록합니다.
remote: access denied by the git proxy를 포함하는 라인: Anthropic이 제공하는 세션도 거부될 수 있습니다. 예를 들어 조직 정책이 세션의 git 액세스를 거부하거나 세션이 저장소에 대한 권한이 없는 경우입니다. 그러면 러너의 로그에remote: access denied by the git proxy를 포함하는 라인이 표시되며, 해당 라인의 나머지 부분에 이유가 나옵니다.GitHub authentication required: 세션 작성자가 claude.ai에서 작동하는 GitHub 연결이 없을 때 표시됩니다. 세션의 클론이 실패하고 git 오류는GitHub authentication required. Please reconnect your GitHub account.로 표시됩니다. 해당 사용자에게 claude.ai 설정에서 GitHub를 연결하거나 다시 연결하도록 요청하세요.
Anthropic git 프록시 끄기
환경의 세션이 GitHub Enterprise Server와 같이 github.com이 아닌 git 호스트의 저장소를 사용하는 경우 해당 환경의 러너에 대해--use-anthropic-git-proxy를 끄세요.
1
플래그 제거
러너의 명령에서
--use-anthropic-git-proxy를 제거하세요. 파드 사양이나 Compose 파일과 같은 러너의 환경에서 CLAUDE_RUNNER_USE_GIT_PROXY를 설정한 경우 그곳에서 제거하세요. 셸에서는 설정을 해제하세요:2
러너에 git 자격 증명 제공
github.com을 포함하여 러너의 세션이 사용하는 모든 git 호스트에 대해 프롬프트 없이 작동하는 자격 증명을 제공하세요.
--use-anthropic-git-proxy가 설정되어 있는 동안 러너가 러너 사용자의 전역 git 구성을 삭제했으므로 그곳에 있던 자격 증명은 사라졌습니다. 이미지에 자격 증명을 제공하거나 checkout 수명 주기 훅을 사용하세요.3
네트워크 경로 열기
러너가 러너의 세션이 사용하는 각 git 호스트에 포트 443 또는 22로 도달할 수 있도록 허용하세요. 네트워크 요구 사항의 git 호스트 행을 참조하세요.
4
러너 다시 시작
러너가 git 프록시 없이 등록되도록 러너를 다시 시작하세요. 그런 다음 실패한 각 세션을 다시 시작하세요.
GitHub CLI 없이 GitHub API 액세스
러너 이미지에 GitHub CLI가 포함되어 있지 않은 경우 Claude Code가 내장gh를 제공할 수 있으므로 Claude는 여전히 풀 리퀘스트를 열고, 댓글을 달고, CI 결과를 읽을 수 있습니다. 내장 gh는 Anthropic 관리 git을 사용하는 러너를 위한 것입니다. GitHub의 REST API를 호출하는 gh api 명령 하나를 지원합니다. 러너 이미지에 Claude Code v2.1.287 이상이 필요합니다.
다음 명령은 gh pr create 대신 풀 리퀘스트를 엽니다. 내장 gh는 현재 저장소에 대해 {owner} 및 {repo}를 채웁니다:
- 자격 증명: 내장
gh는 REST 요청을 Anthropic 관리 git을 통해 보내며, Anthropic 관리 git이 Anthropic 측에서 GitHub 자격 증명을 제공하므로 이미지에는 이를 위한 GitHub 토큰이 필요하지 않습니다 - 어떤 세션이 사용할 수 있는지: Anthropic은 Anthropic 관리 git이 세션의
gh를 제공할지 세션별로 결정합니다. 제공하는 경우 러너가 세션에 대해 기록하는[runner:session] governed git ACTIVE라인에gh_path_shim=true가 표시됩니다. 제공하지 않는 경우 세션에는gh가 없습니다 jq:--jq가 작동하도록 하려면 이미지에jq를 설치하세요CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: 세션 환경에서 이를 설정하면 Claude Code는 내장gh를 제공하지 않으며 세션에는gh가 없습니다
Anthropic 관리 git으로 개인 인증 기관 신뢰
이 섹션은 세션이 Anthropic 관리 git을 사용하는 러너의 환경에서GIT_SSL_CAINFO 또는 GIT_SSL_NO_VERIFY를 설정한 경우에 적용됩니다. 설명하는 처리에는 러너가 Claude Code v2.1.283 이상을 실행해야 합니다.
러너의 git이 TLS 검사 프록시가 서명하는 개인 인증 기관(CA)과 같은 개인 인증 기관을 신뢰해야 하는 경우 일반적인 접근 방식은 다음과 같이 작동합니다:
- 시스템 인증서 저장소: 러너 호스트의 시스템 인증서 저장소에 CA를 설치하면 git은 변수 없이 신뢰합니다.
GIT_SSL_CAINFO: CA의 PEM 파일로 설정하세요(예:GIT_SSL_CAINFO=/etc/ssl/corp-ca.pem).GIT_SSL_NO_VERIFY: 재서명 프록시 뒤에서는 도움이 되지 않습니다. 러너의 자신의 클론은 Anthropic 관리 git을 통해 변수가 설정된 경우에도 인증서를 확인하므로 git이 다른 두 접근 방식 중 하나를 통해 CA를 신뢰할 때까지 해당 클론이 실패합니다.
command 훅은 세션의 환경으로 시작하므로 세션 내부의 git이 얻는 것을 얻습니다:
GIT_SSL_CAINFO: git이 Anthropic 관리 git에 대해 확인하는 것은 git이 실행되는 위치에 따라 다릅니다:- 러너의 자신의 클론 및 페치: 변수 없이 실행되고 러너가 작성하는 세션별 인증서 파일에 대해 Anthropic 관리 git을 확인합니다. 러너는 러너 호스트의 시스템 CA 번들과 파일의 인증서로 해당 파일을 빌드합니다.
- 세션 내부의 git: 변수 대신 파일을 이름으로 지정하는
http.sslCAInfo구성을 얻고, Anthropic 관리 git을 세션별 파일에 대해 확인하는http.<url>.sslCAInfo항목을 얻습니다. checkout및post-session훅: 변수를 변경하지 않고 상속합니다.
GIT_SSL_NO_VERIFY: 어떤 인증서 확인이 꺼져 있는지는 git이 실행되는 위치에 따라 다릅니다:- 러너의 자신의 클론 및 페치: 변수 없이 실행되고 제시된 인증서를 확인합니다.
- 세션 내부의 git: 변수 대신
http.sslVerify=false구성을 얻으므로 다른 호스트에 대한 확인이 꺼져 있습니다. 또한 Anthropic 관리 git에 대한 확인을 유지하는http.<url>.sslVerify=true항목을 얻습니다. checkout및post-session훅: 세션이 Anthropic 관리 git에 리포지토리를 가지고 있을 때 변수 대신http.sslVerify=false구성을 얻습니다. 또한 Anthropic 관리 git에 대한 확인을 유지하는http.<url>.sslVerify=true항목을 얻습니다.
/etc/ssl/certs/ca-certificates.crt 또는 /etc/pki/tls/certs/ca-bundle.crt에서 시스템 CA 번들이 필요합니다. 또한 러너의 사용자가 읽을 수 있고, PEM CERTIFICATE 블록을 보유하며, 최대 1 MiB인 GIT_SSL_CAINFO 파일이 필요합니다. 러너가 세션별 파일을 빌드할 수 없으면 did not build the certificate file을 포함하는 [runner:warn] 라인과 이유를 기록합니다. 그러면 git은 Anthropic 관리 git에 대해 파일을 그대로 사용합니다. 라인이 이름을 지정하는 것을 수정하세요.
Anthropic 관리 git을 사용하는 각 세션에 대해 러너는 governed git: GIT_SSL_CAINFO is set 또는 governed git: GIT_SSL_NO_VERIFY is set로 시작하는 [runner:warn] 라인도 기록합니다. 라인은 러너가 자신의 git, 세션 내부의 git, 수명 주기 훅에 대해 해당 변수로 수행한 작업을 나타냅니다. 변경해야 할 사항이 있는지 여부로 끝납니다.
Claude Tag 채널 세션에 github.com 저장소 추가
Claude가 Claude Tag 채널 세션에 github.com 저장소를 추가할 수 없는 경우, 러너가 해당 저장소를 클론할 수 있더라도 채널이 해당 저장소에 액세스할 수 있는지 확인하세요. 이 액세스는 러너의 git 자격 증명이 아니라 GitHub 및 Claude 조직의 설정에서 비롯됩니다.1
Claude GitHub App 설치
저장소를 소유한 GitHub 조직 또는 계정에 Claude GitHub App을 설치하고, 설치에 해당 저장소를 포함하세요.
2
GitHub 조직 또는 계정 연결
Organization settings > Git providers에서 해당 조직 또는 계정을 Claude 조직에 연결하세요. GitHub 조직도 소유한 Claude 조직의 Owner가 연결할 수 있습니다. GitHub 조직 연결을 참조하세요.
3
저장소 권한 부여
Claude Tag 관리자 설정에서 채널 또는 채널을 포함하는 더 넓은 범위, 즉 해당 Slack 워크스페이스 또는 Slack 전체에 저장소를 부여하세요. Owner 역할 또는 Claude Tag Admin 권한이 있는 사람이 부여할 수 있습니다. 채널 관리자도 자신의 GitHub 계정이 저장소의 관리자인 경우 자신의 채널에 저장소를 추가할 수 있습니다. 저장소 액세스 권한 부여를 참조하세요.
개인 네트워크에 대한 git URL 다시 쓰기
저장소 URL은 제어 평면에서 HTTPS로 도착하며 git 호스트의 호스트 이름이 있습니다. GitHub Enterprise의 경우 claude.ai에서 GitHub Enterprise 통합에 대해 구성한 호스트 이름입니다. 두 개의 반복 가능한 플래그는 클론 전에 이러한 URL을 다시 작성합니다:--git-host-rewrite <from>=<to>: 분할 수평 DNS의 경우, Anthropic이 외부 호스트 이름을 통해 git 호스트에 도달하지만 러너는 내부 호스트 이름을 사용해야 합니다.--git-ssh-rewrite <host>: SSH만 허용하는 git 호스트 또는 SSH 키만 있는 러너 호스트의 경우입니다. 예를 들어 github.com 저장소를 SSH로 클론하려면 러너 명령에--git-ssh-rewrite github.com을 추가하세요. 그러면 러너는https://github.com/owner/repo를git@github.com:owner/repo로 다시 작성합니다. SSH는 프롬프트 없이 연결되어야 합니다.
--git-ssh-rewrite에 내부 호스트 이름을 나열하세요. 체크아웃을 완전히 제어하려면 checkout 수명 주기 훅을 사용하세요.
러너 이미지 빌드
Anthropic은 사전 빌드된 러너 이미지를 게시하지 않습니다.claude 바이너리 주위에 자신의 이미지를 빌드하고, 리포지토리가 필요로 하는 모든 도구 체인을 계층화하세요: 언어 런타임, 컴파일러, 패키지 관리자, MCP 사이드카.
아래 레시피는 --capacity 4를 사용하므로 하나의 컨테이너는 동일한 잠긴 소유자의 최대 4개의 동시 세션을 제공합니다. 이는 강화 섹션의 세션별 컨테이너 격리를 제공하지 않습니다: 환경을 프로덕션 시스템에 연결하기 전에 레시피를 --capacity 1로 실행하여 세션당 하나의 컨테이너를 사용하거나 온디맨드 러너를 사용하세요. 이는 또한 환경 시크릿을 세션 실행 호스트에서 벗어나게 유지합니다. 이 레시피 중 하나에 Anthropic git 프록시를 추가하면 --capacity를 1로 변경하세요.
이 Dockerfile은 최소한의 시작점입니다. Claude가 Slack에 답장을 게시하도록 하는 훅을 포함하여 Anthropic의 컨트롤 플레인이 Claude Tag 세션에 제공하는 훅이 python3를 실행하므로 이 Dockerfile은 python3를 설치합니다. Claude Tag 세션을 제공하는 모든 이미지에서 python3를 PATH에 유지하세요:
linux-x64를 linux-arm64로 바꾸거나, Alpine과 같은 musl 기반 이미지에서 linux-x64-musl 또는 linux-arm64-musl로 바꾸세요. musl 이미지가 필요로 하는 추가 패키지는 Alpine Linux 설정을 참조하세요. URL은 표준 Claude Code 릴리스 위치이므로 바이너리 무결성 및 코드 서명에 설명된 대로 릴리스의 서명된 매니페스트에 대해 다운로드된 바이너리를 확인할 수 있습니다. 러너는 Claude Code 버전 2.1.224 이상이 필요하며, Claude Tag 세션을 제공하려면 2.1.229 이상이 필요합니다. 이미지를 빌드한 다음 레지스트리로 푸시하고 아래 레시피에서 참조하세요:
stable 릴리스 번호를 조회하고 빌드 인수로 전달하므로, 새로운 안정 릴리스 후에 동일한 명령을 실행하면 더 새로운 바이너리로 다운로드 계층을 다시 빌드합니다. 재현 가능한 빌드를 위해 특정 릴리스를 고정하려면 버전 번호를 CLAUDE_CODE_VERSION으로 직접 전달하세요. 새로 출시된 모델이 필요로 하는 것과 같이 안정 채널보다 더 새로운 릴리스가 필요할 때 조회 URL에서 stable을 latest로 바꾸세요.
세션에 대한 CPU 및 메모리 크기 조정
러너 프로세스가 아닌 실행하는 세션에 대해 러너의 컨테이너 또는 호스트 크기를 조정하세요. 러너 자체는 작업을 폴링하고, 각 세션의 체크아웃을 준비하고, 수명 주기 훅을 실행하고, 세션 프로세스를 시작하고 감독합니다. 로드는 세션에서 나옵니다: 각 세션은 Claude Code 프로세스와 빌드, 테스트 스위트, 패키지 설치, MCP 서버와 같이 시작하는 모든 것입니다. 한 세션의 경우 다음 값으로 시작하세요. Kubernetes 요청 및 제한 또는 플랫폼의 동등한 것으로 표시되며, 요구사항이 아닌 시작점으로 취급하세요:- 메모리: 각각 4 GiB의 요청 및 제한. Claude Code의 시스템 요구사항의 4 GB 최소값을 충족합니다. 두 개를 같게 유지하여 스케줄러가 컨테이너의 전체 메모리를 고려하도록 합니다. 컨테이너가 메모리 제한에 도달하면 커널이 내부 프로세스를 종료하여 세션을 중간에 종료할 수 있습니다.
- CPU: 2 CPUs의 요청 및 4 CPUs의 제한. 세션이 빌드 중에 요청 위로 버스트할 수 있습니다. 커널은 CPU 제한에서 프로세스를 종료하는 대신 스로틀링하므로 제한에서 세션이 더 느리게 실행되지만 계속 실행됩니다.
resources 블록으로 이러한 시작 값을 설정하세요:
--capacity를 사용하여 한 번에 실행하는 세션 수를 제한합니다. CPU 또는 메모리를 세션 간에 나누지 않으므로 러너의 세션은 컨테이너의 CPU 및 메모리를 공유합니다. 한 세션의 메모리 사용으로 다른 세션이 종료되지 않도록 하려면 자체 메모리 제한을 둔 러너당 한 세션을 실행하세요.
-
러너당 한 세션: 각 컨테이너에 한 세션의 값을 제공하세요.
--capacity 1에서 이 크기 조정을 사용하세요. 강화 섹션이 권장하고, 온디맨드 러너의 경우, 값을spawn-runner훅이 제출하는 워크로드(예: Kubernetes Job의 pod 템플릿)에 설정합니다. -
러너당 여러 세션:
--capacity가 1보다 클 때 한 세션의 값에 용량을 곱하세요. 컨테이너에서 한 번에 최대 그 많은 세션이 실행될 수 있기 때문입니다. Kubernetes 및 Docker Compose 레시피는 CPU 또는 메모리 제한 없이--capacity 4를 실행하므로 실행하는 용량에 맞게 크기가 조정된 제한을 추가하세요. -
한 호스트의 여러 러너 컨테이너: 각 러너는
--capacity에 대해 자신의 세션만 계산하므로 러너 용량의 합계에 맞게 호스트 크기를 조정하세요:- 메모리: 한 세션의 메모리에 합계를 곱한 다음 호스트 자체 프로세스가 사용하는 양을 더합니다
- CPU: 한 세션의 CPU 요청에 합계를 곱합니다. 모든 세션이 동시에 버스트할 수 있게 하려면 대신 CPU 제한을 곱합니다.
--base-dir경로를 사용하세요.
각 세션에 자체 메모리 제한 부여
메모리가 부족할 때 한 세션이 다른 세션을 종료하지 않도록 하려면 각 러너를 한 세션의 메모리로 설정된 자체 메모리 제한 아래에서--capacity 1로 실행하세요:
- 컨테이너에서: 컨테이너당 하나의 러너를 실행하고 Kubernetes의
limits.memory, Docker의--memory, Compose 파일의mem_limit와 같은 컨테이너의 메모리 제한을 설정하세요. 호스트 자체의 메모리가 부족해지면 커널이 호스트의 어디에서든 프로세스를 종료할 수 있으므로 컨테이너 제한의 합계를 호스트 메모리 미만으로 유지하세요. - 컨테이너 없이 systemd에서: 러너의 유닛에
MemoryMax를 설정하고 호스트당 하나의 러너를 실행하세요. 한 파일 시스템에서 동일한--base-dir를 사용하는 러너는 그곳에 클론된 저장소를 공유하기 때문입니다. 예를 들어checkout훅 없이--capacity 1에서 한 저장소에 대한 이들의 세션이 동시에 같은 체크아웃에서 작업할 수 있습니다. 한 호스트에서 여러 러너를 실행하려면 각각에 컨테이너를 제공하세요.
--capacity를 1보다 크게 설정하면 세션들이 하나의 제한을 공유합니다. 세션들이 제한에 도달하면 커널은 어떤 프로세스를 종료할지 세션 단위로 선택하지 않으므로 한 세션의 메모리 사용으로 다른 세션이 종료될 수 있습니다.
Kubernetes에서는 kubelet의 singleProcessOOMKill 설정의 어느 값으로도 세션을 분리할 수 없습니다. 래퍼 스크립트의 메모리 ulimit으로도 세션의 총 메모리를 제한할 수 없습니다. 커널은 ulimit -v와 같은 값을 각 프로세스에 개별적으로 적용합니다.
프로세스 및 스레드 제한에 여유 공간 남기기
프로세스 및 스레드 제한의 크기를 정하려면 한 세션에 필요한 양을 측정하고 제한을 공유하는 세션 수를 곱하세요. 해당 세션들이 제한에 도달하면 커널이 새 프로세스와 스레드를 거부하므로 어느 세션에서든 명령, 빌드, 테스트가 실패할 수 있습니다. 거부된 호출은EAGAIN을 반환하며, 그 표준 메시지는 Resource temporarily unavailable입니다.
한 세션이 최대 300개의 프로세스와 스레드에 도달하고 네 개의 세션이 제한을 공유한다고 가정합니다. 합쳐서 1,200개가 필요하며, 제한을 2,400으로 하면 두 배가 필요한 빌드를 위한 여유가 생깁니다.
각 제한을 설정하는 위치
이 표의 각 제한은 프로세스뿐 아니라 스레드도 계산하며,pids.max는 cgroup의 제한을 담는 파일입니다:
TasksMax를 설정하지 않으면 systemd는 DefaultTasksMax를 적용하며, 이 값의 기본값은 kernel.pid_max, kernel.threads-max, 루트 cgroup의 pids.max 중 가장 낮은 값의 15%입니다.
한 세션의 피크 측정
자체 러너에 대한 수치를 얻으려면 호스트(Kubernetes에서는 노드)에서 한 세션을 측정하세요:1
러너 하나만 시작
컨테이너 또는 systemd 유닛에 다른 것 없이 러너 하나를
--capacity 1로 시작하되, 호스트와 환경에서 단독으로 실행되도록 하세요:- 호스트: 다른 러너를 실행하지 않는 호스트를 사용하세요. 그러면 다음 단계에서 해당 프로세스를 찾아낼 수 있습니다.
- 환경: 예를 들어 테스트용으로 생성하는 환경에서 유일한 러너가 되도록 하세요. 세션은 러너가 아닌 환경으로 라우팅됩니다.
2
러너의 cgroup 디렉터리 찾기
컨테이너 내부가 아닌 호스트에서
ps 등을 사용하여 러너의 프로세스 ID를 찾으세요. 해당 ID의 /proc/<pid>/cgroup을 읽습니다::pids:를 포함하는 줄이 있는 경우: 호스트가 cgroup v1을 사용하며, 디렉터리는/sys/fs/cgroup/pids뒤에:pids:다음의 텍스트를 붙인 것입니다- 유일한 줄이
0::로 시작하는 경우: 호스트가 cgroup v2를 사용하며, 디렉터리는/sys/fs/cgroup뒤에0::다음의 텍스트를 붙인 것입니다
3
대표적인 빌드를 실행하고 피크 기록
한 세션으로 대표적인 빌드를 실행하고 러너의 cgroup 디렉터리에서 피크를 가져오세요:
- 디렉터리에
pids.peak가 있는 경우: 빌드가 끝난 후 파일을 읽습니다 - 디렉터리에
pids.peak가 없는 경우: 빌드가 실행되는 동안pids.current를 1초마다 읽고 가장 높은 값을 유지합니다. 두 번의 읽기 사이의 피크는 포착되지 않으므로 이 값은 낮은 추정치로 취급하세요.
4
제한을 공유하는 세션 수를 곱하기
- 한 러너의 Pod, 컨테이너 또는 유닛: 피크에 러너의
--capacity를 곱합니다 - 여러 러너가 공유하는 제한: 피크에 해당 러너 용량의 합계를 곱합니다
5
결과보다 높게 제한 설정
제한을 높이면 더 무거운 빌드를 위한 여유가 생기고, 낮추면 포크 폭탄을 더 빨리 멈출 수 있습니다.
pids.max 제한 확인
pids.max에 도달했는지 확인:dmesg등을 사용하여 호스트의 커널 로그에서cgroup: fork rejected by pids controller in과 cgroup 경로를 포함하는 줄을 검색하세요. 커널은 해당 cgroup에서 프로세스나 스레드를 처음 거부할 때만 이를 로그에 기록하므로 전체 로그를 검색하세요.- 적용 중인 제한 확인: 러너의 cgroup 디렉터리와 그 상위의 각 디렉터리에서
pids.max를 읽으세요. 가장 낮은 값이 적용 중인 제한이며,max는 해당 수준에 제한이 없음을 의미합니다.
Kubernetes
러너는 기본적으로 포트 8080에서GET /healthz를 제공하며, --health-port로 구성할 수 있으므로 Kubernetes 프로브는 추가 설정 없이 작동합니다. 엔드포인트는 프로세스가 살아있을 때마다 200을 반환하므로 아래 프로브는 막힌 프로세스가 아닌 죽은 프로세스를 감지합니다. 폴링을 중지한 러너를 잡으려면 /metrics의 last_poll_age_seconds 시리즈에 대해 경고하세요. 아래 Deployment는 Kubernetes Secret에서 환경 시크릿을 마운트하고, 활성 및 준비 프로브를 /healthz로 가리키며, 90초 종료 유예 기간을 설정합니다. 종료 타이밍에서 유예 기간이 중요한 이유를 참조하세요.
매니페스트는 러너 컨테이너에 CPU 또는 메모리 resources를 설정하지 않습니다. 실행하는 용량에 맞게 크기가 조정된 블록을 추가하세요. 세션에 대한 CPU 및 메모리 크기 조정에서 설명합니다.
claude-runners 네임스페이스에 있습니다. 먼저 네임스페이스를 생성하세요:
(umask 077 && cat > ./environment-secret)을 실행하고, 시크릿을 붙여넣고, Enter를 누른 다음 Ctrl-D를 누르세요. 그런 다음 Secret을 생성하고 파일을 삭제하세요:
Docker Compose
아래 Compose 서비스는 종료될 때마다 러너를 재시작합니다. 이는 충돌과 드레인 후 정상 종료를 모두 다룹니다. Docker 재시작 정책은 쓰기 가능한 계층이 그대로 있는 동일한 컨테이너를 재시작하므로 러너는 강화 태세가 권장하는 새로운 파일 시스템이 아닌 재사용된 파일 시스템에서 돌아옵니다. 평가를 위해 이 레시피를 사용하고, 프로덕션의 경우 실행당 컨테이너를 재생성하거나 이를 수행하는 오케스트레이터를 사용하세요. Docker는 계속 종료되는 컨테이너를 재시작하기 전에 더 오래 기다리며, 상한선까지 도달합니다. 따라서 시작할 수 없는 러너는 이 레시피에서 타이트 루프로 계속 재시작되지 않습니다. 러너가 종료될 때는 그런 일이 발생할 때 확인할 사항을 설명합니다.종료 타이밍
SIGTERM 이후 러너는 오케스트레이터가 러너를 종료하기 전에 세션을 깔끔하게 종료할 시간이 필요합니다. 러너는 필요한 시간을 시작 시 로그에 기록하며, 기본 설정에서는 그 줄에 This runner needs up to 80s가 포함됩니다. 오케스트레이터의 중지 타임아웃을 최소한 그 초 수 이상으로 설정하세요. Kubernetes에서는 terminationGracePeriodSeconds, Docker Compose에서는 stop_grace_period, 또는 사용하는 플랫폼의 동등한 설정입니다. Kubernetes 기본값은 30초이므로 이 설정이 없으면 러너가 완료되기 전에 pod를 중지할 수 있습니다.
SIGTERM을 받으면 러너는 새 세션을 받지 않습니다. 그런 다음 --defer-shutdown-max-min을 설정하여 지연하지 않는 한, 드레인이라고 하는 정상 종료를 시작합니다. 드레인은 세 단계로 이루어집니다:
- 러너는 아직 실행 중인 턴이 완료되도록 최대
--drain-wait-sec초(기본값0)까지 대기합니다. - Claude가 아직 실행 중이던 명령을 포함하여 각 세션의 프로세스 트리를 종료합니다. 단, 셸 명령이 종료된 후에도 여전히 실행 중인 프로세스는 종료하지 않습니다.
post-session수명 주기 훅을 실행합니다.
post-session 훅이 아직 커밋되지 않은 작업을 저장하는 동안 다른 러너가 세션을 가져가지 않습니다.
로그에 기록되는 시간은 다음 값의 합계입니다:
- 1단계의
--drain-wait-sec, 기본값 0초 - 2단계의
--session-stop-grace-sec, 기본값 5초 - 3단계의
--post-session-hook-timeout-sec, 기본값 60초 - 고정된 15초의 여유 시간
--push-outcome-on-release가 설정된 경우 추가 30초
--capacity를 높여도 이 시간은 늘어나지 않습니다.
다음 플래그 중 하나를 설정하는 경우 더 많은 시간을 허용하세요:
--retire-at사용 시: 은퇴 시간과 호스트가 중지되는 시간 사이에 일반적인 턴이 완료될 만큼의 시간, 러너 수명 주기에서 설명하는 백그라운드 작업 대기 시간, 그리고 로그에 기록된 시간을 더한 만큼의 여유를 두세요. 은퇴 시간은 시작할 때마다 계산하세요(예:date +%s+ 러너의 의도된 수명).--defer-shutdown-max-min사용 시: 중지 타임아웃은 구성한 분과 드레인이 시작되기 전의 추가 대기 시간(기본 설정에서 75초)도 포함해야 합니다. 첫 신호 이후 드레인 연기에서 두 가지를 모두 설명합니다. 러너는 이 더 긴 시간도 시작 시 로그에 기록합니다.
재시작 전에 실행 중인 턴이 완료되도록 하기
롤링 재시작 전에 실행 중인 턴이 완료되도록 하려면--drain-wait-sec을 설정하고, 중지 타임아웃을 최소한 그 대기 시간에 80초를 더한 값 이상으로 늘리세요. 기본 대기 시간 0에서는 재시작이 아직 실행 중인 턴을 중단하며, 세션은 푸시되지 않은 작업 없이 다른 러너에서 재개됩니다.
--drain-wait-sec: 완료되도록 하려는 턴과 백그라운드 작업에 걸리는 시간 정도로 설정합니다. 이 값은 최댓값이므로, 턴과 작업이 먼저 완료되면 대기가 일찍 끝납니다.- 중지 타임아웃: Kubernetes에서는
terminationGracePeriodSeconds, Docker Compose에서는stop_grace_period입니다. 예를 들어--drain-wait-sec 120을 사용하는 경우 Kubernetes 또는 Docker Compose 레시피의90을200으로 바꾸세요. - 다른 종료 설정을 변경한 경우: 80초는 기본 설정 기준입니다. 사용하는 플래그로 러너 하나를 시작하고, 시작 시 출력되는
This runner needs up to줄의 숫자 이상을 사용하세요. 이 숫자에는 대기 시간이 포함됩니다.--defer-shutdown-max-min을 설정한 경우에는 다음 시작 줄에서Size the stop timeout to at least뒤에 나오는 더 큰 숫자를 사용하세요. 종료 타이밍에 이 숫자를 늘리는 항목이 나열되어 있습니다.
재시작 전 상태 메시지 게시
세션을 중단시킬 재시작 전에 Owner가 Cloud environments 관리자 페이지에서 해당 환경에 상태 메시지를 게시하도록 하세요. claude.ai/code에서 멤버는 해당 환경의 세션에서 이 메시지를 확인합니다. Owner가 게시할 때 만료 시간을 설정하도록 하거나, 러너가 다시 가동되면 메시지를 제거하도록 하세요.첫 신호 이후 드레인 연기
재시작하는 러너가 드레인하는 대신 보유한 세션을 최대n분 동안 계속 제공하도록 하려면 --defer-shutdown-max-min <n>을 설정하세요. 첫 SIGTERM 또는 SIGINT에서 러너는 새 작업을 받지 않고 보유한 세션을 계속 제공합니다. 폴링을 계속하여 제어 평면이 이러한 세션을 재큐하지 않도록 합니다. Claude Code v2.1.238 이상이 필요합니다.
첫 신호 후 러너가 보유한 세션에 어떤 일이 발생하는지
첫 신호를 따르는 처음 두 단계에서 러너는 세션을 릴리스하고, 릴리스된 세션은 사용자가 다음 메시지를 보낼 때 새로운 러너에서 재개됩니다. 첫 신호부터 세어서 러너는 세 단계를 거칩니다:- 처음
n분 동안: 러너는 세션을 정상적으로 제공하고--startup-timeout-min및--kill-session-after-min을 계속 적용합니다.--release-idle-session-min도 설정하면 러너는 사용자가 그 오래 유휴 상태인 모든 세션을 릴리스합니다. 없으면 유휴 세션은 러너에 남아 있습니다. n분이 끝나면: 러너는 여전히 보유한 모든 세션을 릴리스합니다. 유휴 상태이거나 아닙니다. 러너는 중간 턴 세션의 턴이 끝날 때까지 대기하고 턴의 배경 작업을 위해 최대 60초를 더 대기한 후 해당 세션을 릴리스합니다.- 사후 릴리스 유예가 끝나면: 러너는 여전히 보유한 모든 세션을 드레인하고 제어 평면은 각 드레인된 세션을 즉시 다른 러너로 재큐합니다. 사후 릴리스 유예는
n분이 끝날 때 시작되며 기본값에서 75초입니다.--drain-wait-sec을 60초 이상으로 설정하면 사후 릴리스 유예는--drain-wait-sec+ 15초입니다.
--defer-shutdown-max-min 없이 첫 신호에서처럼 즉시 드레인합니다. 드레인이 진행 중이면 다음 신호는 러너를 강제 종료합니다. 이는 두 번째 신호 또는 사후 릴리스 유예가 드레인을 시작했는지 여부와 관계없이 유지됩니다.
중지 타임아웃 크기 조정
호스트의 중지 타임아웃에 세 부분의 합계를 최소한 제공하세요: 구성하는n분, 사후 릴리스 유예, 종료 타이밍에서 설명하는 드레인. 기본 설정에서 사후 릴리스 유예는 75초이고 드레인은 최대 80초가 걸리므로 n분 + 155초를 허용하세요. 러너는 --defer-shutdown-max-min이 설정되었을 때마다 시작 시 이 합계를 인쇄합니다.
중지 타임아웃이 러너가 완료되기 전에 끝나면 러너가 여전히 보유한 세션은 post-session 훅을 받지 않습니다. 러너는 등록 해제되지 않으므로 제어 평면이 해당 세션을 재큐하는 데 몇 분, 때로는 그 이상이 걸릴 수 있습니다. 중지 타임아웃을 그 합계로 제공할 수 없으면 --defer-shutdown-max-min을 설정하지 않은 상태로 두어 러너가 첫 신호에서 드레인하도록 합니다.
실행 중인 post-session 훅에 도달하는 것
post-session 훅과 Claude 세션 자식은 각각 자신의 POSIX 프로세스 그룹에서 실행되며, 러너와 분리되어 있으므로 중지 메커니즘이 다르게 도달합니다:
- 러너가 이미 드레인 중일 때
SIGTERM: 러너를 즉시 강제 종료하여 드레인의 나머지를 건너뜁니다.--defer-shutdown-max-min없이 이는 러너가 받는 두 번째SIGTERM입니다. 아무것도 실행 중인post-session훅에 신호를 보내지 않으므로 고아를 채택하는 init 프로세스가 있는 베어 호스트에서 훅은 스스로 완료되지만 감독 없이 완료됩니다: 타임아웃 예산이 더 이상 적용되지 않으며, 닫힌 로그 파이프에 쓰면SIGPIPE로 종료될 수 있으므로 그런 환경에서 강제 종료에서 생존해야 하는 훅은 자신의 출력을 파일로 리디렉션해야 합니다. 이 페이지의 컨테이너 레시피에서 러너는 컨테이너의 PID 1이고 그 종료는 컨테이너를 종료하며, systemd의 기본KillMode=control-group아래에서 cgroup 전체 킬은 Cgroup 전체 킬 항목에서 설명하는 대로 훅에도 도달합니다. 두 경우 모두 강제 종료를 훅에 치명적인 것으로 취급하고 대신 유예 기간에 의존하세요. - 프로세스 그룹 전체 신호(예: 래퍼 스크립트의
kill -- -<pid>, 셸 작업 제어, 또는 그룹 전체 감시자): 러너 및 중간checkout훅 서브프로세스에 도달합니다. 의도적으로 그룹 연결 상태를 유지하지만 실행 중인post-session훅 또는 세션 자식에는 도달하지 않습니다. - Cgroup 전체 킬(예: systemd의 기본
KillMode=control-group또는terminationGracePeriodSeconds가 만료될 때 Kubernetes가 전체 컨테이너에 전달하는SIGKILL): 훅을 포함한 모든 것에 도달합니다. 프로세스 그룹 격리는 이에 대해 보호하지 않으므로 유예 기간은 전체 드레인을 포함해야 합니다. - 훅의 자신의 타임아웃: 훅이
--post-session-hook-timeout-sec을 초과하면 러너는 훅의 전체 프로세스 그룹에SIGTERM을 보낸 다음 2초 후SIGKILL을 보내므로 훅이 포크한 워커(예: tar, rsync, git)는 래퍼 셸과 함께 종료되고 고아로 생존하지 않습니다. 러너의 감독은 훅의 stdio가 닫히면 끝납니다: 자신의 출력을 파일로 리디렉션하고SIGTERM단계를 능가하는 워커는 러너의 범위를 벗어납니다.
post-session 훅의 수를 기록하므로 조용한 드레인과 스냅샷 중간 드레인을 구분할 수 있습니다.
기본 디렉토리 및 용량을 러너 간에 동일하게 유지
러너가 세션 중간에 죽으면 서버는 세션을 재큐하고 환경의 다른 러너가 이를 선택합니다. 해당 러너는 자신의--base-dir 및 --capacity에서 체크아웃 경로를 파생합니다: --capacity 1은 --base-dir 아래에 직접 체크아웃하고, 1보다 큰 --capacity는 대신 세션별 worktree를 사용합니다. 동일한 환경의 러너가 두 플래그 중 하나라도 다른 값을 사용하면 재개된 세션의 작업 디렉터리가 변경되고, 에이전트가 이전에 기록한 절대 경로(편집, 도구 호출, 또는 자신의 노트)는 더 이상 존재하지 않는 위치를 가리킵니다.
환경의 모든 러너에서 동일한 --base-dir 및 --capacity를 사용하고, 인스턴스 ID 또는 호스트 이름과 같은 호스트별 값을 사용하지 마세요. Kubernetes 및 Docker Compose 레시피처럼 두 플래그를 모두 명시적으로 전달하여 어느 값도 기본값에 의존하지 않도록 하세요.
기본 디렉토리는 --base-dir 참조 행이 기록하는 예외를 제외하고 /workspace로 기본값입니다. 러너는 쓰기 액세스가 필요합니다. 시작 시 등록하기 전에 러너는 디렉토리를 생성하고 쓸 수 있는지 확인하고, 할 수 없으면 cannot create or write to base directory로 종료합니다. root로 시작된 러너는 기본값 /workspace를 자체 생성합니다. root가 아닌 러너의 경우 디렉토리를 생성하고 러너를 시작하기 전에 러너의 사용자에게 소유권을 제공하거나 --base-dir을 해당 사용자가 이미 소유한 디렉토리로 가리키세요.
사전 준비된 체크아웃 재사용
대규모 저장소의 경우 클론이 세션 시작 시간의 대부분을 차지할 수 있습니다. 콜드 클론을 건너뛰려면 러너가 자체 클론을 보관하는 경로에 직접 클론을 제공하세요.checkout 훅이 없으면 러너는 <base-dir>/<repo-owner>/<repo>에 저장소당 하나의 정규 클론을 유지하고 세션 간에 재사용합니다:
--capacity 1에서: 러너는 요청된 ref를 가져오고,HEAD를 분리한 다음, 해당 ref로 하드 리셋합니다. 변경 사항이 적을 때는 거의 즉시 완료됩니다.--capacity가 1보다 클 때: 러너는 해당 클론으로 가져온 다음, 각 세션마다 그 클론에서 별도의 worktree를 체크아웃합니다. 사전 준비된 클론은 다운로드 시간은 절약하지만 체크아웃 시간은 절약하지 않습니다.
- 이미지에 클론: 러너 이미지를 해당 경로에 빌드합니다. 모든 새로운 컨테이너는 디스크를 재사용하지 않고 준비된 클론으로 시작합니다.
- 지속적인 볼륨에 클론:
--lock-to-account로 한 사용자의 계정에 사전 잠긴 러너에서--base-dir을 지속적인 볼륨으로 가리키므로 디스크는 해당 계정만 제공합니다. 사전 잠긴 러너는 Claude Tag 채널 세션을 절대 선택하지 않으므로 이 옵션은 이들을 제공하는 러너에 적용되지 않습니다.
-
클론 깊이:
--single-branch사용 여부와 관계없이--depth없이 클론을 빌드하세요. 러너는origin원격이 있는 클론으로 가져올 때CLAUDE_RUNNER_FETCH_DEPTH를 설정하더라도--depth를 절대 전달하지 않습니다. 전체 클론을 사용하면 각 세션은 클론에 없는 커밋만 다운로드합니다. 얕은 클론을 사용하면 클론의 가장 오래된 커밋 이전에 갈라진 브랜치의 세션은 해당 브랜치의 전체 기록을 다운로드합니다. -
추적된 변경 사항은 리셋되고, 추적되지 않은 파일은 유지됩니다:
--capacity 1에서 각 세션은 이전 세션의 추적된 수정 사항을 지우는 하드 리셋으로 시작하지만, 러너는git clean을 실행하지 않으므로 잠금된 소유자의 이전 세션에서 생긴 추적되지 않은 파일은 트리에 남아 있습니다. -
세션별 디렉토리도 유지됩니다: 체크아웃 옆에 러너는 실행하는 모든 세션에 대해
<base-dir>/_sessions/아래에 세션별 항목을 생성합니다. 세션의 Claude 설정 디렉토리는 대화 기록의 로컬 복사본을 보유합니다. 그 옆에는 세션이 있을 때 세션의 업로드된 파일이 있습니다. 세션 디렉토리도 거기에 있습니다: 세션이 실행되는 동안 세션별 worktrees 및checkout훅 체크아웃을 보유하며, Claude가 그 안에 작성한 다른 모든 것을 유지합니다. 기본적으로 러너는 세션이 끝날 때 이들을 제자리에 두므로 러너 프로세스보다 오래 지속되는 디스크에서 누적됩니다. 모든 세션은 러너 자신의 사용자로 실행되므로 해당 디스크가 제공하는 나중의 모든 세션이 이들을 읽을 수 있습니다. 지속적인--base-dir을 유지하면 해당 성장에 대해 볼륨의 크기를 조정하세요. 동일한 파일 시스템에서 러너를 다시 시작하는 모든 설정(예: Docker Compose 레시피)에도 동일하게 적용됩니다. -
--remove-session-state를 사용하면 세션별 디렉토리가 유지되지 않습니다:--remove-session-state로 러너를 시작하여 세션이 끝날 때 각 세션의 세션별 디렉토리를 삭제하도록 합니다. 삭제는 최선의 노력입니다: 러너가 정리 실행 전에 종료되면 디렉토리가 유지됩니다. 정규 클론 및 세션이 호스트의 다른 곳(예: 임시 디렉토리)에 작성한 파일은 관계없이 유지됩니다. -
git 프록시를 사용하면 리셋이 체크아웃이 됩니다:
--use-anthropic-git-proxy를 사용하면 러너는 각 세션 전에 클론의.git/을 정제합니다. 객체 저장소와 얕은 상태는 유지하고 인덱스는 삭제합니다. 인덱스가 없으므로 각 세션은 거의 즉시 완료되는 리셋 대신 전체 워킹 트리 체크아웃으로 시작합니다. 러너는 여전히 저장소를 다시 클론하지 않고 기존 클론으로 가져옵니다. 프록시에서는 submodule 사전 준비가 지원되지 않습니다. -
git 프록시를 사용하면
reftable사전 준비는 ref를 잃습니다: 러너가 각 세션 전에.git/을 정제할 때.git/refs/는 유지하고.git/reftable/디렉토리는 제거합니다. git의reftableref 형식으로 사전 준비된 클론은 이 과정에서 ref를 잃으므로, 대신 git의filesref 형식으로 클론을 사전 준비하세요. - 긴 클론은 해결 방법이 필요하지 않습니다: 러너는 각 git 작업을 120초 진행 없음 감시자 및 30분 하드 캡으로 제한하며, 플랫 타임아웃이 아니므로 진행을 계속 보고하는 느린 콜드 클론이 완료됩니다.
러너가 클론을 사용하는지 확인
러너가 클론을 사용하는지 확인하려면--log-level debug로 러너를 시작하고 해당 저장소에서 세션을 시작하세요. 러너는 Prefetched repo found at과 클론의 경로를 로그에 기록합니다. 대신 No prefetched repo, fresh init at을 로그에 기록하면 해당 줄의 경로에서 사용 가능한 클론을 찾지 못한 것입니다. 클론이 해당 경로에 있고, 러너의 사용자가 소유하며, origin이라는 이름의 원격이 있는지 확인하세요.
러너가 클론을 찾았는데도 세션이 여전히 느리게 시작되면 클론이 얕은 클론인지 확인하세요.
버전 고정
각 세션의 자식 프로세스는 기본적으로 러너 자신의 바이너리를 실행하고, 러너는 생성하는 세션 내에서 자동 업데이트를 끕니다. 이 기본 설정에서는 모든 세션이 호스트에 설치하거나 이미지에 빌드한 버전을 실행합니다. 호스트 수준 업데이트는 러너가 다음 번에 시작할 때 적용됩니다. 세션이 실행할 버전과 버전이 변경되는 시점을 선택합니다.- 버전을 고정하기 전에: 세션이 사용하는 모든 모델에 대해 모델이 필요로 하는 Claude Code 버전을 확인하세요. 모델이 세션에서 실행 중인 버전보다 최신 버전을 필요로 하는 경우, 서버는 해당 모델에 대한 요청을 Claude Code does not support this model로 거부합니다.
- 플릿을 한 버전으로 유지하려면: 이미지를 고정된 버전으로 빌드하거나 베어 호스트에 특정 버전을 설치하고 자동 업데이트를 비활성화하세요.
- 고정 플릿을 업그레이드하려면: 현재 버전과 설치할 버전 사이의 변경 로그 항목을 읽은 다음, 새로운 버전을 설치하거나 이미지를 다시 빌드하고 러너를 재시작하세요.
- 온디맨드 러너를 업그레이드하려면: 현재 버전과 설치할 버전 사이의 변경 로그 항목을 읽은 다음,
spawn-runner훅이 시작하는 이미지를 변경하세요. 새로 생성되는 각 러너는 새 버전을 받습니다.--min-idle이 시작한 대기 러너를 포함하여 이미 실행 중인 러너는 종료될 때까지 기존 버전을 유지합니다. 해당 러너의 작업 지시는 일회용이므로 재시작하지 마세요. - 세션이 다른 바이너리를 시작하는 경우:
--exec-path또는command훅을 사용하여 러너 자신의 바이너리가 아닌 다른 Claude Code 바이너리를 시작하는 경우, 세션은 해당 바이너리의 버전을 실행합니다. 러너를 업데이트할 때 해당 바이너리도 함께 업데이트하세요. - 플러그인: 플러그인 마켓플레이스도 자동 업데이트되지 않습니다. 바이너리가 고정된 상태로 유지되는 동안 플러그인이 자동 업데이트되도록 하려면 러너의 환경에서
FORCE_AUTOUPDATE_PLUGINS=1을 설정하세요.
플릿 확장
오케스트레이터는 러너를 추가하거나 제거할 시기를 결정합니다. 소유자별 러너 잠금 때문에 최소 복제본 수는 동시에 활성화될 것으로 예상되는 사용자 및 Claude Tag 에이전트의 수입니다.--capacity는 소유자 간이 아닌 한 소유자의 세션 내에서 병렬 처리를 제어합니다.
두 가지 확장 접근 방식을 사용할 수 있습니다:
- 고정 플릿: 정적 러너 복제본 세트를 실행하고 각 러너가 제공하는 Prometheus 메트릭에서 확장하세요.
- 온디맨드 러너:
claude self-hosted-runner orchestrator서브명령을 실행하세요. 이는 Anthropic에서 사용 가능한 러너 없이 대기 중인 세션을 폴링하고spawn-runner훅을 호출하여 세션당 하나를 부팅합니다. 온디맨드 러너를 참조하세요.
고정 플릿에서 유휴 세션 해제
모든 러너가 온라인 상태인데도 고정 플릿에서 세션이 계속 대기 중이라면 유휴 세션이 원인일 수 있습니다. 기본적으로 세션은 턴이 끝난 후에도 슬롯을 유지하며, 해당 러너는 그 세션의 소유자에게 잠긴 상태로 남습니다. 팀의 클라우드 예약 작업이 실행할 때마다 새 세션을 시작한다면, 그 유휴 세션들이 모든 슬롯을 채울 수 있습니다. 확인하려면 각 러너의 Prometheus 메트릭에서claude_code_self_hosted_runner_session_idle_seconds를 읽으세요. 러너가 보유한 각 세션에는 고유한 시리즈가 있습니다. 세션이 사용 중일 때는 0이고, 그렇지 않으면 유휴 상태가 된 이후 경과한 초입니다. 허용할 유휴 시간을 정하세요. 작업 도중 사람들이 잠시 멈추는 시간보다 길고, 예약 작업의 다음 실행이 시작되기 전에 각 실행이 해제될 만큼 짧아야 합니다. 시리즈를 이 시간과 비교하세요.
- 해당 시간을 초과하는 시리즈(예: 30분의 경우
1800): 유휴 세션이 해당 슬롯을 점유하고 있으므로--release-idle-session-min을 설정하세요. - 모든 시리즈가 해당 시간 미만: 그만큼 오래 유휴 상태인 세션이 없습니다. 러너를 추가하거나, 유휴 상태가 되지 않는 세션에 대해
--kill-session-after-min을 설정하세요. - 시리즈가 없는 러너: 해당 러너를 점유한 세션이 없습니다. 문제 해결의 세션이 계속 대기 중인 경우를 참조하세요.
- 메트릭이 응답하지 않는 러너: 프로세스가 종료되었을 수 있습니다. 러너가 종료될 때의 정상으로 보이는 환경을 참조하세요.
--release-idle-session-min과 해당 시간(분 단위)으로 시작하세요(예: --release-idle-session-min 30). 그러면 러너는 예약 작업이 시작했든 사람이 시작했든 관계없이 그만큼 오래 유휴 상태로 남은 세션을 모두 해제합니다.
- 푸시되지 않은 작업: 해제된 세션은 다음 메시지에서 재개되지만, 푸시하지 않은 작업은 포함되지 않습니다. 커밋을 유지하려면 재개된 세션에서 푸시되지 않은 작업 손실을 참조하세요.
- 러너 종료: 마지막 세션을 해제한 러너는 정상 종료를 시작합니다. 배포 강화에 설명된 대로 수퍼바이저가 새 파일 시스템에서 러너를 다시 시작하도록 하세요.
- 유휴 상태가 되지 않는 세션: 일부 세션은 유휴 상태로 간주되지 않으므로
--kill-session-after-min도 설정하세요.
알려진 문제 및 제한 사항
다음은 이 릴리스의 제한 사항이며, 해결 방법이 있는 경우 제시합니다.커넥터 트래픽이 네트워크를 벗어남
Anthropic은 러너에서가 아니라 자체 인프라에서 커넥터 도구를 호출합니다. 커넥터 도구는 GitHub, Slack, Linear와 같은 claude.ai 커넥터입니다. Claude가 자체 호스팅 세션에서 커넥터를 사용할 때, 해당 트래픽은 네트워크 경계 내에서 발생하지 않고api.anthropic.com을 통해 이동합니다.
자체 호스팅 세션에서 커넥터를 제외하려면 allowedMcpServers 및 deniedMcpServers 정책 설정으로 필터링합니다. Claude Code는 이러한 설정을 Anthropic이 제공하는 커넥터뿐만 아니라 러너 호스트에서 시드한 서버 및 사용자가 추가한 서버에도 적용하므로, 다른 서버에 대한 허용 목록을 배포하면 Claude Code는 제공된 커넥터도 차단합니다. 제공된 커넥터를 URL 기반 허용 목록과 함께 사용 가능하게 유지하려면 제공된 커넥터의 Anthropic 프록시 경로와 일치하는 항목을 추가합니다:
https://api.anthropic.com/v2/ccr-sessions/*https://api.anthropic.com/v1/code/sessions/*https://api.anthropic.com/v1/code/mcp/*
일부 세션이 유휴 상태로 계산되지 않음
백그라운드 작업을 보유한 세션이 완료되지 않으면 유휴 상태로 계산되지 않으므로--release-idle-session-min은 해당 세션의 슬롯을 해제하지 않습니다. 실행 중인 도구 호출 내에서 요청된 승인을 기다리는 세션도 유휴 상태로 계산되지 않습니다. 항상 --kill-session-after-min을 함께 설정하여 어떤 세션도 슬롯을 무한정 보유할 수 없도록 하는 하드 백스톱으로 사용합니다.
--kill-session-after-min은 폭주 세션에 대한 백스톱입니다. v2.1.260 이상의 러너에서 제한에 도달한 세션은 즉시 종료되지 않습니다. 러너는 기본값 15분의 유예 기간을 제공하며, SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS로 변경할 수 있습니다:
- 세션이 사용자를 기다리고 있으면, 러너는 이를 해제합니다. 턴이 끝났으며 백그라운드 작업만 보유하고 있으면, 러너는 해당 작업이 완료될 때까지 최대 60초 동안 기다린 후 이를 해제합니다. 세션은 사용자가 다음 메시지를 보낼 때 재개됩니다.
- 턴이 여전히 실행 중이면, 러너는 턴이 완료될 때까지 또는 세션이 다음으로 사용자를 기다릴 때까지 기다린 후 해제합니다.
- 유예 기간이 끝났을 때 세션이 여전히 러너에 있으면, 러너는 이를 종료하고 실행 중인 턴의 작업은 손실됩니다. 실행 중인 도구 호출 내에서 요청된 승인을 기다리는 턴은 세션이 기간을 초과하는 한 가지 방법입니다.
--kill-session-after-min 480으로 8시간 동안 가장 긴 예상 세션 위에 플래그를 설정합니다. 유휴 상태가 된 대화에서 슬롯을 해제하려면 대신 --release-idle-session-min을 사용합니다.
추가 제한 사항
- 재개된 세션이 푸시되지 않은 작업을 손실함: 새로운 러너는 저장소를 시작 브랜치에서 다시 클론하므로, 세션이 푸시하지 않은 작업은 손실됩니다.
- 커밋된 작업을 보존하려면: 환경의 모든 러너에
--push-outcome-on-release를 설정합니다. 플래그가 없는 러너는 세션을 시작 브랜치에서 재개하기 때문입니다. 플래그가 있는 러너는 해제하기 전에 세션의 결과 브랜치를 최선의 노력으로 푸시하며, 재개된 세션은 해당 커밋에서 시작됩니다. 푸시에는 Anthropic 관리 git을 사용하는 러너를 포함하여 러너 호스트 자체의 git 자격 증명이 사용됩니다. checkout훅을 사용하는 경우:checkout라이프사이클 훅을 통해 체크아웃된 저장소는 푸시되지 않습니다. 대신post-session훅에서 해당 저장소의 스냅샷을 만듭니다.- 플래그를 활성화하기 전에: 소스 원격의
claude/*refs에 푸시할 수 있는 사람을 제한합니다. 재개 시 러너는 이전에 푸시된 브랜치를 누가 푸시했는지 확인하지 않고 가져옵니다. - 커밋되지 않은 변경 사항은 여전히 손실됨: Claude가 응답을 마쳤을 때 커밋하고 푸시하도록 요청하려면 세션이 작업을 푸시하도록 프롬프트 지정을 참조합니다.
- 커밋된 작업을 보존하려면: 환경의 모든 러너에
- 러너 손실로 중단된 명령이 다시 실행될 수 있음: 세션이 다른 러너에서 재개되면, Claude에게는 중단된 도구 호출이 그 결과를 알 수 없다는 내용의 결과와 함께 표시됩니다.
- 세션 중간에 추가한 저장소는 클론에 실패할 수 있음: Claude는 HTTPS를 통해
git clone으로 저장소를 클론합니다.--use-anthropic-git-proxy가 없는 러너에서는 호스트에서 해당 저장소를 읽을 수 있는 수단이 없으면 git 인증 오류로 클론이 실패합니다. 가능하면 세션을 만들 때 세션에 필요한 모든 저장소를 선택합니다. - 일부 커넥터는 자체 호스팅 세션에 나타나지 않음: claude.ai 설정에서 아직 연결하지 않은 커넥터는 자체 호스팅 세션에 나열되지 않으며, 세션은 연결하라는 메시지를 표시하지 않습니다. 먼저 설정에서 연결한 후 새로운 세션을 시작합니다. 이미 실행 중인 세션에 커넥터를 추가해도 Claude에서 해당 도구를 사용할 수 없습니다. 새로 추가된 커넥터를 선택하려면 새로운 세션을 시작합니다.
- 누군가 세션을 보관 또는 삭제하거나, 세션 메뉴에서 러너를 해제하는 경우:
--push-outcome-on-release푸시에 의존할 수 없습니다. 이러한 경우 커밋을 저장하려면post-session훅에서HEAD를 복구 브랜치로 푸시합니다. 해당 문서의 예제에서 시작하는 경우, 예제는 스냅샷 커밋 후에만 푸시하므로 커밋되지 않은 항목이 없을 때도 푸시하도록 수정합니다.
문제 보고
자체 호스팅 환경의 문제는 Anthropic 계정 팀에 문의합니다.문제 해결
안내에 따른 진단을 받으려면 러너 호스트에서 doctor 하위 명령을 실행합니다. doctor 하위 명령은 러너의 로그와 상태가 첨부된 대화형 Claude Code 세션을 시작합니다. 세션이 환경, 해당 환경의 러너, 대기 중인 세션을 조회할 수 있도록 먼저 해당 호스트에서claude auth login으로 로그인합니다. 로그인하지 않은 경우, 예를 들어 호스트가 API 키로 인증하는 경우에는 로컬 상태 엔드포인트, 메트릭, 러너의 로그로만 제한되며, 로그는 --log-file로 러너를 시작한 경우에만 읽습니다.
-
러너가 환경에 나타나지 않음: 호스트가 HTTPS를 통해
api.anthropic.com에 연결할 수 있는지, 환경 시크릿이 최신인지, 호스트 시계가 실제 시간과 5분 이내로 차이 나는지 확인합니다. 차이가 더 크면 인증이 실패합니다. 인증이 실패하면 러너는 거부 사유와 함께[runner:fatal]을 로그에 기록합니다. -
러너가 시작 시
cannot create or write to base directory와 함께 종료됨: 러너가--base-dir(기본값은/workspace)을 생성하거나 쓸 수 없습니다. 러너 간에 기본 디렉터리와 용량을 동일하게 유지하기에 설명된 대로 디렉터리의 소유권을 수정하거나--base-dir이 쓰기 가능한 경로를 가리키도록 합니다. 러너가 대신 기본 디렉터리 확인이 시간 초과되었다는[runner:fatal]을 로그에 기록한다면 디렉터리가 응답하지 않는 NFS 또는 CSI 마운트에 있는 것입니다. 권한보다는 마운트 상태를 확인합니다. 러너는--log-file을 열기 전에 이 두 가지 시작 실패를 모두 stderr에 출력하므로, 로그 파일이 아니라 터미널이나 플랫폼의 컨테이너 로그에서 찾아야 합니다. v2.1.225 이전에는 러너가 시작 시 기본 디렉터리를 확인하지 않았으며, 이 잘못된 구성으로 인해 대신 세션을 가져온 후 세션이 실패했습니다. -
러너가 시작 시
RegisterRunner failed:와 TLS 인증서 오류와 함께 종료됨: 오류는 예를 들어self signed certificate in certificate chain일 수 있습니다. TLS 검사 프록시 뒤에 있는 경우처럼, 러너가 수신한 인증서의 인증 기관(CA)을 신뢰하지 않습니다. 러너의 환경에서NODE_EXTRA_CA_CERTS를 프록시의 루트 CA가 포함된 PEM 파일로 설정합니다. 세션은 이 값을 상속하므로 세션에 다시 설정할 필요는 없습니다. Git은 이 값을 읽지 않습니다. 해당 프록시를 통한 클론의 경우GIT_SSL_CAINFO도 설정합니다. -
시작 스크립트가
Resource temporarily unavailable을 포함하는 쓰기 오류를 출력하고 종료됨: 스크립트가 자신이 시작하는claude self-hosted-runner또는 오케스트레이터 프로세스와 stdout 또는 stderr를 공유하고 있으며, 해당 프로세스가 그 공유 파이프나 소켓을 논블로킹 모드로 전환했습니다. 파이프가 가득 차면 스크립트의 쓰기가 대기하는 대신 이 운영 체제 오류와 함께 실패하며,set -e에서는 스크립트가 종료됩니다. 오류를 없애려면 러너 또는 오케스트레이터의 출력을 스크립트의 사용자가 생성할 수 있는 파일로 보냅니다. 이를 시작하는 명령에>> "$HOME/claude-runner.log" 2>&1을 추가하되, 백그라운드로 보내는&가 있다면 그 앞에 둡니다:스크립트 자체의 출력은 이전과 같은 곳으로 가고, 러너가 stdout과 stderr에 쓰는 내용은 시작 오류를 포함하여 해당 파일로 갑니다. 러너가 stdout과 stderr에 계속 쓰는 동안 로그를 파일에 미러링하기만 하는--log-file만 사용하지 말고 이 셸 리디렉션을 사용합니다. 이미--log-file을 전달하고 있다면 리디렉션이 해당 파일이 받는 내용을 바꾸지 않으므로 그대로 유지합니다. -
오케스트레이터가 TLS 인증서 오류와 함께
poll failed:를 로그에 기록하고 계속 재시도함: TLS 검사 프록시 뒤에 있는 경우처럼, 오케스트레이터가 수신한 인증서의 CA를 신뢰하지 않습니다. 오케스트레이터의 환경에서NODE_EXTRA_CA_CERTS를 프록시의 루트 CA가 포함된 PEM 파일로 설정합니다.spawn-runner훅이 시작하는 러너에도 설정합니다. 예를 들어 로그 줄은poll failed: self signed certificate in certificate chain — retrying in 1s로 끝날 수 있으며, 콜론과 대시 사이에는 TLS 라이브러리의 문구가 들어갑니다. 수정 사항을 확인하려면 오케스트레이터의/healthz본문에서connected가true인지 확인합니다. -
세션이 대기 상태로 유지됨: 모든 온라인 러너가 각각 다른 소유자에게 잠겨 있을 수 있습니다. 각 러너의
claude_code_self_hosted_runner_locked_account메트릭 또는[runner:health]로그 줄의locked_account필드를 확인하여 누가 러너를 점유하고 있는지 확인합니다. 둘 다 러너가act.email클레임을 포함한 세션 토큰을 발급받은 후에만 소유자의 이메일을 표시하며, Claude Tag 에이전트의 세션은 이러한 토큰을 발급받지 않습니다. 클레임이 없으면 러너는locked_account시리즈를 내보내지 않고locked_account=yes를 로그에 기록하는데, 이는 러너가 잠겨 있다는 것은 알려 주지만 어느 소유자에게 잠겨 있는지는 알려 주지 않습니다. 레플리카를 추가하거나, 기존 러너가 드레인되고 다시 시작될 때까지 기다립니다. 환경에서 온디맨드 러너를 사용하는 경우 대신 오케스트레이터를 확인합니다. 온디맨드 러너를 참조하십시오. -
세션을 가져온 직후 세션이 실패함: claude.ai/code에서 세션을 열어 오류를 확인합니다. 오류에 git이 인증하지 못했다고 표시되면 러너의 git 자격 증명을 확인합니다.
--use-anthropic-git-proxy로 시작한 러너에서는 git 프록시를 사용하는 러너에서 세션이 시작되지 않는 경우를 참조하십시오. 쓰기 불가능한 기본 디렉터리는 세션을 실패시키는 대신 시작 시 러너를 중지시킵니다. 이 목록의 러너가 시작 시cannot create or write to base directory와 함께 종료됨 항목을 참조하십시오. -
세션을 가져왔지만 Claude가 첫 메시지에 응답하지 않음: 러너가 프록시를 통해
api.anthropic.com에 연결하는 경우, 세션의/worker/events/stream응답을 도착하는 대로 통과시키도록 프록시를 구성합니다. 러너의 타임아웃을 늘려도 도움이 되지 않습니다. 러너의 로그에서 원인을 확인하려면 세션을 가져왔지만 Claude가 첫 메시지에 응답하지 않는 경우를 참조하십시오. -
--use-anthropic-git-proxy를 설정한 러너에서 세션이 시작되지 않음: 러너의 로그에서access denied by the git proxy또는/git_proxy/를 포함하는api.anthropic.com주소를 명시하는 git 오류를 찾습니다. Anthropic이 세션을 제공했는지 확인하고 원인을 해결하려면 git 프록시를 사용하는 러너에서 세션이 시작되지 않는 경우를 참조하십시오. -
세션이 인증 이그레스 프록시를 통해 네트워크에 연결할 수 없음:
--proxy-authorization-command또는--proxy-authorization-file로 설정한 소스가 실패하거나, 30초 후 시간 초과되거나, 빈 값을 반환하면 러너는 해당 연결에502 Bad Gateway로 응답하고 그 이유를 로그에 기록합니다. 러너는 해당 로그에서 명령의 stderr를 가리며 헤더 값은 절대 로그에 기록하지 않습니다.--proxy-authorization-command를 사용하는 경우 호스트에서 직접 명령을 실행하여 stdout에 전체 헤더 값이 출력되는지 확인합니다. 러너가 대신 시작 시could not start the proxy-authorization listener와 함께 종료된다면 루프백 리스너를 열 수 없었던 것입니다. -
러너가
rejecting the malformed poll response를 포함하는Poll failed줄을 로그에 기록함: 러너가 수신한 작업 폴링 응답의 본문이 큐에서 예상하는 JSON이 아닙니다. 한 가지 원인은 가로채는 프록시나 캡티브 포털이 자체 페이지로 응답하는 경우입니다. 러너가 프록시를 통해 연결한다면api.anthropic.com의 응답을 변경 없이 통과시키도록 프록시를 구성합니다. 러너는 잘못된 형식의 응답을 거부하고, 이를claude_code_self_hosted_runner_poll_errors_total메트릭의transport종류로 집계하며, 세션 수명 주기에 설명된 실패한 폴링 일정에 따라 재시도합니다. 러너는 실행 중인 세션을 계속 제공합니다. v2.1.246 이전에는 러너가 이러한 응답을 빈 작업 큐로 해석했으며, 이로 인해 실행 중인 세션이 종료되거나 러너 자체가 종료될 수 있었습니다. -
세션이
no longer exists on the remote또는couldn't find remote ref와 함께 실패함: 러너는 git의couldn't find remote ref를 삭제된 브랜치로 해석합니다. 브랜치는 여전히 존재할 수 있습니다. git 호스트에서 메시지에 표시된 이름 그대로 브랜치를 찾아본 후, 해당하는 각 원인을 확인합니다.- 브랜치가 없는 경우: 메시지에서 요청하는 대로 브랜치를 복원합니다.
- 브랜치가 존재하고 러너를 관리하지 않는 경우: 러너 관리자에게 전체 메시지를 보내고 브랜치가 존재한다고 알립니다.
- 브랜치가 존재하고 러너에
checkout훅이 있는 경우: 훅의 git 명령이 훅이 0이 아닌 코드로 종료되기 전에 stderr에couldn't find remote ref를 출력할 수 있습니다. 해당 명령을 찾아 그 명령이 요청하는 ref 또는 가져오는 원격을 수정합니다. 러너는 세션이 요청한 리비전을CLAUDE_RUNNER_REPO_REF로 훅에 전달합니다. - 브랜치가 존재하고 러너에
checkout훅이 없는 경우: 러너가 직접 브랜치를 가져옵니다.--git-host-rewrite가 저장소에 적용된다면 재작성 대상 호스트에 같은 이름의 브랜치가 있는지 확인하고, 없다면 해당 호스트에 브랜치를 푸시합니다. - 브랜치가 존재하고 러너가 v2.1.301보다 오래된 경우: 다른 실패가 자체 오류를 표시하도록 버전 고정하기에 설명된 대로 러너를 업그레이드합니다. v2.1.301 이전에는 오류 출력에
couldn't find remote ref가 포함된 다른 실패도 삭제된 브랜치로 보고되었습니다.
-
Claude Tag가 Slack에 응답을 게시하지 않는 경우가 있음: 응답 알림은 컨트롤 플레인이 제공하는 훅입니다. 이전 러너는 제공된 훅을 무시하므로
claude --version으로 러너가 Claude Code v2.1.229 이상인지 확인합니다. 훅은python3을 실행하므로 러너 이미지 안에서python3 --version이 성공하는지 확인합니다. 그리고 어떤 설정 파일도 훅을 끄지 않는지 확인합니다. 여기에는managed-settings.json, 러너 사용자의~/.claude/settings.json, 저장소의.claude/settings.json또는.claude/settings.local.json에 있는disableAllHooks, 그리고 관리형 설정에서 훅을 포함하는allowManagedHooksOnly또는strictPluginOnlyCustomization이 해당됩니다. -
세션이 저장소 중 하나 없이 시작됨:
checkout훅이 없는 러너에서는 세션이 읽기만 하는 저장소에 대해 git 호스트가 러너의 액세스 확인을 거부할 수 있습니다. 그러면 러너는 해당 저장소를 건너뛰고, 거부 내용을 명시하는[runner:warn] could not access context source줄을 로그에 기록한 후 나머지 저장소로 세션을 시작합니다. 러너는 명확한 거부인 경우에만 건너뜁니다. 즉, 호스트가 저장소를 찾을 수 없다고 응답하거나, git이 해당 호스트의 자격 증명을 찾지 못하거나, 인증이 실패한 경우입니다. 네트워크 장애, 시간 초과 또는 HTTP403은 여전히 세션 시작을 실패시키며, 세션이 결과를 푸시하는 저장소에 대한 거부도 마찬가지입니다. 건너뛰면 저장소가 하나도 남지 않는 세션은 여전히 러너가 실패시킵니다.--use-anthropic-git-proxy를 사용하는 경우 러너는 git 프록시 자체가 거부한 저장소만 건너뜁니다. 액세스 확인은 세션이 러너에서 시작될 때마다 다시 실행되므로, 러너의 git ID에 읽기 액세스 권한이 생기면 다음 시작 시 저장소를 클론합니다. v2.1.274 이전에는 이러한 거부가 각각 세션 시작을 실패시켰습니다. -
세션이
This runner couldn't read와 함께 시작되지 않음: 러너가 git 자격 증명으로 git 호스트에 저장소를 요청했으나 찾을 수 없다는 응답을 받았습니다. 저장소가 존재하지 않거나 해당 자격 증명으로는 저장소에 접근할 수 없습니다.- 러너를 관리하는 경우: 러너의 git 자격 증명에 저장소에 대한 읽기 액세스 권한을 부여한 후 새 세션을 시작합니다.
- 세션을 시작한 경우: 저장소가 존재하지 않는다면 올바른 저장소로 새 세션을 시작합니다. 러너가 저장소에 접근할 수 없다면 러너 관리자에게 액세스 권한을 부여해 달라고 요청한 후 새 세션을 시작합니다.
-
세션 시작 또는 재개에 몇 분이 걸림: 한 가지 원인은 새로 수행하는 클론입니다. 각 러너를 빈 디스크에서 시작하고
checkout훅이 없는 플릿에서는 러너가 Claude Code를 시작하기 전에 세션의 저장소를 새로 클론합니다. 빈 디스크에서 재개되는 세션도 동일한 클론을 다시 기다립니다. 새 클론을 건너뛰려면 미리 준비된 체크아웃을 제공합니다. 이미 제공하고 있다면 러너가 이를 사용하는지 확인합니다. 러너가 직접 클론을 만들고 세션 하나를 마친 후 종료되는 경우,CLAUDE_RUNNER_FETCH_DEPTH를 기본값인 50개 커밋보다 낮춰 더 적은 기록을 가져옵니다. 훅을 사용하는 경우 훅이 러너의 클론 대신 실행되므로, 훅이 수행하는 작업을 대신 살펴봅니다.claude_code_self_hosted_runner_session_init_duration_seconds메트릭을 확인하여 시작에 걸리는 시간을 파악합니다. -
턴이 401로 실패함: 턴이 Anthropic API의 401 또는 403으로 끝나면 러너는 Anthropic에서 새
CLAUDE_CODE_OAUTH_TOKEN을 가져와 세션에 전달합니다. 실패한 턴은 재시도되지 않습니다. 이 토큰은 수명이 짧으며, 러너는 세션의 stdin을 통해 토큰을 교체합니다. 가져오기가 실패하면 러너는 언제 재시도할지 알려 주는inference_token refresh failed줄을 로그에 기록하고, 세션이 실행되는 동안 계속 재시도합니다. 세션 시작 후 약 30분 뒤부터 모든 호출이 실패하기 시작한다면 래퍼 스크립트가 세션의 stdin을 끊어 토큰 교체가 세션에 도달하지 못하는 것일 가능성이 높습니다. stdin과 파일 디스크립터 3을 연결된 상태로 유지하기를 참조하십시오. v2.1.274 이전에는 러너가 실패한 가져오기를 몇 번 시도한 후 재시도를 중단하고 다음 예정된 가져오기를 기다렸습니다. 실패한 턴은 가져오기를 트리거하지 않았으므로, 다음 예정된 가져오기까지 모든 턴이 401로 실패했습니다. -
claude.ai/code에서 턴이
This model needs a newer version of Claude Code와 함께 실패함: 러너가 세션을 시작할 때 사용하는 Claude Code 버전이 세션의 모델이 요구하는 버전보다 오래되어 API가 요청을 거부합니다. 버전 고정하기에 설명된 대로 러너를 메시지에서 요구하는 버전 이상으로 업그레이드합니다. 그때까지는 세션에서 다른 모델을 선택할 수 있습니다. -
드레인 도중 파드가 종료됨:
terminationGracePeriodSeconds를 러너가 시작 시 로그에 기록하는 값 이상으로 늘립니다. 종료 타이밍을 참조하십시오.
[runner:fatal] 줄을 포함한 수명 주기 로그를 stdout에, 디버그 출력을 stderr에 기록하며, 모두 JSON이 아닌 일반 텍스트 줄입니다. 위의 문제 해결 항목에 설명된 기본 디렉터리 실패는 그 이전에 stderr에 출력됩니다. --log-file(이를 통해 self-hosted-runner doctor가 두 스트림을 추적할 수도 있음) 또는 플랫폼의 로그 수집 기능으로 두 스트림을 모두 캡처합니다.
각 세션의 자식 프로세스는 별도의 디버그 로그를 기록합니다. 실패 시 러너는 claude.ai/code에서 세션과 함께 로그의 마지막 부분을 표시합니다. --remove-session-state로 러너를 시작하지 않았다면, 러너는 실패한 세션의 로그를 디스크에 보관하고 러너 로그에 그 경로를 출력합니다.
러너가 종료되는 경우
온디맨드 러너는 작업 지시가 일회용이므로 다시 시작하지 마십시오. 시작 직후 종료되는 러너는 다른 이유로 종료되는 러너와 다르게 처리해야 합니다.- 정상 종료: 러너가 세션을 마치고 드레인되었거나, 퇴역 시간에 도달했거나, 중지하라는 지시를 받았습니다. 환경이 다시 용량을 확보할 수 있도록 러너를 다시 시작합니다. 이러한 종료는 러너 수명 주기에 설명되어 있습니다.
- 시작 실패: 러너가 주어진 구성이나 호스트로 시작할 수 없어 시작 후 몇 초 만에 종료되며, 다시 시작할 때마다 같은 방식으로 종료됩니다. 더 빨리 다시 시작해도 도움이 되지 않습니다. 누군가가 출력을 읽고 원인을 해결해야 합니다.
- 연결 끊김: 예를 들어 호스트가 절전 상태인 동안처럼 임대 기간보다 오래 Anthropic에 연결할 수 없는 러너는 환경에서 제거될 수 있습니다. 제거된 러너가 다시 연결되면 종료됩니다. 로그에는
runner record gone server-side또는, 더 오래 중단된 경우poll auth failed를 포함하는[runner:fatal]줄이 표시될 수 있습니다. 러너는 스스로 다시 등록하지 않으므로 다시 시작해야 합니다.
시작 실패 인식하기
러너가 시작할 수 없으면 그 이유를 설명하는 줄을 출력한 후 종료됩니다. 대부분의 원인에서 해당 줄에는[runner:fatal]이 포함됩니다. 일부 원인에서는 해당 줄이 대신 error:로 시작하는데, 러너가 플래그를 파싱할 수 없거나, 환경 시크릿을 읽을 수 없거나, 기본 디렉터리를 생성하거나 쓸 수 없는 경우가 이에 해당합니다. 그러면 다음 줄에서 --help를 안내합니다.
대부분의 로그 줄은 타임스탬프와 [self-hosted-runner]로 시작하며, 아래 예시에서는 이를 생략했습니다. 예를 들어 Anthropic git 프록시와 1보다 큰 용량으로 시작된 러너는 다음과 같은 줄을 출력합니다.
--log-file로 설정한 파일에서 해당 줄을 찾습니다. 문제 해결에서 언급한 대로 러너는 로그 파일을 열기 전에 error: 줄을 출력하므로, 해당 줄은 터미널이나 컨테이너 로그에서 찾아야 합니다.
시작 실패를 파악할 때 다음 사항도 도움이 됩니다.
- 아무 줄도 없음: 호스트가 종료시킨 러너는 두 줄 모두 출력하지 않습니다. 출력이
[runner:fatal]줄이나error:줄 없이 끝난다면, 예를 들어 메모리 제한 초과로 인해 호스트나 오케스트레이터가 프로세스를 중지했는지 확인합니다. - 종료 코드: 러너는 시작할 때마다 반복되는 오류를 위한 별도의 종료 코드를 두지 않습니다. 지원되지 않는 플래그 조합과 같은 구성 오류와, 러너 자체의 재시도 동안 API에 계속 연결할 수 없는 경우처럼 저절로 해소될 수 있는 실패에 대해 동일한 코드로 종료됩니다. 재시작 간격을 늘릴지 여부는 러너가 얼마나 빨리 종료되었는지를 기준으로 결정하고, 그 이유는 러너의 출력을 읽어 파악합니다.
- 정상으로 보이는 환경:
--configure-git및 Anthropic git 프록시의 자격 증명 설정과 같은 일부 시작 단계는 러너가 환경에 등록된 후에 실행됩니다. 이러한 단계 중 하나가 실패하면 프로세스가 종료된 후에도 환경이 몇 분 동안 해당 러너를 계속 나열할 수 있으며, 작업을 가져가는 러너가 없는데도 Cloud environments 페이지에 Healthy가 표시될 수 있습니다. 정상으로 보이는 환경에서 세션이 대기 상태로 유지된다면 수퍼바이저가 러너를 계속 다시 시작하고 있는지 확인합니다.
점점 늘어나는 대기 시간으로 다시 시작하기
대기 시간을 점점 늘리는 방법은 수퍼바이저에 따라 다릅니다.-
Kubernetes: 이 페이지의 Deployment는 변경할 필요가 없습니다. 컨테이너가 종료된 후 kubelet은 기본적으로 컨테이너를 다시 시작하기 전에 대기하며, 대기 시간은 재시작할 때마다 상한까지 늘어납니다. 컨테이너가 종료되지 않고 한동안 실행되면 대기 시간이 초기화됩니다.
kubelet은 컨테이너가 짧게만 실행된 경우 정상 종료 후에도 동일한 대기를 적용합니다. 따라서 자주 드레인되는 러너도
CrashLoopBackOff상태를 표시할 수 있으므로, 러너가 시작할 수 없다고 결론 내리기 전에 출력을 읽어 보십시오. 아래 명령은 Deployment의 파드 하나에서 마지막 실행의 출력을 읽습니다.마지막 실행이 시작 실패였다면[runner:fatal]또는error:줄이 출력의 마지막 줄들 사이에 있습니다. 다른 파드의 마지막 실행을 읽으려면deploy/claude-runner대신 해당 파드의 이름을 지정합니다. -
Docker 및 Docker Compose: 이 페이지의 Compose 레시피는 변경할 필요가 없습니다.
restart: always를 사용하면 Docker는 계속 종료되는 컨테이너를 다시 시작할 때마다 상한까지 더 오래 대기합니다. Docker가 컨테이너를 다시 시작한 횟수를 읽는 아래 명령에서<container>를 컨테이너 이름으로 바꿉니다.이 명령은 숫자를 출력합니다. 숫자가 계속 증가한다면 Docker가 러너를 계속 다시 시작하고 있다는 의미입니다. -
systemd 유닛: 기본적으로 systemd는 매 재시작 전에 동일한
RestartSec만큼 대기하며 이를 늘리지 않으므로,Restart=always가 설정된 유닛은 시작할 수 없는 러너를 매번 같은 간격으로 다시 시작합니다. 시작이 유닛의 시작 속도 제한(기본값은 10초에 5회)에 도달할 만큼 빠르게 이루어지면 systemd는 유닛 재시작을 중단합니다. 유닛은 누군가가 다시 시작할 때까지 중지된 상태로 유지되며, systemd는 속도 제한 간격이 지난 후 또는systemctl reset-failed후에 이를 허용합니다.RestartSec은 모든 재시작에 적용되므로 값을 길게 하면 정상 종료 후의 재시작도 지연됩니다. 이 둘의 균형을 맞추는 값을 선택하고, 유닛의 재시작 횟수에 대해 알림을 설정합니다. - 셸 루프 또는 자체 수퍼바이저: 동일한 규칙을 직접 적용합니다. 5초의 대기 시간으로 시작합니다. 1분 이내에 끝난 각 실행 후에는 다음 재시작의 대기 시간을 최대 5분까지 두 배로 늘립니다. 1분 이상 지속된 실행 후에는 다시 5초로 돌아갑니다.
러너가 계속 종료되는 이유 확인하기
러너가 시작 직후 여러 번 연속으로 종료되었다면, 다시 시작하기 전에 멈추고 다음 사항을 확인합니다.- 마지막
[runner:fatal]또는error:줄: 러너가 중지된 이유를 알려 줍니다. 일반적인 원인은 문제 해결에 나열되어 있습니다. - 플래그 조합: Anthropic git 프록시에는
--capacity 1이 필요합니다. 이 페이지의 레시피는 더 높은 용량을 사용하므로, 레시피에 프록시를 추가할 때는 용량을 낮춥니다. - 서비스 환경에서 접근할 수 있는 항목: 러너가 수동으로는 시작되지만 수퍼바이저에서는 실패한다면 사용자, 홈 디렉터리,
PATH, 메모리 제한을 비교합니다.--configure-git과 Anthropic git 프록시에는PATH에 있는 git과 쓰기 가능한~/.gitconfig이 필요합니다. - 환경 시크릿: 시크릿을 취소했거나 잘못 입력한 경우 러너는
RegisterRunner auth failed를 포함하는 줄을 출력합니다. - 환경의 Activity 탭: 환경을 열고 Activity를 선택합니다. 새 러너가 계속 나타나는데 작업을 가져가는 러너가 없다면 수퍼바이저가 러너를 계속 다시 시작하고 있는 것입니다.
세션을 가져왔지만 Claude가 첫 메시지에 응답하지 않는 경우
러너가 세션을 가져왔지만 Claude가 첫 메시지에 응답하지 않고, 러너가 프록시를 통해api.anthropic.com에 연결한다면 프록시가 원인일 수 있습니다. Anthropic은 열린 상태로 유지되는 하나의 스트리밍 응답을 통해 세션에 메시지를 보냅니다. 프록시가 이 응답을 버퍼링하거나 아무것도 전달하기 전에 종료하면 첫 메시지가 도착하지 않습니다. 세션의 다른 요청은 여전히 성공합니다.
이를 해결하려면 프록시가 다음 요청을 처리하는 방식을 변경합니다.
- 응답을 버퍼링하지 않음: 응답을 도착하는 대로 통과시킵니다. 콘텐츠 유형은
text/event-stream입니다. - 응답이 열린 상태로 유지되도록 허용: 응답은 최대 1시간 동안 열려 있을 수 있습니다. 프록시에 유휴 타임아웃이 있다면 15초보다 충분히 길게 유지합니다. Anthropic은 기본적으로 최소 그 간격마다 keep-alive를 보내기 때문입니다.
러너의 로그에서 원인 확인하기
이 단계는 기본값인--log-level info로 실행되는 Claude Code v2.1.287 이상의 러너를 위한 것입니다. 이전 러너는 이러한 줄을 더 늦게 기록하거나 전혀 기록하지 않습니다.
--startup-timeout-min(기본값 15분)이 지날 때까지 기다립니다. 그러면 러너가 세션을 중지하고no child output for를 포함하며— releasing으로 끝나는 줄을 로그에 기록합니다.- 러너의 로그에서
SDKStartup: SSE를 검색합니다. 이러한 줄은 1단계의 줄 몇 줄 뒤에 있는finished: interrupted줄 다음에 나옵니다. 대부분 타임스탬프가 없으므로 로그 수집기에서는 다른 위치에 표시될 수 있습니다. - 다음 표에서 해당 줄을 찾습니다.
로그에 이러한 줄이 하나도 없거나 프록시의 로그에 아무 문제가 없다면 문제를 보고합니다.
세션의 재시도 중지하기
세션은 메시지를 대기 상태로 유지하며, 세션의 소유자를 처리할 수 있는 러너가 세션을 다시 가져옵니다. 프록시를 변경하기 전까지 세션은 매번 같은 방식으로 기다립니다. 이를 중지하려면 claude.ai/code에서 세션을 보관합니다.다음 단계
- 세션 사용자 정의: 래퍼 스크립트, 수명 주기 훅, 온디맨드 러너, MCP 서버, 권한
- 엔드 투 엔드 테스트: CI에서 새로운 러너 이미지를 확인한 후 프로모션
- 참조: 모든 CLI 플래그, 환경 변수, 메트릭