> ## Documentation Index
> Fetch the complete documentation index at: https://code.claude.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Terapkan lingkungan yang di-host sendiri ke produksi

> Jalankan runner yang di-host sendiri dalam produksi: pengerasan keamanan, kontrol egress jaringan, kredensial git, resep Kubernetes dan Compose, serta pemecahan masalah.

> [!NOTE]
>
> Lingkungan yang di-host sendiri berada dalam beta publik pada paket Team dan Enterprise; [Ketersediaan dan batasan](https://code.claude.com/docs/id/self-hosted-environments#availability-and-limitations) mencakup jalur pengaktifan. Halaman ini mencakup menjalankan armada dalam produksi; lihat [quickstart](https://code.claude.com/docs/id/self-hosted-environments-quickstart.md) untuk runner dan sesi pertama Anda.

Sebuah [lingkungan yang di-host sendiri](https://code.claude.com/docs/id/self-hosted-environments) menjalankan [sesi cloud](https://code.claude.com/docs/id/claude-code-on-the-web.md) Claude Code pada runner yang Anda terapkan di dalam jaringan Anda, dan dalam produksi sesi-sesi tersebut menjalankan kode yang diarahkan model atas nama semua orang yang dapat mengirim sesi ke lingkungan. Halaman ini untuk operator yang membawa lingkungan yang berfungsi ke produksi. Ini berfungsi melalui penerapan secara berurutan: apa yang harus dikunci sebelum menghubungkan sistem nyata, egress yang dibutuhkan armada, bagaimana sesi mengautentikasi ke host git Anda, resep penerapan itu sendiri, dan apa yang harus diperiksa ketika sesi berperilaku tidak normal.

## Perkuat deployment Anda

Runner self-hosted mengeksekusi kode arbitrer yang diarahkan oleh model di infrastruktur Anda atas nama setiap orang yang dapat mengirimkan sesi ke lingkungannya. Itu mencakup setiap anggota organisasi Anthropic Anda, dan siapa pun yang dapat memulai sesi channel [Claude Tag](https://claude.com/docs/claude-tag/overview) dalam cakupan yang telah dirutekan oleh Owner ke lingkungan tersebut. Selesaikan setiap item sebelum Anda menghubungkan lingkungan ke sistem produksi:

* **Container ephemeral per sesi**: jalankan setiap proses runner dalam container atau VM baru yang dihancurkan saat proses berakhir, dengan `--capacity 1` dan `--drain-grace-sec 0` default sehingga setiap container melayani tepat satu sesi. Pada kapasitas yang lebih tinggi, atau dengan drain grace positif, satu container melayani beberapa sesi dari [pemilik terkunci](https://code.claude.com/docs/id/self-hosted-environments#key-concepts) yang sama; lihat [Siklus hidup runner](https://code.claude.com/docs/id/self-hosted-environments#runner-lifecycle). Jangan gunakan ulang sistem file di antara restart runner, kecuali dalam penyiapan [checkout yang sudah dipanaskan sebelumnya](#reuse-a-pre-warmed-checkout) yang disengaja, dan jangan pernah lintas pemilik.
  * Saat runner menghentikan sesi, runner tidak mengirimkan sinyal ke proses yang masih berjalan setelah perintah shell-nya selesai, seperti layanan yang berjalan sebagai daemon. Menghancurkan container atau VM akan mengakhiri proses tersebut.
* **Tidak ada kredensial luas di dalam image**: jangan sertakan kunci SSH berumur panjang, kredensial penyedia cloud, atau token akses pribadi yang memberikan lebih dari yang dibutuhkan sesi. Buat kredensial yang digunakan selama sesi, seperti token push atau API, per sesi dari [skrip wrapper](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#wrapper-scripts) Anda. Clone awal terjadi sebelum wrapper berjalan, jadi tangani dengan [hook siklus hidup `checkout`](https://code.claude.com/docs/id/self-hosted-environments-configuration#checkout), atau dengan [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy) jika semua repositori sesi berada di github.com. Untuk keduanya, lihat [Konfigurasikan git](#configure-git).
* **Jauhkan kredensial GitHub milik host dari sesi**: Claude dapat menggunakan kredensial GitHub apa pun yang dapat dibaca oleh sesi, dengan akses apa pun yang diberikan oleh kredensial tersebut. Jauhkan kredensial GitHub milik host runner sendiri yang memiliki cakupan luas dari apa pun yang dapat dibaca oleh sesi. Kredensial semacam itu dapat berupa token akses pribadi, token yang disimpan `gh auth login` untuk akun Anda, atau `GH_TOKEN` di lingkungan runner.
  * **Dengan [git yang dikelola Anthropic](#use-the-anthropic-git-proxy)**: dengan kredensial semacam itu, Claude menjangkau GitHub secara langsung alih-alih melalui git yang dikelola Anthropic.
  * **Tanpa git yang dikelola Anthropic**: kredensial clone dapat tetap berada di dalam image jika Anda membatasinya seketat yang dijelaskan di [Sertakan konfigurasi git di image Anda](#ship-git-config-in-your-image).
* **Jauhkan secret lingkungan dari host yang menjalankan sesi**: secret lingkungan dapat mendaftarkan runner dan mengambil sesi apa pun yang diantrekan pada lingkungan tersebut. Pada armada tetap, secret ini berada di setiap host runner, tempat kode sesi mana pun dapat membaca file secret. Utamakan [runner on-demand](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#on-demand-runners), di mana secret tetap berada di host orkestrator, yang tidak pernah menjalankan kode pengguna, dan setiap runner menerima work order sekali pakai yang mendaftarkan tepat satu runner. Pada armada tetap, perlakukan file secret lingkungan sebagai dapat dibaca oleh setiap sesi dan rotasi secret setelah adanya dugaan kompromi sesi.
* **Tolak egress jaringan secara default**: batasi lalu lintas keluar runner dan container sesi di batas jaringan Anda sendiri pada setiap lingkungan; [Tolak egress secara default](#default-deny-egress) menjelaskan apa yang perlu diizinkan dan alasannya.
* **IAM host dengan hak akses minimal**: identitas komputasi yang terpasang pada host runner, seperti instance profile atau akun layanan node, sebaiknya hanya memberikan apa yang dibutuhkan oleh runner itu sendiri. Sesi sebaiknya memperoleh kredensialnya sendiri melalui skrip wrapper Anda alih-alih mewarisi kredensial host.
* **Blokir endpoint metadata cloud dari sesi**: menjauhkan sesi dari identitas host memerlukan pemblokiran akses mereka ke endpoint metadata cloud, dan kebijakan egress tingkat subnet tidak mencegat lalu lintas metadata link-local, jadi blokir di dalam container itu sendiri:

  * IMDSv2 dengan hop limit satu
  * GKE Workload Identity dengan metadata concealment
  * Penolakan eksplisit untuk `169.254.169.254` di namespace jaringan container sesi

  Pemblokiran ini juga berlaku untuk skrip wrapper dan hook siklus hidup Anda, karena keduanya berbagi container yang sama. Autentikasikan setiap pertukaran token dengan [JWT sesi](https://code.claude.com/docs/id/self-hosted-environments-identity.md) terhadap layanan token Anda sendiri melalui egress yang ada di allowlist, atau gunakan identitas web berbasis file seperti IAM Roles for Service Accounts (IRSA) di Amazon EKS.
* **Isolasi sistem file per runner**: setiap proses runner mendapatkan direktori kerjanya sendiri yang tidak dapat dibaca atau ditulis oleh proses lain di host. Jadikan `--hooks-dir`, skrip wrapper, dan `~/.claude/` milik host read-only bagi sesi, baik dengan membangunnya ke dalam image maupun memasangnya sebagai read-only.
* **Dispatch tidak memiliki kontrol akses per lingkungan**: setiap anggota organisasi Anthropic Anda dapat mengirimkan sesi ke lingkungan mana pun milik organisasi tersebut. Jika Owner [merutekan channel Claude Tag ke lingkungan tersebut](https://code.claude.com/docs/id/cloud-environments.md#set-the-environment-a-claude-tag-channel-uses), siapa pun yang diizinkan oleh [pengaturan akses Claude Tag](https://claude.com/docs/claude-tag/admins/restrict-access#restrict-who-can-use-claude) dapat memulai sesi channel yang berjalan di sana. Secara default, itu adalah siapa pun di workspace Slack yang terhubung, dengan atau tanpa akun Claude. Perlakukan setiap host runner sebagai dapat dijangkau untuk eksekusi kode oleh setiap orang yang dapat mengirimkan sesi ke sana, dan tempatkan di host runner hanya data dan kredensial yang boleh dibaca oleh semua orang tersebut. [`--lock-to-account`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) membatasi sesi akun mana yang dieksekusi oleh host tertentu, tetapi tidak mempersempit siapa yang dapat mengirimkan sesi ke lingkungan tersebut. Untuk menjadikan lingkungan self-hosted sebagai satu-satunya opsi pemilih, seorang [Owner](https://code.claude.com/docs/id/cloud-environments.md#organization-shared-environments) dapat menyembunyikan lingkungan yang di-host Anthropic untuk seluruh organisasi dari [halaman **Cloud environments**](https://claude.ai/admin-settings/cloud-environments).
* **Terapkan guard pengaturan repo**: pilih mode guard dengan [`--confine-repo-settings`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags). Mode default `warn` mencatat pelanggaran ke log dan tetap memunculkan sesi, `enforce` menolak sesi, dan `off` menonaktifkan pemindaian. Runner memindai pengaturan yang di-commit di setiap repositori untuk:

  * Pemberian akses yang mengarah ke luar workspace milik sesi itu sendiri: entri `additionalDirectories`, aturan `Edit`, `Write`, atau `NotebookEdit` di `permissions.allow`, atau entri `sandbox.filesystem.allowWrite` atau `allowRead`
  * Blok `env` yang tidak kosong
  * Override postur operator seperti `sandbox.enabled: false`

  Guard berjalan terlepas dari [`--trust-workspace`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags), dan tidak mencakup hook repositori, `.mcp.json`, atau aturan Bash; lihat [Izin dan persetujuan tool](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#permissions-and-tool-approval) untuk mengetahui tempat pemberian akses tersebut seharusnya berada.

> [!NOTE]
>
> Jika organisasi Anda mengaktifkan [allowlist IP](https://support.claude.com/en/articles/13200993-restrict-access-to-claude-with-ip-allowlisting), tambahkan alamat egress publik runner dan container sesi Anda ke allowlist sebelum Anda memulainya. Jika Anda menjalankan [runner on-demand](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#on-demand-runners), tambahkan juga alamat host orkestrator. Jangan mengandalkan allowlist sebagai kontrol jaringan untuk lalu lintas runner atau sesi. Sebagai gantinya, terapkan penolakan egress secara default di batas jaringan Anda sendiri.

## Persyaratan jaringan

Runner dan anak-anak sesi yang dihasilkannya membuat koneksi keluar ke host di bawah. Batasi egress kontainer sesi ke host ini dan layanan internal spesifik yang perlu dijangkau sesi; [Default-deny egress](#default-deny-egress) mencakup cara dan alasannya.

Host-host ini selalu diperlukan:

| Host | Port | Digunakan untuk |
| :- | :- | :- |
| `api.anthropic.com` | 443, HTTPS; WSS untuk [git yang dikelola Anthropic](#use-the-anthropic-git-proxy) | Bidang kontrol runner dan streaming sesi, inferensi model, flag fitur, analitik produk, pengambilan kunci [JWKS](https://code.claude.com/docs/id/self-hosted-environments-identity.md), penandatanganan commit, dan git yang dikelola Anthropic ketika `--use-anthropic-git-proxy` diatur |
| Host git Anda, seperti `github.com` atau host GitHub Enterprise Anda | 443 atau 22 | Kloning dan push repositori di setiap host git yang digunakan oleh sesi runner. Pada runner yang menggunakan [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), lihat [kapan jalur `github.com` masih diperlukan](#github-com-egress-with-the-anthropic-git-proxy). |

Runner yang menggunakan [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy) merutekan lalu lintas git `github.com`-nya melalui `api.anthropic.com`, sehingga tidak memerlukan jalur host git untuk `github.com`. Runner tersebut tetap memerlukan jalur itu jika Anda mengatur `--push-outcome-on-release` atau melakukan push dari hook `post-session`.

Apakah host-host ini diperlukan tergantung pada konfigurasi Anda:

| Host | Port | Ketika diperlukan |
| :- | :- | :- |
| `downloads.claude.ai` | 443 | Pada waktu instalasi, ketika Anda menginstal atau memperbarui Claude Code di host dengan installer asli; script `install.sh` itu sendiri disajikan dari `claude.ai`. Pada waktu runtime sesi, hanya ketika sesi menginstal plugin dari marketplace Anthropic resmi. |
| `storage.googleapis.com` | 443 | Pada waktu runtime sesi, untuk hitungan instalasi plugin dan metadata yang ditampilkan dalam `/plugin`. |
| `code.claude.com` dan `claude.com` | 443 | Pencarian dokumentasi oleh agen claude-code-guide built-in dan permintaan WebFetch yang telah disetujui sebelumnya selama sesi. Memblokir host-host ini hanya mempengaruhi pencarian dokumentasi. |
| `*.frame.claudeusercontent.com` | 443 | Hanya ketika [alat Artifact](https://code.claude.com/docs/id/artifacts.md#availability) tersedia untuk sesi di organisasi Anda; default bervariasi menurut paket, sesuai tabel ketersediaan di sana. Atur `CLAUDE_CODE_DISABLE_ARTIFACT=1` di runner untuk menjaga alat tetap dinonaktifkan terlepas dari pengaturan organisasi. |
| `registry.npmjs.org` | 443 | Ketika sesi menginstal plugin, baik untuk mengambil paket plugin sumber npm maupun untuk menginstal dependensi Node.js plugin, atau ketika server MCP yang diluncurkan `npx` berjalan |
| `http-intake.logs.us5.datadoghq.com` | 443 | Metrik operasional Anthropic. Hanya ketika `CLAUDE_CODE_BYOC_ENABLE_DATADOG=1` diatur; dimatikan secara default di lingkungan yang di-host sendiri. |
| `browser-intake-us5-datadoghq.com` | 443 | Unggahan laporan kesalahan Anthropic, dikirim hanya ketika [pelaporan kesalahan](https://code.claude.com/docs/id/data-usage.md#telemetry-services) diaktifkan untuk akun sesi. Ditekan oleh `DISABLE_ERROR_REPORTING=1` atau `DISABLE_TELEMETRY=1`. |
| Endpoint penyedia cloud Anda untuk permintaan model, pencarian model, dan pembaruan kredensial, seperti `bedrock-runtime.us-east-1.amazonaws.com` atau `aiplatform.googleapis.com` | 443 | Hanya ketika runner [mengirim permintaan model ke Amazon Bedrock atau Agent Platform Google Cloud](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#send-model-requests-to-bedrock-or-agent-platform) |

Anda tidak perlu menambahkan host-host ini ke allowlist untuk lalu lintas runner atau sesi:

* **`statsig.anthropic.com`, `*.sentry.io`, `claude.ai`, dan `platform.claude.com`**: host-host ini muncul dalam beberapa daftar periksa jaringan enterprise yang lebih lama, tetapi runner tidak menjangkaunya. Pengambilan flag fitur pergi ke `api.anthropic.com`, dan runner mengautentikasi dengan rahasia lingkungan daripada OAuth interaktif.
* **`mcp-proxy.anthropic.com`**: sesi yang di-host sendiri tidak menggunakannya. Ketika pengiriman konektor diaktifkan untuk organisasi Anda, konektor claude.ai organisasi Anda menjangkau sesi melalui `api.anthropic.com`. Lihat [MCP servers](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#mcp-servers).

Alur sisi host berikut memang menjangkau `claude.ai`, jadi jalankan dari host yang egress-nya memungkinkannya daripada memperluas egress kontainer sesi:

* **Installer satu baris**: mengambil `install.sh` dari `claude.ai` pada waktu instalasi.
* **`claude auth login` interaktif**: masuk melalui `claude.ai`, `claude.com`, dan `platform.claude.com`. [Guided setup](https://code.claude.com/docs/id/self-hosted-environments-quickstart.md#run-the-guided-setup), mode signed-in `doctor`, dan [CI dispatch](https://code.claude.com/docs/id/self-hosted-environments-testing.md#authenticate-from-ci) menggunakannya. Browser yang Anda gunakan untuk masuk juga memuat pemeriksaan browser halaman masuk claude.ai dari `hcaptcha.com`, `*.hcaptcha.com`, dan `challenges.cloudflare.com`. Jika organisasi Anda menggunakan single sign-on, browser tersebut juga dapat dialihkan melalui `sso.claude.com`.

### Default-deny egress

Terapkan kontainer runner dan sesi dalam segmen jaringan atau namespace yang lalu lintas keluarnya dibatasi ke host dalam [tabel persyaratan jaringan](#network-requirements), host git Anda, dan layanan internal spesifik yang perlu dijangkau sesi. Produk tidak dapat memverifikasi atau menegakkan ini, jadi terapkan di batas jaringan Anda sendiri di setiap lingkungan. Kode sesi diarahkan model dan dapat mencoba koneksi ke host arbitrer; default-deny egress di lapisan jaringan membatasi di mana upaya tersebut dapat mendarat. Ini berlaku terlepas dari mode izin: set alat yang telah disetujui sebelumnya sudah mencakup `Bash`, jadi egress shell berjalan tanpa prompt bahkan tanpa [mode auto](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#permissions-and-tool-approval).

Untuk detail tentang telemetri mana yang dipancarkan setiap sesi dan cara mematikannya, lihat [Telemetry](https://code.claude.com/docs/id/self-hosted-environments-reference.md#telemetry).

### Autentikasi ke proxy egress

Beberapa proxy egress korporat memerlukan header `Proxy-Authorization` pada setiap koneksi. Token dalam header itu sering berputar terlalu cepat untuk ditulis ke URL proxy yang Anda atur dalam `HTTPS_PROXY`. Atur `HTTPS_PROXY` atau `HTTP_PROXY` ke URL proxy Anda seperti biasa, kemudian atur `--proxy-authorization-command` atau `--proxy-authorization-file` untuk memberi tahu runner di mana membaca nilai header. Kedua flag memerlukan Claude Code v2.1.238 atau lebih baru.

#### Pilih dari mana nilai `Proxy-Authorization` berasal

Pilih flag yang cocok dengan cara Anda menghasilkan token `Proxy-Authorization`:

* **[`--proxy-authorization-command <command>`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags)**: pilih ini untuk token yang Anda hasilkan sesuai permintaan. Runner menjalankan perintah shell dan menggunakan stdout yang dipangkas sebagai nilai header, misalnya `Bearer <token>`.
* **[`--proxy-authorization-file <path>`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags)**: pilih ini untuk token yang proses lain putar di tempat. Runner membaca file dan menggunakan isinya yang dipangkas sebagai nilai header.

#### Konfigurasi yang menolak runner untuk dimulai dengan

Setiap flag juga memiliki bentuk variabel lingkungan, tercantum di sebelahnya dalam [referensi flag CLI runner](https://code.claude.com/docs/id/self-hosted-environments-reference.md#runner-cli-flags). Sebelum runner menghubungi proxy atau bidang kontrol Anda, itu memeriksa flag dan variabelnya, dan menolak untuk memulai dalam tiga kasus:

* **Kedua flag diatur**: satu flag ditambah variabel lingkungan flag lainnya dihitung sebagai pengaturan keduanya.
* **Tidak ada URL proxy**: baik `HTTPS_PROXY` maupun `HTTP_PROXY` tidak memegang URL `http://` atau `https://`. Runner membaca kedua variabel dalam huruf besar atau kecil, dan tidak berkonsultasi dengan `ALL_PROXY`.
* **Flag apa pun diteruskan ke subperintah orchestrator**: `self-hosted-runner orchestrator` tidak menerima flag atau variabel lingkungannya. Teruskan flag ke setiap runner yang dimulai orchestrator.

#### Apa yang diubah runner saat flag proxy-authorization diatur

Dengan flag apa pun yang diatur, runner memulai pendengar miliknya sendiri dan mengirim lalu lintas proxy dari dirinya sendiri, lifecycle hooks-nya, dan sesi-sesinya melalui pendengar itu. Pendengar menambahkan header `Proxy-Authorization` dalam perjalanan ke proxy Anda.

* **Pendengar**: pendengar adalah proxy forward di `127.0.0.1`. Runner memulai pendengar sebelum mendaftar dengan bidang kontrol, dan keluar pada startup jika pendengar tidak dapat dimulai.
* **Variabel proxy**: runner menulis ulang mana pun dari `HTTPS_PROXY` dan `HTTP_PROXY` yang Anda atur sehingga menunjuk ke pendengar. Nilai yang ditulis ulang itu menjangkau runner itu sendiri, lifecycle hooks-nya, dan setiap sesi yang dijalankannya.
* **Rotasi token**: token yang diputar berlaku tanpa restart. Untuk setiap koneksi yang dibuka pendengar ke proxy Anda, runner menjalankan perintah Anda atau membaca file Anda lagi dan menambahkan hasilnya sebagai header.
* **Lingkungan sesi**: sesi menjangkau proxy Anda hanya melalui pendengar. Dalam lingkungan setiap sesi, runner menghapus `ALL_PROXY`, menghapus ejaan apa pun dari `HTTPS_PROXY` atau `HTTP_PROXY` yang tidak Anda atur, dan menyematkan `NO_PROXY` ke nilai runner itu sendiri.
* **Log**: runner tidak pernah mencatat nilai header.

## Konfigurasi git

Runner mengelola checkout repositori tetapi tidak mengonfigurasi identitas git atau kredensial secara default. Anda mengontrol gambar dan lingkungan proses runner, jadi Anda mengontrol konfigurasi git. Pilih salah satu dari dua pendekatan:

* **Biarkan runner mengonfigurasi git**: mulai runner dengan `--configure-git` untuk memilikinya menulis identitas yang sama dan konfigurasi penandatanganan komit yang digunakan sesi yang di-host Anthropic
* **Kirim konfigurasi git dalam gambar Anda**: atur identitas dan kredensial push sendiri, misalnya untuk berkomitmen di bawah identitas bot Anda sendiri

Untuk repositori di github.com, Anda juga dapat memulai runner dengan [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), atau menetapkan `CLAUDE_RUNNER_USE_GIT_PROXY=1`, untuk meminta Anthropic melayani git bagi sesi-sesi runner.

Lantai versi Git di host runner: [`--configure-git`](#let-the-runner-configure-git) penandatanganan komit SSH memerlukan Git 2.34 atau lebih baru, [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy) memerlukan 2.32 atau lebih baru, dan melanjutkan sesi dari cabang yang didorong oleh [`--push-outcome-on-release`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) memerlukan 2.29 atau lebih baru. Git 2.24 cukup jika Anda menghilangkan ketiganya dan mengelola identitas git sendiri.

### Biarkan runner mengonfigurasi git

Mulai runner dengan `--configure-git`, atau atur `SELF_HOSTED_RUNNER_CONFIGURE_GIT=1`, untuk memilikinya menulis konfigurasi git global pada startup:

* `user.name = Claude` dan `user.email = noreply@anthropic.com`, cocok dengan sesi yang di-host Anthropic
* Penandatanganan komit dan tag format SSH, dirutekan melalui shim yang dikelola runner yang menandatangani setiap komit melalui layanan penandatanganan Anthropic menggunakan kredensial sesi itu sendiri. Tanda tangan dapat diverifikasi di GitHub terhadap kunci penandatanganan SSH yang dipublikasikan Anthropic.
* `push.negotiate = true`, jadi git menanyakan host git Anda komit mana yang sudah dimilikinya sebelum mengemas push. Memerlukan Claude Code v2.1.257 atau lebih baru.
* `core.hooksPath` menunjuk ke direktori hooks yang dikelola runner. Hook `commit-msg` dan `prepare-commit-msg`-nya menambahkan trailer `Co-authored-by:` untuk pembuat sesi ke setiap commit. Trailer tersebut dibangun dari email dalam [`CCR_SESSION_ACCOUNT_EMAIL`](https://code.claude.com/docs/id/self-hosted-environments-configuration#wrapper-scripts) dan dihilangkan ketika variabel itu tidak diatur. Jika gambar Anda sudah menetapkan `core.hooksPath` dan runner tidak menggunakan [git yang dikelola Anthropic](#use-the-anthropic-git-proxy), runner mempertahankan pengaturan Anda, melewati instalasi hook ini, dan mencetak peringatan `[runner:git]`.

Penandatanganan komit memerlukan git 2.34 atau lebih baru; runner memeriksa pada startup dan keluar dengan kesalahan jika git Anda lebih lama. Flag ini tidak mengonfigurasi kredensial push, yang masih Anda sediakan dalam gambar.

Pada runner dengan v2.1.280 atau lebih baru, commit yang Anda buat dari lifecycle hook `checkout` atau `post-session` juga ditandatangani sebagai sesi, tanpa trailer `Co-authored-by:`. [Konfigurasi git di dalam lifecycle hook](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#git-configuration-inside-lifecycle-hooks) menjelaskan pengaturan git yang ditetapkan runner di dalam hook tersebut.

Dengan atau tanpa `--configure-git`, Claude Code menginstruksikan Claude untuk mengakhiri pesan commit-nya dengan trailer `Claude-Session: <url>` dan deskripsi pull request-nya dengan URL sesi. Untuk menghilangkan keduanya, atur [`attribution.sessionUrl`](https://code.claude.com/docs/id/settings-reference#attribution-sessionurl) ke `false` di [`~/.claude/settings.json`](https://code.claude.com/docs/id/self-hosted-environments-configuration#how-each-session’s-config-is-assembled) host runner, lalu mulai ulang runner.

### Kirim konfigurasi git dalam gambar Anda

Identitas git diperlukan untuk komit apa pun. Aturnya di seluruh sistem dalam Dockerfile Anda sehingga konfigurasi berlaku terlepas dari pengguna mana yang menjalankan proses runner:

```dockerfile theme={null}
RUN git config --system user.name "Claude" && \
    git config --system user.email "noreply@anthropic.com"
```

Tanpa identitas, `git commit` gagal dengan `Please tell me who you are` dan sesi tidak dapat membuat kemajuan. Anda dapat menggunakan identitas bot Anda sendiri; runner tidak mengganti nilai-nilai ini.

Jangan menanamkan kredensial push jangka panjang atau berscope luas ke gambar runner bersama: kredensial dalam gambar tersedia untuk setiap sesi yang dijalankan gambar, siapa pun yang memulainya. Sebagai gantinya, buat token jangka pendek dengan scope seminimal mungkin per sesi dari [script wrapper](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#wrapper-scripts) Anda, menggunakan identitas pembuat sesi yang didekode dari session JWT. Pasangkan dengan kontainer per-sesi ephemeral, yang memerlukan `--capacity 1`, sehingga tidak ada kredensial yang bertahan lebih lama dari sesi yang membuatnya; lihat [bagian pengerasan](#harden-your-deployment).

Jika Anda harus mengonfigurasi kredensial push di tingkat gambar, misalnya untuk kunci deploy read-only, cakupkan seerat mungkin yang diizinkan host git Anda:

* Kunci deploy SSH terbatas pada satu repositori dengan rewrite `url.<base>.insteadOf`
* `credential.helper` yang mengembalikan token berscope minimal
* `GIT_SSH_COMMAND` menunjuk ke kunci berscope sempit

Mekanisme apa pun yang Anda konfigurasi harus bekerja tanpa prompt, karena klon built-in runner dan fetch menonaktifkan prompt yang git, SSH, dan Git Credential Manager akan menampilkan:

* Runner menetapkan `GIT_TERMINAL_PROMPT=0`, jadi git tidak meminta nama pengguna atau kata sandi.
* Runner menjalankan SSH dengan `BatchMode=yes`, ditambahkan ke `GIT_SSH_COMMAND` Anda jika Anda menetapkan satu, jadi SSH tidak meminta frasa sandi atau konfirmasi host.
* Runner menetapkan `GCM_INTERACTIVE=never`, jadi Git Credential Manager tidak membuka dialog sign-in.
* Runner menghapus `core.askPass`, jadi jika Anda menggunakan helper askpass, aturnya melalui variabel lingkungan `GIT_ASKPASS` sebagai gantinya.

Jika host git Anda menolak kredensial, atau Anda tidak mengonfigurasi satu, runner mencoba beberapa kali dan kemudian gagal persiapan repositori ketika repositori adalah yang didorong sesi untuk hasil. Untuk repositori yang hanya dibaca sesi, [Troubleshooting](#troubleshooting) mencakup kapan runner melewatinya. Runner tidak meneruskan pengaturan ini ke lingkungan sesi.

Jaga agar program apa pun yang Anda beri nama dalam `GIT_SSH_COMMAND` atau `GIT_ASKPASS` tetap berada di tempat sesi tidak dapat menulisnya, seperti yang diminta [daftar pengerasan](#harden-your-deployment) untuk direktori hooks dan script wrapper. Hal yang sama berlaku untuk kunci atau file apa pun pada baris perintah program itu. Git runner itu sendiri menjalankan program itu ketika klon atau fetch.

Jika direktori checkout dimiliki oleh uid berbeda dari proses runner, git menolak untuk beroperasi pada mereka; tambahkan `safe.directory`:

```dockerfile theme={null}
RUN git config --system --add safe.directory '*'
```

### Gunakan proxy git Anthropic

Dengan proxy git Anthropic, yang juga disebut git yang dikelola Anthropic, gambar runner tidak memerlukan kunci SSH, credential helper, `.netrc`, atau kredensial git lain untuk sesi itu sendiri. Sebagai gantinya, runner meminta Anthropic untuk melayani git bagi sesi-sesinya. Untuk sesi pengguna yang dilayani Anthropic, clone runner serta fetch dan push milik sesi itu sendiri melewati Anthropic, yang menggunakan token OAuth GitHub yang disimpan untuk pembuat sesi. [Bagaimana Anthropic melayani git untuk sebuah sesi](#how-anthropic-serves-git-for-a-session) membahas sesi bot dan agent.

Proxy git nonaktif kecuali Anda [mengaktifkannya](#turn-the-anthropic-git-proxy-on). Runner yang menjangkau host git Anda dengan kredensialnya sendiri tidak memerlukannya, dan git-nya bekerja dengan host git apa pun.

Sebagai gantinya, proxy git membatasi apa yang didukung runner dan mengubah apa yang dibutuhkannya:

* **Hanya github.com**: Anthropic melayani sebuah sesi hanya ketika semua repositorinya berada di github.com, dan proxy git belum mendukung GitHub Enterprise Server. Pada runner dengan proxy git, sesi dengan repositori di host git lain [gagal dimulai](#when-anthropic-doesnt-serve-a-session).
* **Kredensial hanya untuk repositori sesi**: Anthropic menyediakan kredensial git untuk repositori yang merupakan bagian dari sesi, bukan untuk repositori lain di host git yang sama. Submodule privat, dependensi yang di-fetch oleh package manager Anda dengan git, atau marketplace plugin di repositori lain tidak mendapatkan kredensial dari Anthropic. Minta orang-orang yang membuat sesi untuk [menambahkan setiap repositori](https://code.claude.com/docs/id/web-quickstart.md#start-a-task) yang dibutuhkan sesi saat mereka membuatnya.

  Setiap eksekusi [tugas terjadwal cloud](https://code.claude.com/docs/id/routines.md) adalah sesi dengan repositori milik tugas tersebut, jadi minta pemilik setiap tugas untuk [menambahkan setiap repositori yang dibutuhkan eksekusinya](https://code.claude.com/docs/id/routines.md#edit-and-control-routines).
* **Hanya push branch**: push yang menghapus branch gagal, begitu pula push ke jenis ref lain, seperti tag. Untuk branch mana yang dapat diperbarui oleh push, lihat [GitHub proxy](https://code.claude.com/docs/id/cloud-environments.md#github-proxy).
* **Akun GitHub yang terhubung**: minta setiap orang yang membuat sesi pengguna dengan repositori untuk [menghubungkan GitHub](https://code.claude.com/docs/id/web-quickstart.md#connect-github). Tanpa koneksi mereka sendiri, sesi tersebut [tidak dimulai](#creator-has-no-github-connection).
* **`--capacity 1`**: proxy git memerlukan satu sesi per proses runner, jadi jalankan lebih banyak replika untuk paralelisme. [Aktifkan proxy git Anthropic](#turn-the-anthropic-git-proxy-on) mencantumkan persyaratannya.
* **Konfigurasi git global yang digantikan**: runner [menghapus dan menggantikan konfigurasi git global](#git-proxy-replaces-global-git-config) milik pengguna yang menjalankannya. Jalankan runner sebagai pengguna khusus atau di dalam kontainer.
* **Kredensial host untuk push dari host**: push [`--push-outcome-on-release`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) milik runner dan push apa pun yang dilakukan [hook `post-session`](https://code.claude.com/docs/id/self-hosted-environments-configuration#post-session) Anda tetap menggunakan kredensial git milik host runner sendiri dan [jalur jaringannya ke `github.com`](#github-com-egress-with-the-anthropic-git-proxy). Jauhkan kredensial tersebut dari [konfigurasi git global](#git-proxy-replaces-global-git-config), dan lihat [Kirim konfigurasi git dalam gambar Anda](#ship-git-config-in-your-image) untuk cara menyediakannya.
* **Keputusan per sesi**: Anthropic memutuskan untuk setiap sesi di runner apakah akan melayani git-nya, dan sesi yang tidak dilayaninya gagal dimulai. [Ketika sesi gagal dimulai pada runner dengan proxy git](#when-anthropic-doesnt-serve-a-session) membahas penyebabnya.

> [!WARNING]
>
> Dengan `--use-anthropic-git-proxy` ditetapkan, runner menghapus dan menggantikan konfigurasi git global milik pengguna yang menjalankannya, tanpa menyimpan cadangan. Ini dilakukan saat startup dan sebelum setiap sesi. Login atau credential helper yang Anda simpan di sana akan hilang. Pengaturan yang ditulis [`--configure-git`](#let-the-runner-configure-git) tetap bertahan. Jalankan runner sebagai pengguna khusus atau di dalam kontainer, jangan pernah sebagai pengguna Anda sendiri.

Simpan pengaturan git yang tidak rahasia, seperti identitas dan `safe.directory`, di konfigurasi git sistem.

#### Aktifkan proxy git Anthropic

Sebelum Anda memulai runner dengan `--use-anthropic-git-proxy`, pastikan host runner memenuhi setiap persyaratan berikut. Runner menolak untuk dimulai ketika persyaratan kapasitas atau git tidak terpenuhi:

* **Claude Code v2.1.267 atau lebih baru**: versi sebelumnya menerima flag tetapi tidak melaporkan permintaan agar Anthropic melayani git atau mencetak baris `Registering as opted in`, sehingga Anthropic tidak melayani sesi-sesinya.
* **`--capacity 1`, nilai default**: setiap proses runner menangani satu sesi pada satu waktu, jadi jalankan lebih banyak replika untuk paralelisme.
* **Git 2.32 atau lebih baru**: git yang lebih lama mengabaikan konfigurasi git per sesi yang disiapkan runner untuk proxy git.

> [!WARNING]
>
> Resep [Kubernetes](#kubernetes) dan [Docker Compose](#docker-compose) di halaman ini menggunakan `--capacity 4`. Jika Anda menambahkan `--use-anthropic-git-proxy` atau `CLAUDE_RUNNER_USE_GIT_PROXY=1` ke salah satunya tanpa mengubah kapasitas menjadi `1`, runner keluar pada startup setiap kali orchestrator Anda memulainya kembali. Atur `--capacity 1` dan jalankan lebih banyak replika untuk paralelisme. [When the runner exits](#when-the-runner-exits) menunjukkan baris yang dicetak runner.

Untuk mengaktifkan proxy git, tambahkan `--use-anthropic-git-proxy` ke perintah runner, atau atur `CLAUDE_RUNNER_USE_GIT_PROXY=1` di lingkungan runner. Perintah ini, yang dijalankan di shell pada host runner, memulai runner dari [mulai cepat](https://code.claude.com/docs/id/self-hosted-environments-quickstart.md#set-up-manually) dengan proxy git aktif:

```bash theme={null}
claude self-hosted-runner --environment-secret-file '/etc/claude/environment-secret' --base-dir '<writable-dir>' --use-anthropic-git-proxy
```

Saat startup, runner mencetak `Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy)`. Anthropic kemudian memutuskan untuk setiap sesi pada runner tersebut apakah akan melayani git-nya. Untuk setiap sesi yang dilayaninya, runner mencatat baris `[runner:session]` yang berisi `governed git ACTIVE`. Jika sebuah sesi justru gagal dimulai, lihat [Ketika sesi gagal dimulai pada runner dengan proxy git](#when-anthropic-doesnt-serve-a-session).

#### Bagaimana Anthropic melayani git untuk sebuah sesi

Untuk sesi yang dilayani Anthropic, clone runner serta fetch dan push milik sesi itu sendiri melewati Anthropic, diautentikasi dengan token jangka pendek milik sesi itu sendiri:

* **Sesi pengguna**: Anthropic menggunakan token OAuth GitHub yang disimpan untuk pembuat sesi.
* **Sesi bot dan agent**: Anthropic menggunakan token instalasi GitHub App organisasi Anda.
* **Penulisan ulang URL**: `--git-host-rewrite` dan `--git-ssh-rewrite` tidak berpengaruh pada repositori yang dilayani proxy git.

#### Ketika sesi gagal dimulai pada runner dengan proxy git

Pada runner yang dimulai dengan `--use-anthropic-git-proxy`, sebuah sesi gagal dimulai ketika Anthropic tidak melayani git-nya. Cari di log runner kesalahan git yang menyebutkan alamat `api.anthropic.com` yang berisi `/git_proxy/`.

Untuk setiap sesi, runner dengan Claude Code v2.1.267 atau lebih baru juga mencatat baris `[runner:session]` yang berisi `governed git ACTIVE` ketika Anthropic melayani git sesi tersebut, atau satu baris `[runner:warn]` yang berisi `the server withheld Anthropic-managed git for this session` ketika tidak. Temukan baris yang Anda lihat di antara kasus-kasus berikut:

* **Baik `governed git ACTIVE` maupun baris `withheld` tidak ada**: runner yang lebih lama dari Claude Code v2.1.267 tidak mencatat kedua baris tersebut, dan Anthropic tidak melayani sesi-sesinya. Perbarui runner ke v2.1.267 atau lebih baru dengan mengikuti [Pin the version](#pin-the-version).
* **Baris `withheld`**: Anthropic tidak melayani sesi tersebut. Runner yang sebelumnya bekerja dengan proxy git dapat gagal dengan cara ini tanpa perubahan apa pun di pihak Anda.
  * **Sebuah repositori tidak berada di github.com**: sesi dengan satu repositori saja di host git lain, seperti GitHub Enterprise Server, tidak dilayani, termasuk repositori github.com-nya. [Nonaktifkan proxy git Anthropic](#turn-the-anthropic-git-proxy-off) untuk runner di lingkungan tersebut.
  * **Setiap repositori berada di github.com**: laporkan kegagalan tersebut ke [tim akun Anthropic Anda](#report-an-issue) dengan ID sesi dari baris `withheld`. Anthropic mencatat alasannya di sisinya.
* **Baris yang berisi `remote: access denied by the git proxy`**: sesi yang dilayani Anthropic masih dapat ditolak, misalnya ketika kebijakan organisasi menolak akses git untuk sesi tersebut, atau sesi tidak diotorisasi untuk repositori. Log runner kemudian menampilkan baris yang berisi `remote: access denied by the git proxy`, dan sisa baris tersebut menjelaskan alasannya.
* **`GitHub authentication required`**: ini muncul ketika pembuat sesi tidak memiliki koneksi GitHub yang berfungsi di claude.ai. Clone sesi gagal, dan kesalahan git berbunyi `GitHub authentication required. Please reconnect your GitHub account.` Minta orang tersebut untuk menghubungkan atau menghubungkan ulang GitHub di pengaturan claude.ai mereka.

Setelah Anda memperbaiki penyebabnya, mulai lagi sesi-sesi yang gagal.

#### Nonaktifkan proxy git Anthropic

Jika sesi di suatu lingkungan menggunakan repositori di host git selain github.com, seperti GitHub Enterprise Server, nonaktifkan `--use-anthropic-git-proxy` untuk runner di lingkungan tersebut.

**1. Hapus flag**

Hapus `--use-anthropic-git-proxy` dari perintah runner. Jika Anda mengatur `CLAUDE_RUNNER_USE_GIT_PROXY` di lingkungan runner, seperti pod spec atau file Compose, hapus dari sana. Di shell, batalkan pengaturannya:

```bash theme={null}
unset CLAUDE_RUNNER_USE_GIT_PROXY
```

**2. Berikan kredensial git ke runner**

Sediakan kredensial yang bekerja tanpa prompt untuk setiap host git yang digunakan sesi-sesi runner, termasuk github.com. Kredensial apa pun yang ada di konfigurasi git global pengguna runner sudah hilang, karena runner menghapus konfigurasi tersebut selama `--use-anthropic-git-proxy` ditetapkan. [Kirim kredensial dalam gambar Anda](#ship-git-config-in-your-image) atau gunakan [lifecycle hook `checkout`](https://code.claude.com/docs/id/self-hosted-environments-configuration#checkout).

**3. Buka jalur jaringan**

Izinkan runner menjangkau setiap host git yang digunakan sesi-sesi runner pada port 443 atau 22. Lihat baris host git di [Persyaratan jaringan](#network-requirements).

**4. Mulai ulang runner**

Mulai ulang runner agar terdaftar tanpa proxy git. Kemudian mulai lagi setiap sesi yang gagal.

#### Akses GitHub API tanpa GitHub CLI

Jika gambar runner Anda tidak menyertakan GitHub CLI, Claude Code dapat menyediakan `gh` bawaan, sehingga Claude tetap dapat membuka pull request, berkomentar, dan membaca hasil CI. `gh` bawaan ditujukan untuk runner yang menggunakan git yang dikelola Anthropic. `gh` bawaan mendukung satu perintah, `gh api`, yang memanggil REST API GitHub. Memerlukan Claude Code v2.1.287 atau lebih baru di gambar runner.

Perintah ini membuka pull request sebagai pengganti `gh pr create`. `gh` bawaan mengisi `{owner}` dan `{repo}` untuk repositori saat ini:

```bash theme={null}
gh api repos/{owner}/{repo}/pulls -f title='Fix' -f head='my-branch' -f base='main'
```

* **Kredensial**: `gh` bawaan mengirimkan permintaan REST-nya melalui git yang dikelola Anthropic, yang menyediakan kredensial GitHub di sisi Anthropic, sehingga gambar tidak memerlukan token GitHub untuk itu
* **Sesi mana yang mendapatkannya**: Anthropic memutuskan per sesi apakah git yang dikelola Anthropic melayani `gh` sesi tersebut. Jika ya, baris `[runner:session] governed git ACTIVE` yang dicatat runner untuk sesi tersebut menampilkan `gh_path_shim=true`. Jika tidak, sesi tidak memiliki `gh`
* **`jq`**: instal `jq` di gambar jika Anda ingin `--jq` berfungsi
* **[`CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`](https://code.claude.com/docs/id/env-vars)**: jika lingkungan sesi menetapkannya, Claude Code tidak menyediakan `gh` bawaan, dan sesi tidak memiliki `gh`

Ketika gambar menyertakan GitHub CLI, sesi menggunakannya.

#### Percayai otoritas sertifikat pribadi dengan git yang dikelola Anthropic

Bagian ini berlaku jika Anda menetapkan `GIT_SSL_CAINFO` atau `GIT_SSL_NO_VERIFY` dalam lingkungan runner yang sesinya menggunakan git yang dikelola Anthropic. Penanganan yang dijelaskannya memerlukan runner untuk menjalankan Claude Code v2.1.283 atau lebih baru.

Ketika git di runner harus mempercayai otoritas sertifikat pribadi (CA), seperti yang ditandatangani oleh proxy yang menginspeksi TLS, pendekatan biasa bekerja sebagai berikut:

* **Penyimpanan sertifikat sistem**: instal CA Anda di penyimpanan sertifikat sistem host runner, dan git mempercayainya tanpa variabel apa pun.
* **`GIT_SSL_CAINFO`**: aturnya ke file PEM dari CA Anda, misalnya `GIT_SSL_CAINFO=/etc/ssl/corp-ca.pem`.
* **`GIT_SSL_NO_VERIFY`**: tidak membantu di balik proxy yang menandatangani ulang. Klon runner sendiri melalui git yang dikelola Anthropic memeriksa sertifikat bahkan ketika variabel diatur, jadi klon itu gagal sampai git mempercayai CA Anda melalui salah satu dari dua pendekatan lainnya.

Untuk koneksi git yang membawa token sesi ke git yang dikelola Anthropic, runner menerapkan dua variabel sebagai berikut. Hook [`command`](https://code.claude.com/docs/id/self-hosted-environments-configuration#command) dimulai dengan lingkungan sesi, jadi itu mendapatkan apa yang git di dalam sesi dapatkan:

* **`GIT_SSL_CAINFO`**: terhadap apa git memeriksa git yang dikelola Anthropic tergantung di mana git berjalan:
  * **Klon dan fetch runner sendiri**: berjalan tanpa variabel dan memeriksa git yang dikelola Anthropic terhadap file sertifikat per-sesi yang ditulis runner. Runner membangun file itu dari bundel CA sistem host runner dan sertifikat dalam file Anda.
  * **Git di dalam sesi**: mendapatkan konfigurasi `http.sslCAInfo` yang menamai file Anda sebagai pengganti variabel, ditambah entri `http.<url>.sslCAInfo` yang memeriksa git yang dikelola Anthropic terhadap file per-sesi.
  * **Hook `checkout` dan `post-session`**: mewarisi variabel tanpa perubahan.
* **`GIT_SSL_NO_VERIFY`**: pemeriksaan sertifikat mana yang tetap mati tergantung di mana git berjalan:
  * **Klon dan fetch runner sendiri**: berjalan tanpa variabel dan periksa sertifikat yang mereka sajikan.
  * **Git di dalam sesi**: mendapatkan konfigurasi `http.sslVerify=false` sebagai pengganti variabel, jadi pemeriksaan tetap mati untuk host lain. Itu juga mendapatkan entri `http.<url>.sslVerify=true` yang menjaga pemeriksaan tetap aktif untuk git yang dikelola Anthropic.
  * **Hook `checkout` dan `post-session`**: ketika sesi memiliki repositori di git yang dikelola Anthropic, dapatkan konfigurasi `http.sslVerify=false` sebagai pengganti variabel. Mereka juga mendapatkan entri `http.<url>.sslVerify=true` yang menjaga pemeriksaan tetap aktif untuk git yang dikelola Anthropic.

File sertifikat per-sesi memerlukan bundel CA sistem di `/etc/ssl/certs/ca-certificates.crt` atau `/etc/pki/tls/certs/ca-bundle.crt` di host runner. Itu juga memerlukan file `GIT_SSL_CAINFO` yang dapat dibaca pengguna runner, yang menyimpan blok `CERTIFICATE` PEM, dan yang paling banyak 1 MiB. Ketika runner tidak dapat membangun file per-sesi, itu mencatat baris `[runner:warn]` yang berisi `did not build the certificate file` dan alasannya. Git kemudian menggunakan file Anda seperti apa adanya untuk git yang dikelola Anthropic. Perbaiki apa yang dinamai baris.

Untuk setiap sesi yang menggunakan git yang dikelola Anthropic, runner juga mencatat baris `[runner:warn]` yang dimulai dengan `governed git: GIT_SSL_CAINFO is set` atau `governed git: GIT_SSL_NO_VERIFY is set`. Baris mengatakan apa yang dilakukan runner dengan variabel itu untuk git-nya sendiri, untuk git di dalam sesi, dan untuk hook lifecycle Anda. Itu berakhir dengan apakah Anda perlu mengubah apa pun.

### Tambahkan repositori github.com ke sesi channel Claude Tag

Jika Claude tidak dapat menambahkan repositori github.com ke sesi channel Claude Tag, pastikan channel tersebut memiliki akses ke repositori itu, bahkan ketika runner Anda dapat meng-clone-nya. Akses tersebut berasal dari pengaturan di GitHub dan di organisasi Claude Anda, bukan dari kredensial git runner Anda.

**1. Instal Claude GitHub App**

Instal [Claude GitHub App](https://github.com/apps/claude) pada organisasi atau akun GitHub yang memiliki repositori tersebut, dan sertakan repositori itu dalam instalasi.

**2. Tautkan organisasi atau akun GitHub**

Di [**Organization settings > Git providers**](https://claude.ai/admin-settings/source-control), tautkan organisasi atau akun tersebut ke organisasi Claude Anda. Owner organisasi Claude Anda yang juga memiliki organisasi GitHub dapat menautkannya. Lihat [Link your GitHub organization](https://claude.com/docs/claude-tag/admins/configure-github#link-your-github-organization).

**3. Berikan akses repositori**

Di [pengaturan admin Claude Tag](https://claude.ai/admin-settings/claude-tag), berikan akses repositori ke channel atau ke cakupan yang lebih luas yang mencakup channel tersebut: workspace Slack-nya atau seluruh Slack. Seseorang dengan peran Owner atau izin Claude Tag Admin dapat memberikannya. Pengelola channel juga dapat menambahkannya ke channel mereka sendiri jika akun GitHub mereka adalah admin repositori tersebut. Lihat [Grant repository access](https://claude.com/docs/claude-tag/admins/configure-github#grant-repository-access).

Kemudian mulai thread baru di channel dan [sebutkan nama repositori](https://claude.com/docs/claude-tag/admins/configure-github#how-granted-repositories-reach-a-session) dalam pesan pertama Anda.

Jika Claude masih tidak dapat menambahkan repositori, lihat [If Claude can't reach a repository](https://claude.com/docs/claude-tag/admins/configure-github#if-claude-can%E2%80%99t-reach-a-repository) untuk mengetahui di mana memeriksa tautan GitHub dan pemberian akses repositori.

### Tulis ulang URL git untuk jaringan pribadi

URL repositori tiba dari bidang kontrol sebagai HTTPS, dengan nama host dari host git Anda; untuk GitHub Enterprise, itu adalah nama host yang Anda konfigurasi untuk [integrasi GitHub Enterprise](https://code.claude.com/docs/id/github-enterprise-server.md) di claude.ai. Dua flag yang dapat diulang menulis ulang URL tersebut sebelum klon:

* `--git-host-rewrite <from>=<to>`: untuk split-horizon DNS, di mana Anthropic menjangkau host git Anda melalui nama host eksternal tetapi runner harus menggunakan yang internal
* `--git-ssh-rewrite <host>`: untuk host git yang hanya menerima SSH, atau host runner yang hanya memiliki kunci SSH. Misalnya, untuk meng-clone repositori github.com melalui SSH, tambahkan `--git-ssh-rewrite github.com` ke perintah runner. Runner kemudian menulis ulang `https://github.com/owner/repo` menjadi `git@github.com:owner/repo`. SSH harus [terhubung tanpa prompt](#git-must-work-without-a-prompt).

Penulisan ulang host berjalan terlebih dahulu, jadi daftar nama host internal dalam `--git-ssh-rewrite` jika Anda memerlukan keduanya. Untuk kontrol penuh atas checkout, gunakan [`checkout` lifecycle hook](https://code.claude.com/docs/id/self-hosted-environments-configuration#checkout).

## Bangun gambar runner

Anthropic tidak menerbitkan gambar runner yang telah dibangun sebelumnya. Bangun milik Anda sendiri di sekitar biner `claude`, berlapis dengan toolchain apa pun yang dibutuhkan repositori Anda: runtime bahasa, compiler, package manager, dan sidecar [MCP](https://code.claude.com/docs/id/mcp.md).

Resep di bawah menggunakan `--capacity 4`, jadi satu kontainer melayani hingga empat sesi bersamaan dari pemilik terkunci yang sama. Itu tidak memberikan isolasi kontainer per-sesi dalam [bagian pengerasan](#harden-your-deployment): sebelum menghubungkan lingkungan ke sistem produksi, jalankan resep pada `--capacity 1` dengan satu kontainer per sesi, atau gunakan [runner on-demand](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#on-demand-runners), yang juga menjaga rahasia lingkungan dari host yang menjalankan sesi. Jika Anda menambahkan [proxy git Anthropic](#use-the-anthropic-git-proxy) ke salah satu resep ini, juga ubah `--capacity` menjadi `1`.

Dockerfile ini adalah titik awal minimal. Dockerfile ini menginstal `python3` karena [hook yang disediakan control plane Anthropic](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#how-each-session’s-config-is-assembled) untuk sesi Claude Tag, termasuk hook yang membuat Claude memposting balasannya di Slack, menjalankan `python3`. Pertahankan `python3` pada `PATH` di gambar apa pun yang melayani sesi Claude Tag:

```dockerfile theme={null}
FROM debian:bookworm-slim
ARG CLAUDE_CODE_VERSION
RUN apt-get update && apt-get install -y --no-install-recommends git curl ca-certificates openssh-client jq python3 \
 && rm -rf /var/lib/apt/lists/*
RUN curl -fsSL "https://downloads.claude.ai/claude-code-releases/${CLAUDE_CODE_VERSION:?set with --build-arg CLAUDE_CODE_VERSION}/linux-x64/claude" \
      -o /usr/local/bin/claude && chmod +x /usr/local/bin/claude
RUN git config --system user.name "Claude" \
 && git config --system user.email "noreply@anthropic.com" \
 && git config --system --add safe.directory '*'
ENTRYPOINT ["claude"]
```

Tukar `linux-x64` untuk `linux-arm64` jika node Anda adalah ARM, atau untuk `linux-x64-musl` atau `linux-arm64-musl` pada gambar berbasis musl seperti Alpine; lihat [Alpine Linux setup](https://code.claude.com/docs/id/setup.md#alpine-linux-and-musl-based-distributions) untuk paket tambahan yang dibutuhkan gambar musl. URL adalah lokasi rilis Claude Code standar, jadi Anda dapat memverifikasi biner yang diunduh terhadap manifes yang ditandatangani rilis seperti yang dijelaskan dalam [Binary integrity and code signing](https://code.claude.com/docs/id/setup.md#binary-integrity-and-code-signing). Runner memerlukan Claude Code versi 2.1.224 atau lebih baru, atau 2.1.229 atau lebih baru untuk melayani sesi Claude Tag. Bangun gambar, kemudian dorong ke registri Anda dan referensikan dalam resep di bawah:

```bash theme={null}
docker build \
  --build-arg CLAUDE_CODE_VERSION="$(curl -fsSL https://downloads.claude.ai/claude-code-releases/stable)" \
  -t <your-registry>/claude-runner:latest .
```

Substitusi perintah mencari nomor rilis `stable` saat ini dan meneruskannya sebagai argumen build, jadi menjalankan perintah yang sama setelah rilis stabil baru membangun ulang lapisan unduhan dengan biner yang lebih baru. Untuk menyematkan rilis tertentu untuk build yang dapat direproduksi, teruskan nomor versi langsung sebagai `CLAUDE_CODE_VERSION`. Ganti `stable` dengan `latest` dalam URL pencarian ketika Anda memerlukan rilis yang lebih baru dari saluran stabil, seperti yang [diperlukan model yang baru diluncurkan](https://code.claude.com/docs/id/model-config.md).

## Ukuran CPU dan memori untuk sesi

Ukuran kontainer atau host runner untuk sesi yang dijalankannya daripada untuk proses runner. Runner itu sendiri menanyakan pekerjaan, menyiapkan checkout setiap sesi, menjalankan [lifecycle hooks](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#lifecycle-hooks) Anda, dan memulai serta mengawasi proses sesi. Beban datang dari sesi: masing-masing adalah proses Claude Code ditambah apa pun yang dimulainya, seperti build, test suite, instalasi paket, dan [MCP server](https://code.claude.com/docs/id/mcp.md).

Untuk satu sesi, mulai dengan nilai-nilai berikut, dinyatakan sebagai permintaan dan batas Kubernetes atau setara platform Anda, dan perlakukan sebagai titik awal daripada persyaratan:

* **Memori**: permintaan dan batas 4 GiB masing-masing, yang memenuhi minimum 4 GB dalam [persyaratan sistem](https://code.claude.com/docs/id/setup.md#system-requirements) Claude Code. Jaga keduanya tetap sama sehingga penjadwal memperhitungkan memori penuh kontainer. Ketika kontainer mencapai batas memorinya, kernel membunuh proses di dalamnya, yang dapat mengakhiri sesi di tengah-tugas.
* **CPU**: permintaan 2 CPU dan batas 4 CPU, jadi sesi dapat meledak di atas permintaan selama build. Kernel membatasi kontainer pada batas CPU-nya daripada membunuh proses di dalamnya, jadi sesi pada batas berjalan lebih lambat tetapi terus berjalan.

Dalam spec kontainer Kubernetes, atur nilai awal tersebut dengan blok `resources` berikut:

```yaml theme={null}
resources:
  requests:
    cpu: "2"
    memory: 4Gi
  limits:
    cpu: "4"
    memory: 4Gi
```

Build dan test biasanya merupakan bagian terbesar dan paling variabel dari beban sesi, jadi jalankan build representatif repositori Anda, ukur puncak CPU dan memorinya, dan naikkan nilai awal apa pun yang tidak meninggalkan ruang untuk proses Claude Code di atas puncak itu.

Runner menggunakan `--capacity` untuk membatasi berapa banyak sesi yang dijalankannya sekaligus. Itu tidak membagi CPU atau memori di antara mereka, jadi sesi pada runner berbagi CPU dan memori kontainer. Agar penggunaan memori satu sesi tidak mengakhiri sesi lainnya, jalankan satu sesi per runner dengan [batas memorinya sendiri](#give-each-session-its-own-memory-limit).

* **Satu sesi per runner**: berikan setiap kontainer nilai satu sesi. Gunakan ukuran ini pada `--capacity 1`, yang [bagian pengerasan](#harden-your-deployment) rekomendasikan, dan untuk [runner on-demand](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#on-demand-runners), di mana Anda menetapkan nilai pada workload yang [`spawn-runner` hook](https://code.claude.com/docs/id/self-hosted-environments-configuration#the-spawn-runner-hook) Anda kirimkan, seperti template pod Job Kubernetes.
* **Beberapa sesi per runner**: pada `--capacity` di atas satu, kalikan nilai satu sesi dengan kapasitas, karena hingga banyak sesi dapat berjalan dalam kontainer sekaligus. Resep [Kubernetes](#kubernetes) dan [Docker Compose](#docker-compose) menjalankan `--capacity 4` tanpa batas CPU atau memori, jadi tambahkan batas yang diukur untuk kapasitas yang Anda jalankan.
* **Beberapa kontainer runner pada satu host**: ukur host untuk jumlah kapasitas semua runner, karena setiap runner hanya menghitung sesinya sendiri terhadap `--capacity`:

  * **Memori**: kalikan memori satu sesi dengan jumlah tersebut, lalu tambahkan apa yang digunakan oleh proses host itu sendiri
  * **CPU**: kalikan permintaan CPU satu sesi dengan jumlah tersebut. Agar setiap sesi dapat meledak sekaligus, kalikan batas CPU-nya sebagai gantinya.

  Berikan setiap runner kontainernya sendiri. Gunakan [path `--base-dir` yang sama](#keep-the-base-directory-and-capacity-identical-across-runners) di masing-masing, pada penyimpanan yang tidak di-mount oleh kontainer runner lain, sehingga setiap runner memiliki [direktori kerjanya sendiri](#harden-your-deployment).

### Berikan setiap sesi batas memorinya sendiri

Untuk mencegah satu sesi mengakhiri sesi lainnya ketika memori habis, jalankan setiap runner pada `--capacity 1` di bawah batas memorinya sendiri, yang diatur ke memori satu sesi:

* **Di dalam kontainer**: jalankan satu runner per kontainer, dan atur batas memori kontainer, seperti `limits.memory` di Kubernetes, `--memory` di Docker, atau `mem_limit` dalam file Compose. Jaga agar jumlah batas semua kontainer tetap di bawah memori host, karena ketika host itu sendiri kehabisan memori, kernel dapat membunuh proses di mana saja pada host.
* **Di bawah systemd tanpa kontainer**: atur `MemoryMax` pada unit runner, dan jalankan satu runner per host, karena runner yang [menggunakan `--base-dir` yang sama](#keep-the-base-directory-and-capacity-identical-across-runners) pada satu sistem file berbagi repositori yang di-clone di sana. Misalnya, pada `--capacity 1` tanpa hook `checkout`, sesi mereka pada satu repositori dapat bekerja di checkout yang sama sekaligus. Untuk menjalankan beberapa runner pada satu host, berikan masing-masing sebuah kontainer.

Ketika kontainer atau unit mencapai batas memorinya, kernel hanya membunuh proses di dalamnya. Jika Anda mengatur `--capacity` di atas satu, sesi-sesi tersebut berbagi satu batas. Ketika mereka mencapainya, kernel tidak memilih berdasarkan sesi proses mana yang akan dibunuh, sehingga penggunaan memori satu sesi dapat mengakhiri sesi lainnya.

Di Kubernetes, Anda tidak dapat memisahkan sesi dengan nilai mana pun dari pengaturan `singleProcessOOMKill` milik kubelet. Anda juga tidak dapat membatasi total memori sesi dengan `ulimit` memori dalam [script wrapper](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#wrapper-scripts) Anda. Kernel menerapkan nilai seperti `ulimit -v` ke setiap proses secara terpisah.

### Sisakan ruang di bawah batas proses dan thread

Untuk menentukan ukuran batas proses dan thread, ukur apa yang dibutuhkan satu sesi dan kalikan dengan jumlah sesi yang berbagi batas tersebut. Ketika sesi-sesi tersebut mencapai batas, kernel menolak proses dan thread baru, sehingga perintah, build, dan test di salah satu sesi dapat gagal. Panggilan yang ditolak mengembalikan `EAGAIN`, yang pesan standarnya adalah `Resource temporarily unavailable`.

Misalkan satu sesi mencapai puncak 300 proses dan thread, dan empat sesi berbagi batas tersebut. Bersama-sama mereka membutuhkan 1.200, dan dengan batas 2.400, Anda menyisakan ruang untuk build yang membutuhkan dua kali lipat.

#### Tempat mengatur setiap batas

Setiap batas dalam tabel ini menghitung thread maupun proses, dan `pids.max` adalah file yang menyimpan batas cgroup:

| Batas | Tempat Anda mengaturnya | Apa yang berbagi batas tersebut |
| :- | :- | :- |
| `pids.max` di Kubernetes | `podPidsLimit` dalam konfigurasi kubelet dari node yang menjalankan runner. Anda tidak dapat mengaturnya dalam spec Pod. | Kontainer-kontainer dari satu Pod |
| `pids.max` di Docker | `--pids-limit`, atau `pids_limit` dalam file Compose | Satu kontainer |
| `pids.max` di bawah systemd | `TasksMax` pada unit runner | Satu unit |
| `ulimit -u` | Shell atau layanan yang memulai runner | Semua hal pada host yang berjalan sebagai user ID runner. Tidak diberlakukan untuk root. |

Ketika Anda tidak mengatur `TasksMax`, systemd menerapkan `DefaultTasksMax`, yang secara default bernilai 15% dari nilai terendah di antara `kernel.pid_max`, `kernel.threads-max`, dan `pids.max` milik cgroup root.

#### Ukur puncak satu sesi

Untuk mendapatkan angka bagi runner Anda sendiri, ukur satu sesi pada host-nya, yang di Kubernetes adalah node:

**1. Mulai satu runner sendirian**

Mulai satu runner pada `--capacity 1` tanpa hal lain di kontainer atau unit systemd-nya, sendirian pada host-nya dan di environment-nya:

* **Host-nya**: gunakan host yang tidak menjalankan runner lain. Anda kemudian dapat mengenali prosesnya pada langkah berikutnya.
* **Environment-nya**: jadikan runner tersebut satu-satunya runner di sana, misalnya di environment yang Anda [buat untuk pengujian](https://code.claude.com/docs/id/self-hosted-environments-quickstart.md#set-up-manually). Sesi dirutekan ke environment dan bukan ke runner.

Jangan mulai sesi dulu, karena Anda memerlukan direktori dari langkah berikutnya sebelum build dimulai.

**2. Temukan direktori cgroup runner**

Pada host, bukan di dalam kontainer, temukan ID proses runner, misalnya dengan `ps`. Baca `/proc/<pid>/cgroup` untuk ID tersebut:

* **Sebuah baris berisi `:pids:`**: host menggunakan cgroup v1, dan direktorinya adalah `/sys/fs/cgroup/pids` diikuti teks setelah `:pids:`
* **Satu-satunya baris dimulai dengan `0::`**: host menggunakan cgroup v2, dan direktorinya adalah `/sys/fs/cgroup` diikuti teks setelah `0::`

**3. Jalankan build representatif dan catat puncaknya**

Jalankan satu sesi melalui build representatif, dan ambil puncaknya dari direktori cgroup runner:

* **Direktori memiliki `pids.peak`**: baca file tersebut setelah build selesai
* **Direktori tidak memiliki `pids.peak`**: baca `pids.current` sekali setiap detik selama build berjalan, dan simpan nilai tertinggi. Puncak di antara dua pembacaan tidak akan terlihat, jadi perlakukan nilai tersebut sebagai perkiraan rendah.

**4. Kalikan dengan sesi yang berbagi batas**

* **Pod, kontainer, atau unit satu runner**: kalikan puncak dengan `--capacity` runner
* **Batas yang dibagi oleh beberapa runner**: kalikan puncak dengan jumlah kapasitas mereka

Kemudian tambahkan puncak dari hal lain yang berbagi batas tersebut, seperti kontainer lain di Pod.

**5. Atur batas di atas hasilnya**

Dengan batas yang lebih tinggi, Anda menyisakan ruang untuk build yang lebih berat, dan dengan batas yang lebih rendah, Anda menghentikan fork bomb lebih cepat.

#### Periksa batas `pids.max`

* **Periksa apakah `pids.max` telah tercapai**: cari di log kernel host, misalnya dengan `dmesg`, baris yang berisi `cgroup: fork rejected by pids controller in` dan path sebuah cgroup. Kernel hanya mencatatnya pertama kali ia menolak proses atau thread dalam cgroup tersebut, jadi cari di seluruh log.
* **Lihat batas yang berlaku**: baca `pids.max` di direktori cgroup runner dan di setiap direktori di atasnya. Nilai terendah adalah batas yang berlaku, dan `max` berarti tidak ada batas pada tingkat tersebut.

## Kubernetes

Runner melayani `GET /healthz` di port 8080 secara default, dapat dikonfigurasi dengan `--health-port`, jadi probe Kubernetes bekerja tanpa setup tambahan. Endpoint mengembalikan `200` kapan pun proses hidup, jadi probe di bawah mendeteksi proses mati, bukan yang terjebak; untuk menangkap runner yang berhenti polling, beri peringatan pada seri `last_poll_age_seconds` dari [`/metrics`](https://code.claude.com/docs/id/self-hosted-environments-reference#prometheus-metrics). Deployment di bawah memasang rahasia lingkungan dari Secret Kubernetes, menunjuk probe liveness dan readiness ke `/healthz`, dan menetapkan periode grace terminasi 90 detik. Lihat [Shutdown timing](#shutdown-timing) untuk alasan periode grace penting.

Manifest tidak menetapkan `resources` CPU atau memori pada kontainer runner. Tambahkan blok yang diukur untuk kapasitas yang Anda jalankan, seperti [Size CPU and memory for sessions](#size-cpu-and-memory-for-sessions) jelaskan.

```yaml theme={null}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: claude-runner
  namespace: claude-runners
spec:
  replicas: 3
  selector:
    matchLabels:
      app: claude-runner
  template:
    metadata:
      labels:
        app: claude-runner
        app.kubernetes.io/part-of: claude-code-self-hosted-runner
    spec:
      terminationGracePeriodSeconds: 90
      containers:
        - name: runner
          image: <your-registry>/claude-runner:latest
          args:
            - self-hosted-runner
            - --environment-secret-file
            - /etc/claude/environment-secret
            - --capacity
            - "4"
            - --base-dir
            - /workspace
          volumeMounts:
            - name: environment-secret
              mountPath: /etc/claude
              readOnly: true
          ports:
            - name: health
              containerPort: 8080
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 30
      volumes:
        - name: environment-secret
          secret:
            secretName: claude-runner-environment-secret
```

Deployment di atas hidup dalam namespace `claude-runners`. Buat namespace terlebih dahulu:

```bash theme={null}
kubectl create namespace claude-runners
```

Buat Secret pendukung dari file lokal yang memegang nilai yang Anda salin dalam [langkah **Copy environment key**](https://code.claude.com/docs/id/self-hosted-environments-quickstart.md#set-up-manually) UI admin, jadi rahasia tidak pernah muncul dalam riwayat shell Anda. Jalankan `(umask 077 && cat > ./environment-secret)`, tempel rahasia, tekan Enter, kemudian Ctrl-D. Kemudian buat Secret dan hapus file:

```bash theme={null}
kubectl create secret generic claude-runner-environment-secret -n claude-runners --from-file=environment-secret=./environment-secret
```

## Docker Compose

Layanan Compose di bawah memulai ulang runner kapan pun keluar, yang mencakup crash dan exit normal setelah draining. Kebijakan restart Docker memulai ulang kontainer yang sama dengan lapisan yang dapat ditulisnya utuh, jadi runner kembali pada filesystem yang digunakan kembali daripada yang segar yang [postur pengerasan](#harden-your-deployment) rekomendasikan; gunakan resep ini untuk evaluasi, dan untuk produksi baik buat ulang kontainer per run atau gunakan orchestrator yang melakukannya.

Docker menunggu lebih lama sebelum setiap restart kontainer yang terus keluar, hingga batas maksimal, jadi runner yang tidak dapat dimulai tidak terus memulai ulang dalam loop ketat di bawah resep ini. [Ketika runner keluar](#when-the-runner-exits) menjelaskan apa yang harus diperiksa ketika itu terjadi.

```yaml theme={null}
services:
  claude-runner:
    image: <your-registry>/claude-runner:latest
    command:
      - self-hosted-runner
      - --environment-secret-file
      - /run/secrets/environment-secret
      - --capacity
      - "4"
      - --base-dir
      - /workspace
    secrets:
      - environment-secret
    restart: always
    stop_grace_period: 90s

secrets:
  environment-secret:
    file: ./environment-secret
```

## Waktu shutdown

Setelah `SIGTERM`, runner memerlukan waktu untuk menghentikan sesinya dengan bersih sebelum orchestrator Anda membunuhnya. Runner mencatat berapa lama waktu tersebut saat startup, dalam baris yang berisi `This runner needs up to 80s` pada pengaturan default. Atur stop timeout orchestrator Anda ke setidaknya jumlah detik tersebut: `terminationGracePeriodSeconds` pada Kubernetes, `stop_grace_period` pada Docker Compose, atau yang setara di platform Anda. Default Kubernetes adalah 30 detik, jadi tanpa pengaturan ini Kubernetes dapat menghentikan pod sebelum runner selesai.

Pada `SIGTERM`, runner berhenti menerima sesi baru. Runner kemudian memulai shutdown yang terkendali, yang disebut drain, kecuali Anda menetapkan [`--defer-shutdown-max-min`](#defer-the-drain-past-the-first-signal) untuk menundanya. Drain memiliki tiga langkah:

1. Runner menunggu hingga [`--drain-wait-sec`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) detik, `0` secara default, agar giliran yang masih berjalan selesai.
2. Runner menghentikan pohon proses setiap sesi, termasuk perintah apa pun yang masih dijalankan Claude, tetapi tidak termasuk [proses yang masih berjalan setelah perintah shell-nya keluar](#processes-a-stopped-session-leaves).
3. Runner menjalankan [hook lifecycle `post-session`](https://code.claude.com/docs/id/self-hosted-environments-configuration#post-session).

Runner terus melakukan polling ke Anthropic sepanjang drain. Hal itu membuat sesinya tetap ditugaskan kepadanya, sehingga runner lain tidak mengambil salah satu sesi tersebut sementara hook `post-session` Anda masih menyimpan pekerjaan yang belum dikomit.

Waktu yang dicatat adalah jumlah dari nilai-nilai berikut:

* `--drain-wait-sec` untuk langkah 1, 0 detik secara default
* [`--session-stop-grace-sec`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) untuk langkah 2, 5 detik secara default
* [`--post-session-hook-timeout-sec`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) untuk langkah 3, 60 detik secara default
* Ruang cadangan tetap sebesar 15 detik
* 30 detik tambahan ketika [`--push-outcome-on-release`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) diatur

Pada pengaturan default, totalnya adalah 0 + 5 + 60 + 15 = 80 detik. `--capacity` yang lebih tinggi tidak menambahnya, karena runner melakukan drain terhadap semua sesinya secara bersamaan.

Sediakan waktu lebih jika Anda mengatur salah satu dari flag berikut:

* **Dengan [`--retire-at`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags)**: sisakan cukup waktu antara waktu retire dan waktu host berhenti agar giliran tipikal dapat selesai, ditambah waktu tunggu untuk tugas latar belakang yang dijelaskan [Runner lifecycle](https://code.claude.com/docs/id/self-hosted-environments#runner-lifecycle), ditambah waktu yang dicatat. Hitung waktu retire di setiap peluncuran, misalnya `date +%s` ditambah masa hidup yang dimaksudkan untuk runner.
* **Dengan [`--defer-shutdown-max-min`](#defer-the-drain-past-the-first-signal)**: stop timeout juga harus mencakup menit yang Anda konfigurasi dan waktu tunggu tambahan sebelum drain dimulai, 75 detik pada pengaturan default. [Defer the drain past the first signal](#defer-the-drain-past-the-first-signal) menjelaskan keduanya. Runner juga mencatat waktu yang lebih panjang ini saat startup.

### Biarkan giliran yang sedang berjalan selesai sebelum restart

Untuk membiarkan giliran yang sedang berjalan selesai sebelum restart bergulir, atur `--drain-wait-sec`, dan naikkan stop timeout Anda ke setidaknya waktu tunggu tersebut ditambah 80 detik. Dengan waktu tunggu default `0`, restart memotong giliran apa pun yang masih berjalan, dan sesi dilanjutkan di runner lain tanpa [pekerjaan yang belum di-push](#additional-limitations).

* **`--drain-wait-sec`**: kira-kira selama giliran dan tugas latar belakang yang ingin Anda biarkan selesai. Ini adalah nilai maksimum: waktu tunggu berakhir lebih awal jika semuanya selesai lebih dulu.
* **Stop timeout Anda**: `terminationGracePeriodSeconds` di Kubernetes, `stop_grace_period` di Docker Compose. Misalnya, dengan `--drain-wait-sec 120`, ganti `90` dalam resep [Kubernetes](#kubernetes) atau [Docker Compose](#docker-compose) dengan `200`.
* **Jika Anda mengubah pengaturan shutdown lainnya**: 80 detik tersebut berlaku untuk pengaturan default. Jalankan satu runner dengan flag Anda, dan gunakan setidaknya angka dalam baris startup-nya `This runner needs up to`, yang sudah mencakup waktu tunggu. Dengan [`--defer-shutdown-max-min`](#defer-the-drain-past-the-first-signal) diatur, gunakan angka yang lebih besar di baris startup berikutnya, setelah `Size the stop timeout to at least`. [Shutdown timing](#shutdown-timing) mencantumkan apa saja yang menambah angka-angka ini.

> [!WARNING]
>
> Jika stop timeout habis selama waktu tunggu, host Anda membunuh runner saat pekerjaan masih berjalan. Sesinya tidak mendapatkan hook `post-session`. Runner juga tidak melakukan deregistrasi, sehingga control plane dapat memerlukan beberapa menit, dan terkadang lebih lama, untuk mengantrekan kembali sesi tersebut.

### Kirim pesan status sebelum restart

Sebelum restart yang akan mengganggu sesi, minta seorang [Owner](https://code.claude.com/docs/id/cloud-environments.md#organization-shared-environments) mengirim pesan status pada environment dari [halaman admin **Cloud environments**](https://claude.ai/admin-settings/cloud-environments). Di [claude.ai/code](https://claude.ai/code), anggota melihat pesan tersebut di sesi mereka dalam environment itu. Minta Owner menetapkan waktu kedaluwarsa saat mengirim pesan, atau hapus pesan tersebut setelah runner kembali aktif.

### Defer the drain past the first signal

Atur [`--defer-shutdown-max-min <n>`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) jika Anda ingin runner yang sedang Anda restart terus melayani sesi yang dipegang selama hingga `n` menit, alih-alih mendrainnya pada sinyal pertama. Pada `SIGTERM` atau `SIGINT` pertama, runner berhenti menerima pekerjaan baru dan terus melayani sesi yang dipegang. Ini terus polling sehingga control plane tidak mengantrekan kembali sesi tersebut. Memerlukan Claude Code v2.1.238 atau lebih baru.

#### Apa yang terjadi pada sesi yang dipegang runner setelah sinyal pertama

Dalam dua tahap pertama yang mengikuti sinyal, runner melepaskan sesi, dan sesi yang dirilis dilanjutkan di runner segar ketika pengguna mereka mengirim pesan berikutnya. Menghitung dari sinyal pertama, runner bergerak melalui tiga tahap:

* **Untuk `n` menit pertama**: runner melayani sesinya secara normal dan terus memberlakukan `--startup-timeout-min` dan `--kill-session-after-min`. Jika Anda juga menetapkan [`--release-idle-session-min`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags), runner melepaskan sesi apa pun yang penggunanya telah idle selama itu; tanpanya, sesi idle tetap di runner.
* **Ketika `n` menit habis**: runner melepaskan setiap sesi yang masih dipegang, idle atau tidak. Runner menunggu turn sesi mid-turn berakhir, dan hingga 60 detik lagi untuk background tasks turn, sebelum melepaskan sesi itu.
* **Ketika post-release grace habis**: runner mendrainkan sesi apa pun yang masih dipegang, dan control plane mengantrekan kembali setiap sesi yang didrain ke runner lain segera. Post-release grace dimulai ketika `n` menit habis dan 75 detik pada default. Jika Anda menetapkan `--drain-wait-sec` di atas 60 detik, post-release grace adalah `--drain-wait-sec` ditambah 15 detik sebagai gantinya.

Di tahap mana pun, runner keluar 0 segera setelah tidak memegang sesi. Sinyal kedua memotong tahap pendek: runner mendrainkan segera, seperti yang dilakukan pada sinyal pertama tanpa `--defer-shutdown-max-min`. Setelah drain sedang berlangsung, sinyal berikutnya force-exits runner. Itu berlaku apakah sinyal kedua atau post-release grace yang habis memulai drain.

#### Ukuran stop timeout

Berikan stop timeout host Anda setidaknya jumlah dari tiga bagian: `n` menit yang Anda konfigurasi, post-release grace, dan drain yang dijelaskan [Shutdown timing](#shutdown-timing). Dengan pengaturan default, post-release grace adalah 75 detik dan drain memerlukan hingga 80 detik, jadi sediakan `n` menit ditambah 155 detik. Runner mencetak jumlah ini saat startup kapan pun `--defer-shutdown-max-min` diatur.

Jika stop timeout habis sebelum runner selesai, sesi yang masih dipegangnya tidak mendapatkan hook `post-session`. Runner tidak melakukan deregistrasi, sehingga control plane dapat memerlukan beberapa menit, dan terkadang lebih lama, untuk mengantrekan kembali sesi tersebut. Jika Anda tidak dapat memberikan stop timeout jumlah itu, biarkan `--defer-shutdown-max-min` tidak diatur sehingga runner mendrainkan pada sinyal pertama sebagai gantinya.

### Apa yang mencapai hook post-session yang sedang berjalan

Hook `post-session` dan setiap anak sesi Claude masing-masing berjalan dalam kelompok proses POSIX mereka sendiri, terpisah dari runner, jadi mekanisme stop mencapainya secara berbeda:

* **`SIGTERM` sementara runner sudah draining**: force-exits runner segera, melewati apa pun yang tersisa dari drain. Tanpa [`--defer-shutdown-max-min`](#defer-the-drain-past-the-first-signal), itu adalah `SIGTERM` kedua yang diterima runner. Tidak ada yang mengirim sinyal ke hook `post-session` yang sedang berjalan, jadi di host bare di mana proses init mengadopsi orphans, hook itu selesai sendiri, tetapi tanpa pengawasan: anggaran timeout-nya tidak lagi berlaku, dan penulisan ke pipa log tertutup dapat membunuhnya dengan `SIGPIPE`, jadi hook yang perlu bertahan dari forced exit di sana harus mengarahkan ulang output-nya sendiri ke file. Dalam resep container di halaman ini runner adalah PID 1 container dan keluarnya mengakhiri container, dan di bawah `KillMode=control-group` default systemd, kill cgroup-wide mencapai hook juga, seperti yang dijelaskan entri **Cgroup-wide kills**; di keduanya, perlakukan forced exit sebagai fatal untuk hook dan andalkan periode grace sebagai gantinya.
* **Sinyal process-group-wide**, seperti `kill -- -<pid>` dalam skrip wrapper, shell job control, atau watchdog group-wide: mencapai runner dan subprocess mid-`checkout`-hook, yang tetap group-attached dengan sengaja, tetapi bukan hook `post-session` mid-run atau anak sesi.
* **Cgroup-wide kills**, seperti `KillMode=control-group` default systemd atau `SIGKILL` yang dikirimkan Kubernetes ke seluruh container ketika `terminationGracePeriodSeconds` kedaluwarsa: mencapai segalanya, termasuk hook. Isolasi process-group tidak melindungi terhadap ini, itulah mengapa periode grace harus mencakup seluruh drain.
* **Timeout hook sendiri**: ketika hook melebihi `--post-session-hook-timeout-sec`, runner mengirim `SIGTERM` ke seluruh kelompok proses hook, kemudian `SIGKILL` dua detik kemudian, jadi worker yang hook fork, seperti tar, rsync, atau git, berakhir dengan shell wrapper alih-alih bertahan sebagai orphan. Pengawasan runner berakhir setelah stdio hook ditutup: worker yang mengarahkan ulang output-nya sendiri ke file dan bertahan melampaui tahap `SIGTERM` melampaui jangkauan runner.

Ketika drain dimulai, dan lagi pada forced exit, runner mencatat berapa banyak hook `post-session` yang masih berjalan, jadi Anda dapat membedakan drain yang tenang dari yang mid-snapshot.

## Jaga direktori dasar dan kapasitas identik di seluruh runner

Jika runner mati mid-sesi, server antri ulang sesi dan runner lain dalam lingkungan mengambilnya. Runner itu menurunkan jalur checkout dari `--base-dir` dan `--capacity` miliknya sendiri: `--capacity 1` checkout langsung di bawah `--base-dir`, dan `--capacity` di atas `1` menggunakan worktree per-sesi. Ketika runner dalam lingkungan yang sama menggunakan nilai berbeda untuk flag apa pun, direktori kerja sesi yang dilanjutkan berubah, dan jalur absolut yang agen catat sebelumnya, dalam edit, panggilan alat, atau catatan miliknya sendiri, menunjuk ke lokasi yang tidak lagi ada.

Gunakan `--base-dir` dan `--capacity` yang sama pada setiap runner dalam lingkungan, dan jangan gunakan nilai per-host seperti ID instans atau nama host. Berikan kedua flag secara eksplisit, seperti yang dilakukan resep [Kubernetes](#kubernetes) dan [Docker Compose](#docker-compose), sehingga tidak ada nilai yang bergantung pada default.

Direktori dasar default ke `/workspace`, dengan pengecualian baris referensi [`--base-dir`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) catat. Runner memerlukan akses tulis ke sana. Pada startup, sebelum mendaftar, runner membuat direktori dan mengkonfirmasi dapat menulis ke sana, dan keluar dengan `cannot create or write to base directory` ketika tidak bisa. Runner yang dimulai sebagai root membuat `/workspace` default itu sendiri. Untuk runner non-root, buat direktori dan berikan kepemilikan pengguna runner sebelum memulai runner, atau arahkan `--base-dir` ke direktori yang pengguna itu sudah miliki.

## Gunakan kembali checkout yang telah dipanaskan sebelumnya

Untuk repositori besar, klon dapat mendominasi startup sesi. Untuk melewati klon dingin, sediakan sendiri klon di jalur tempat runner menyimpan klonnya sendiri. Tanpa [`checkout` hook](https://code.claude.com/docs/id/self-hosted-environments-configuration#checkout), runner menyimpan satu klon kanonik per repositori di `<base-dir>/<repo-owner>/<repo>` dan menggunakannya kembali di seluruh sesi:

* **Pada `--capacity 1`**: runner mengambil ref yang diminta, melepaskan `HEAD`, dan melakukan reset keras ke sana, yang hampir instan ketika hanya sedikit yang berubah.
* **Pada `--capacity` di atas satu**: runner mengambil ke dalam klon tersebut, lalu melakukan checkout worktree terpisah darinya untuk setiap sesi. Klon yang telah dipanaskan sebelumnya menghemat unduhan tetapi tidak menghemat checkout.

Sediakan klon di dalam image atau pada volume persisten:

* **Klon dalam gambar**: bangun klon ke gambar runner Anda di jalur itu. Setiap kontainer segar kemudian dimulai dengan klon hangat tanpa menggunakan kembali disk.
* **Klon pada volume persisten**: pada runner yang Anda pre-lock ke akun satu pengguna dengan [`--lock-to-account`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags), arahkan `--base-dir` ke volume persisten, jadi disk hanya pernah melayani akun itu. Runner yang pre-locked tidak pernah mengambil sesi saluran Claude Tag, jadi opsi ini tidak berlaku untuk runner yang melayani mereka.

Apa jalur penggunaan kembali lakukan dan tidak jamin:

* **Kedalaman klon**: lakukan build klon tanpa `--depth`, dengan atau tanpa `--single-branch`. Runner tidak pernah meneruskan `--depth` saat mengambil ke dalam klon yang memiliki remote `origin`, bahkan jika Anda menetapkan [`CLAUDE_RUNNER_FETCH_DEPTH`](https://code.claude.com/docs/id/self-hosted-environments-reference#environment-variable-only-settings). Dengan klon penuh, setiap sesi hanya mengunduh commit yang belum dimiliki klon. Dengan klon shallow, sesi pada branch yang bercabang sebelum commit tertua klon akan mengunduh seluruh riwayat branch tersebut.
* **Perubahan terlacak di-reset, file tidak terlacak bertahan**: pada `--capacity 1`, setiap sesi dimulai dari reset keras yang menghapus modifikasi terlacak dari sesi sebelumnya, tetapi runner tidak pernah menjalankan `git clean`, sehingga file tidak terlacak dari sesi-sesi sebelumnya milik pemilik terkunci tetap ada di pohon.
* **Direktori per-sesi juga bertahan**: di samping checkout, runner membuat entri per-sesi di bawah `<base-dir>/_sessions/` untuk setiap sesi yang dijalankannya. Direktori konfigurasi Claude sesi menyimpan salinan lokal transkrip percakapan. Di sebelahnya duduk file yang diunggah sesi, ketika sesi memiliki apa pun. Direktori sesi duduk di sana juga: itu menyimpan worktree per-sesi apa pun dan checkout hook `checkout` sementara sesi berjalan, dan itu menyimpan apa pun yang Claude tulis di dalamnya.

  Secara default runner meninggalkan ini di tempat ketika sesi berakhir, jadi pada disk yang melampaui proses runner mereka menumpuk. Setiap sesi berjalan sebagai pengguna runner sendiri, jadi sesi kemudian apa pun yang disk layani dapat membacanya. Jika Anda menyimpan `--base-dir` persisten, ukuran volume untuk pertumbuhan itu. Hal yang sama berlaku untuk setup apa pun yang memulai ulang runner pada filesystem yang sama, termasuk resep [Docker Compose](#docker-compose).
* **Dengan `--remove-session-state`, direktori per-sesi tidak bertahan**: mulai runner dengan [`--remove-session-state`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) untuk memilikinya menghapus direktori per-sesi setiap sesi saat sesi berakhir. Penghapusan adalah best-effort: direktori tetap ketika runner dibunuh sebelum cleanup-nya berjalan. Klon kanonik dan file yang ditulis sesi di tempat lain di host, seperti direktori sementara, tetap terlepas.
* **Dengan proxy git, reset menjadi checkout**: dengan [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), runner membersihkan `.git/` klon sebelum setiap sesi. Runner tetap menyimpan object store dan shallow state, serta menghapus index. Tanpa index, setiap sesi dimulai dengan checkout working-tree penuh alih-alih reset yang hampir instan. Runner tetap mengambil ke dalam klon yang ada alih-alih mengklon ulang repositori. Pre-warm submodule tidak didukung di bawah proxy.
* **Dengan proxy git, pre-warm `reftable` kehilangan ref-nya**: saat runner membersihkan `.git/` sebelum setiap sesi, runner menyimpan `.git/refs/` dan menghapus direktori `.git/reftable/`. Klon yang dipanaskan sebelumnya dalam format ref `reftable` milik git kehilangan ref-nya dengan cara itu, jadi lakukan pre-warm klon dalam format ref `files` milik git sebagai gantinya.
* **Klon panjang tidak memerlukan workaround**: runner membatasi setiap operasi git dengan watchdog no-progress 120 detik dan hard cap 30 menit, bukan timeout flat, jadi klon dingin lambat yang terus melaporkan kemajuan selesai.

### Pastikan bahwa runner menggunakan klon Anda

Untuk memastikan bahwa runner menggunakan klon Anda, mulai runner dengan [`--log-level debug`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) dan mulai sesi pada repositori tersebut. Runner menulis `Prefetched repo found at` beserta jalur klon ke log. Jika runner justru menulis `No prefetched repo, fresh init at` ke log, runner tidak menemukan klon yang dapat digunakan di jalur pada baris tersebut. Periksa bahwa klon Anda berada di jalur tersebut, dimiliki oleh pengguna runner, dan memiliki remote bernama `origin`.

Jika runner menemukan klon Anda tetapi sesi masih dimulai dengan lambat, periksa apakah klon tersebut shallow.

## Sematkan versi

Secara default, proses anak setiap sesi menjalankan biner runner itu sendiri, dan runner mematikan auto-update di dalam sesi yang dihasilkannya. Dengan default tersebut, setiap sesi menjalankan versi yang Anda instal di host atau yang di-build ke dalam gambar. Update tingkat host berlaku waktu berikutnya runner dimulai.

Pilih versi yang dijalankan sesi Anda dan kapan versi tersebut berubah:

* **Sebelum Anda menyematkan versi**: periksa [versi Claude Code yang diperlukan model](https://code.claude.com/docs/id/model-config.md#available-models) untuk setiap model yang digunakan sesi Anda. Jika suatu model memerlukan versi yang lebih baru daripada yang dijalankan sesi Anda, server menolak permintaan untuk model tersebut dengan [Claude Code does not support this model](https://code.claude.com/docs/id/errors.md#claude-code-does-not-support-this-model).
* **Untuk menyimpan armada pada satu versi**: bangun gambar dengan versi yang disematkan, atau pada host telanjang instal versi spesifik dan [nonaktifkan auto-update](https://code.claude.com/docs/id/setup.md#disable-auto-updates)
* **Untuk upgrade armada tetap**: baca entri [changelog](https://code.claude.com/docs/en/changelog.md) antara versi Anda dan versi yang akan Anda instal, kemudian instal versi yang lebih baru atau build ulang gambar dan mulai ulang runner
* **Untuk upgrade runner sesuai permintaan**: baca entri [changelog](https://code.claude.com/docs/en/changelog.md) antara versi Anda dan versi yang akan Anda instal, kemudian ubah gambar yang dimulai oleh [hook `spawn-runner`](https://code.claude.com/docs/id/self-hosted-environments-configuration#the-spawn-runner-hook) Anda. Setiap runner baru mendapatkan versi baru. Runner yang sudah berjalan, termasuk runner siaga yang dimulai oleh [`--min-idle`](https://code.claude.com/docs/id/self-hosted-environments-reference#orchestrator-cli-flags), mempertahankan versinya hingga keluar. Jangan mulai ulang runner tersebut, karena perintah kerjanya hanya sekali pakai.
* **Jika sesi memulai biner yang berbeda**: jika Anda menggunakan [`--exec-path`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) atau [hook `command`](https://code.claude.com/docs/id/self-hosted-environments-configuration#command) untuk memulai biner Claude Code selain biner runner itu sendiri, sesi menjalankan versi biner tersebut. Perbarui biner tersebut saat Anda memperbarui runner.
* **Plugin**: marketplace plugin tidak auto-update juga; atur `FORCE_AUTOUPDATE_PLUGINS=1` dalam lingkungan runner untuk membiarkan plugin auto-update sementara biner tetap disematkan

## Skala armada

Orchestrator Anda memutuskan kapan menambah atau menghapus runner. Karena [kunci one-owner-per-runner](https://code.claude.com/docs/id/self-hosted-environments#runner-lifecycle), jumlah replika minimum adalah jumlah pengguna dan agen Claude Tag yang Anda harapkan aktif bersamaan; `--capacity` mengontrol paralelisme dalam sesi satu pemilik, bukan di seluruh pemilik.

Dua pendekatan penskalaan tersedia:

* **Armada tetap**: jalankan set replika runner statis dan skala pada [metrik Prometheus](https://code.claude.com/docs/id/self-hosted-environments-reference.md#prometheus-metrics) yang dilayani setiap runner
* **Runner on-demand**: jalankan subperintah `claude self-hosted-runner orchestrator`, yang menanyakan Anthropic untuk sesi yang antri tanpa runner tersedia dan memanggil hook `spawn-runner` Anda untuk boot satu per sesi. Lihat [On-demand runners](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#on-demand-runners).

### Lepaskan sesi idle pada armada tetap

Jika sesi tetap mengantri pada armada tetap sementara setiap runner online, sesi idle mungkin penyebabnya. Secara default, sebuah sesi mempertahankan slotnya setelah gilirannya selesai, dan runner-nya tetap terkunci pada [pemilik](https://code.claude.com/docs/id/self-hosted-environments#key-concepts) sesi tersebut. Jika [tugas terjadwal cloud](https://code.claude.com/docs/id/routines.md) tim Anda memulai sesi baru pada setiap eksekusi, sesi idle-nya dapat memenuhi setiap slot.

Untuk memeriksanya, baca `claude_code_self_hosted_runner_session_idle_seconds` di [metrik Prometheus](https://code.claude.com/docs/id/self-hosted-environments-reference.md#prometheus-metrics) setiap runner. Setiap sesi yang dipegang runner memiliki seri tersendiri: `0` selama sesi sibuk, jika tidak, jumlah detik sejak sesi tersebut menjadi idle. Pilih waktu idle yang Anda izinkan: lebih lama dari jeda yang diambil orang di tengah tugas, dan cukup singkat sehingga setiap eksekusi dilepaskan sebelum eksekusi berikutnya dari tugas terjadwal dimulai. Bandingkan seri tersebut dengan waktu itu.

* **Seri di atas waktu tersebut, seperti `1800` untuk 30 menit**: sesi idle memegang slot tersebut, jadi atur `--release-idle-session-min`
* **Setiap seri di bawahnya**: tidak ada sesi yang idle selama itu. Tambahkan runner, atau atur [`--kill-session-after-min`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) untuk [sesi yang tidak pernah menjadi idle](#some-sessions-don’t-count-as-idle).
* **Runner tanpa seri**: tidak ada sesi yang memegangnya. Lihat **Sessions stay queued** di bawah [Pemecahan masalah](#troubleshooting).
* **Runner yang metriknya tidak merespons**: prosesnya mungkin telah keluar. Lihat **An environment that looks healthy** di bawah [When the runner exits](#when-the-runner-exits).

Untuk melepaskan sesi idle, mulai setiap runner dengan [`--release-idle-session-min`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) dan waktu tersebut dalam menit, seperti `--release-idle-session-min 30`. Runner kemudian melepaskan setiap sesi yang tetap idle selama itu, baik yang dimulai oleh tugas terjadwal maupun oleh seseorang.

* **Pekerjaan yang belum di-push**: sesi yang dilepaskan dilanjutkan pada pesan berikutnya, tanpa pekerjaan yang belum di-push. Untuk mempertahankan commit-nya, lihat [Resumed sessions lose unpushed work](#additional-limitations).
* **Runner keluar**: runner yang melepaskan sesi terakhirnya memulai [keluar normal](#when-the-runner-exits). Minta supervisor Anda memulai ulang runner tersebut pada filesystem baru, seperti yang dijelaskan di [Harden your deployment](#harden-your-deployment).
* **Sesi yang tidak pernah menjadi idle**: atur juga [`--kill-session-after-min`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags), karena [beberapa sesi tidak dihitung sebagai idle](#some-sessions-don’t-count-as-idle)

## Masalah dan batasan yang diketahui

Berikut adalah batasan dalam rilis ini, dengan workaround di mana ada.

### Lalu lintas konektor meninggalkan jaringan Anda

Anthropic memanggil alat konektor dari infrastruktur miliknya sendiri daripada dari runner Anda. Alat konektor adalah konektor claude.ai, seperti GitHub, Slack, dan Linear. Ketika Claude menggunakan konektor dalam sesi yang di-host sendiri, lalu lintas itu melewati `api.anthropic.com` daripada berasal dari dalam batas jaringan Anda.

Untuk menjaga konektor keluar dari sesi yang di-host sendiri, saring dengan [pengaturan kebijakan `allowedMcpServers` dan `deniedMcpServers`](https://code.claude.com/docs/id/managed-mcp#policy-based-control-with-allowlists-and-denylists). Claude Code menerapkan pengaturan ini ke konektor yang dikirimkan Anthropic serta ke server yang Anda seed dari host runner dan server yang ditambahkan pengguna, jadi jika Anda terapkan allowlist untuk server lain, Claude Code memblokir konektor yang dikirimkan juga. Untuk menjaga konektor tersedia bersama allowlist berbasis URL, tambahkan entri yang cocok dengan jalur proxy Anthropic untuk konektor yang dikirimkan:

* `https://api.anthropic.com/v2/ccr-sessions/*`
* `https://api.anthropic.com/v1/code/sessions/*`
* `https://api.anthropic.com/v1/code/mcp/*`

Jika lalu lintas alat harus tetap di dalam jaringan, jalankan alat setara sebagai server MCP lokal pada gambar runner. Lihat [MCP servers](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#mcp-servers).

### Beberapa sesi tidak dihitung sebagai menganggur

Sesi yang memegang tugas latar belakang yang tidak pernah selesai tidak dihitung sebagai menganggur, jadi `--release-idle-session-min` tidak akan melepaskan slot sesi itu. Sesi yang menunggu persetujuan yang diminta dari dalam panggilan alat yang berjalan juga tidak dihitung sebagai menganggur. Selalu atur `--kill-session-after-min` bersama sebagai backstop keras sehingga tidak ada sesi yang dapat memegang slot tanpa batas.

`--kill-session-after-min` adalah backstop untuk sesi yang melarikan diri. Pada runner pada v2.1.260 atau lebih baru, sesi yang mencapai batas tidak dihentikan begitu saja. Runner memberikan jendela grace, 15 menit secara default, yang dapat Anda ubah dengan [`SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS`](https://code.claude.com/docs/id/self-hosted-environments-reference#environment-variable-only-settings):

* Jika sesi menunggu penggunanya, runner melepaskannya. Jika putarannya telah berakhir dan hanya memegang tugas latar belakang, runner menunggu hingga 60 detik untuk tugas-tugas itu selesai dan kemudian melepaskannya. Sesi dilanjutkan ketika penggunanya mengirim pesan berikutnya.
* Jika putaran masih berjalan, runner menunggu putaran selesai, atau untuk sesi berikutnya menunggu penggunanya, dan kemudian melepaskannya.
* Jika sesi masih di runner ketika jendela grace berakhir, runner menghentikannya, dan pekerjaan putaran yang berjalan hilang. Putaran menunggu persetujuan yang diminta dari dalam panggilan alat yang berjalan adalah satu cara sesi melampaui jendela.

Sesi yang dilepaskan dilanjutkan dari klon segar, jadi pekerjaan yang tidak didorong hilang bagaimanapun; lihat [Resumed sessions lose unpushed work](#additional-limitations). Sebelum v2.1.260, runner menghentikan setiap sesi pada batas, setelah menunggu paling banyak jendela grace untuk putaran yang berjalan selesai.

Atur flag di atas sesi terpanjang yang diharapkan, seperti `--kill-session-after-min 480` untuk 8 jam. Untuk membebaskan slot dari percakapan yang menjadi menganggur, gunakan `--release-idle-session-min`.

### Batasan tambahan

* **Sesi yang dilanjutkan kehilangan pekerjaan yang belum di-push**: runner segar mengklon repositori lagi dari branch awalnya, jadi pekerjaan yang belum di-push oleh sesi hilang.
  * **Untuk mempertahankan pekerjaan yang sudah di-commit**: atur [`--push-outcome-on-release`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) pada setiap runner di lingkungan tersebut, karena runner tanpa flag ini melanjutkan sesi dari branch awalnya. Runner dengan flag ini melakukan push best-effort atas branch hasil sesi sebelum melepaskannya, dan sesi yang dilanjutkan dimulai dari commit tersebut. Push tersebut menggunakan kredensial git milik host runner sendiri, termasuk pada runner yang menggunakan [git yang dikelola Anthropic](#use-the-anthropic-git-proxy).
  * **Dengan hook `checkout`**: repositori yang di-checkout melalui [hook siklus hidup `checkout`](https://code.claude.com/docs/id/self-hosted-environments-configuration#checkout) tidak di-push. Buat snapshot repositori tersebut dari [hook `post-session`](https://code.claude.com/docs/id/self-hosted-environments-configuration#post-session) sebagai gantinya.
  * **Sebelum mengaktifkan flag ini**: batasi siapa yang dapat melakukan push ke ref `claude/*` pada remote sumber. Saat dilanjutkan, runner mengambil branch yang sebelumnya di-push tanpa memverifikasi siapa yang melakukan push-nya.
  * **Perubahan yang belum di-commit tetap hilang**: untuk meminta Claude melakukan commit dan push ketika selesai merespons, lihat [Prompt sessions to push their work](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#prompt-sessions-to-push-their-work)
* **Perintah yang terputus karena runner yang hilang dapat berjalan lagi**: ketika sesi dilanjutkan di runner lain, Claude melihat panggilan tool yang terinterupsi dengan hasil yang menyatakan bahwa hasil akhirnya tidak diketahui.
* **Repositori yang ditambahkan di tengah sesi dapat gagal diklon**: Claude mengklonnya dengan `git clone` melalui HTTPS. Pada runner tanpa [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), klon gagal dengan kesalahan autentikasi git jika tidak ada apa pun di host yang dapat membaca repositori tersebut. Jika memungkinkan, pilih setiap repositori yang dibutuhkan sesi ketika Anda membuatnya.
* **Beberapa konektor tidak muncul dalam sesi yang di-host sendiri**: konektor yang belum Anda hubungkan dalam pengaturan claude.ai tidak terdaftar dalam sesi yang di-host sendiri, dan sesi tidak akan meminta Anda untuk menghubungkannya. Hubungkan dalam Pengaturan terlebih dahulu, kemudian mulai sesi segar. Menambahkan konektor ke sesi yang sudah berjalan juga tidak membuat alat-alatnya tersedia untuk Claude; mulai sesi segar untuk mengambil konektor yang baru ditambahkan.
* **Ketika seseorang mengarsipkan atau menghapus sesi, atau melepaskan runner-nya dari menu sesi**: jangan mengandalkan push [`--push-outcome-on-release`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags). Untuk menyimpan commit dalam kasus tersebut, push `HEAD` ke branch penyelamat dari [hook `post-session`](https://code.claude.com/docs/id/self-hosted-environments-configuration#post-session). Jika Anda memulai dari contoh di sana, ubah agar contoh tersebut melakukan push bahkan ketika tidak ada perubahan yang belum di-commit, karena contoh tersebut hanya melakukan push setelah commit snapshot.

### Laporkan masalah

Untuk masalah dengan lingkungan yang di-host sendiri, hubungi tim akun Anthropic Anda.

## Pemecahan masalah

Untuk diagnosis terpandu, jalankan subperintah doctor pada host runner. Subperintah doctor memulai sesi Claude Code interaktif dengan log dan status runner terlampir. Masuk terlebih dahulu dengan `claude auth login` pada host tersebut agar sesi dapat melakukan kueri terhadap lingkungan Anda, runner-nya, dan sesi yang sedang dalam antrean. Tanpa proses masuk tersebut, misalnya ketika host melakukan autentikasi dengan kunci API, sesi hanya dapat mengakses endpoint kesehatan lokal, metrik, dan log runner, dan sesi hanya membaca log tersebut jika Anda memulai runner dengan `--log-file`.

```bash theme={null}
claude self-hosted-runner doctor
```

Masalah umum:

* **Runner tidak muncul di lingkungan**: pastikan host dapat menjangkau `api.anthropic.com` melalui HTTPS, secret lingkungan masih yang terkini, dan jam host berselisih tidak lebih dari lima menit dari waktu sebenarnya; selisih yang lebih besar menyebabkan autentikasi gagal. Runner mencatat `[runner:fatal]` beserta alasan penolakan ke log saat autentikasi gagal.
* **Runner keluar saat startup dengan `cannot create or write to base directory`**: runner tidak dapat membuat atau menulis ke `--base-dir`, yang secara default adalah `/workspace`. Perbaiki kepemilikan direktori tersebut atau arahkan `--base-dir` ke path yang dapat ditulisi, seperti yang dijelaskan di [Jaga agar direktori dasar dan kapasitas identik di seluruh runner](#keep-the-base-directory-and-capacity-identical-across-runners). Jika runner justru mencatat `[runner:fatal]` yang menyatakan bahwa pemeriksaan direktori dasar mengalami timeout, direktori tersebut berada pada mount NFS atau CSI yang macet. Periksa kesehatan mount, bukan permission. Runner mencetak kedua kegagalan startup ini ke stderr sebelum membuka `--log-file`, jadi cari keduanya di terminal atau log container platform Anda, bukan di file log. Sebelum v2.1.225, runner tidak memeriksa direktori dasar saat startup, dan kesalahan konfigurasi ini justru menggagalkan sesi setelah sesi diambil.
* **Runner keluar saat startup dengan `RegisterRunner failed:` dan kesalahan sertifikat TLS**: kesalahannya dapat berupa `self signed certificate in certificate chain`, misalnya. Runner tidak memercayai otoritas sertifikat (CA) di balik sertifikat yang diterimanya, seperti yang terjadi di balik proxy yang melakukan inspeksi TLS. Atur [`NODE_EXTRA_CA_CERTS`](https://code.claude.com/docs/id/network-config#custom-ca-certificates) di lingkungan runner ke file PEM yang berisi root CA proxy tersebut.

  Anda tidak perlu mengaturnya lagi untuk sesi, karena sesi mewarisinya. Git tidak membacanya: untuk clone melalui proxy tersebut, atur juga [`GIT_SSL_CAINFO`](#trust-a-private-certificate-authority-with-anthropic-managed-git).
* **Skrip start Anda mencetak kesalahan penulisan yang berisi `Resource temporarily unavailable` lalu keluar**: skrip Anda berbagi stdout atau stderr dengan proses `claude self-hosted-runner` atau [orkestrator](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#on-demand-runners) yang dimulainya, dan proses tersebut telah mengalihkan pipe atau socket bersama itu ke mode non-blocking. Ketika pipe penuh, penulisan dari skrip Anda gagal dengan kesalahan sistem operasi ini alih-alih menunggu, dan di bawah `set -e` skrip tersebut keluar.

  Untuk menghentikan kesalahan ini, kirim output runner atau orkestrator ke file yang dapat dibuat oleh pengguna skrip Anda. Tambahkan `>> "$HOME/claude-runner.log" 2>&1` ke perintah yang memulainya, sebelum `&` yang menjalankannya di latar belakang:

  ```bash theme={null}
  claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret >> "$HOME/claude-runner.log" 2>&1
  ```

  Output skrip Anda sendiri tetap menuju ke tempat sebelumnya, sedangkan apa yang ditulis runner ke stdout dan stderr, termasuk kesalahan startup-nya, menuju ke file tersebut. Gunakan pengalihan shell ini, bukan [`--log-file`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) saja, yang mencerminkan log ke sebuah file sementara runner tetap menulis ke stdout dan stderr. Jika Anda sudah meneruskan `--log-file`, pertahankan juga, karena pengalihan tersebut tidak mengubah apa yang diterima file itu.
* **Orkestrator mencatat `poll failed:` dengan kesalahan sertifikat TLS dan terus mencoba ulang**: orkestrator tidak memercayai CA di balik sertifikat yang diterimanya, seperti yang terjadi di balik proxy yang melakukan inspeksi TLS. Atur [`NODE_EXTRA_CA_CERTS`](https://code.claude.com/docs/id/network-config#custom-ca-certificates) di lingkungan orkestrator ke file PEM yang berisi root CA proxy tersebut. Atur juga untuk runner yang dimulai oleh hook `spawn-runner` Anda.

  Misalnya, sebuah baris dapat diakhiri dengan `poll failed: self signed certificate in certificate chain — retrying in 1s`, dengan kata-kata dari pustaka TLS di antara titik dua dan tanda pisah. Untuk memastikan perbaikannya berhasil, periksa bahwa `connected` bernilai `true` di body [`/healthz`](https://code.claude.com/docs/id/self-hosted-environments-reference#health-endpoint) orkestrator.
* **Sesi tetap dalam antrean**: setiap runner yang online mungkin terkunci ke pemilik yang berbeda. Periksa [metrik](https://code.claude.com/docs/id/self-hosted-environments-reference.md#prometheus-metrics) `claude_code_self_hosted_runner_locked_account` setiap runner atau field `locked_account` pada baris log `[runner:health]`-nya untuk melihat siapa yang memegangnya. Keduanya hanya menampilkan email pemilik setelah runner diberi token sesi yang membawa klaim `act.email`, yang tidak pernah dimiliki oleh sesi agent Claude Tag. Tanpa klaim tersebut, runner tidak memancarkan deret `locked_account` dan mencatat `locked_account=yes`, yang memberi tahu Anda bahwa runner terkunci tetapi tidak ke pemilik mana. Tambahkan replika, atau tunggu runner yang ada selesai melakukan drain dan dimulai ulang. Jika lingkungan menggunakan runner on-demand, periksa orkestrator sebagai gantinya; lihat [Runner on-demand](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#on-demand-runners).
* **Sesi gagal segera setelah diambil**: buka sesi di claude.ai/code untuk melihat kesalahannya. Jika kesalahan menunjukkan bahwa git tidak dapat melakukan autentikasi, periksa [kredensial git](#configure-git) runner. Pada runner yang dimulai dengan `--use-anthropic-git-proxy`, lihat [Ketika sesi gagal dimulai pada runner dengan git proxy](#when-anthropic-doesnt-serve-a-session).

  Direktori dasar yang tidak dapat ditulisi menghentikan runner saat startup alih-alih menggagalkan sesi. Lihat entri **Runner keluar saat startup dengan `cannot create or write to base directory`** dalam daftar ini.
* **Sesi diambil, tetapi Claude tidak pernah menjawab pesan pertamanya**: jika runner Anda menjangkau `api.anthropic.com` melalui proxy, konfigurasikan proxy tersebut agar meneruskan respons `/worker/events/stream` sesi saat respons tersebut tiba. Menaikkan timeout runner tidak membantu. Untuk memastikan penyebabnya di log runner, lihat [Sesi diambil, tetapi Claude tidak pernah menjawab pesan pertamanya](#when-a-sessions-first-message-is-never-answered).
* **Sesi gagal dimulai pada runner yang mengatur `--use-anthropic-git-proxy`**: cari `access denied by the git proxy` di log runner, atau kesalahan git yang menyebutkan alamat `api.anthropic.com` yang berisi `/git_proxy/`. Untuk mengetahui apakah Anthropic melayani sesi tersebut dan memperbaiki penyebabnya, lihat [Ketika sesi gagal dimulai pada runner dengan git proxy](#when-anthropic-doesnt-serve-a-session).
* **Sesi tidak dapat menjangkau jaringan melalui proxy egress yang melakukan autentikasi**: ketika sumber yang Anda atur dengan [`--proxy-authorization-command` atau `--proxy-authorization-file`](#authenticate-to-an-egress-proxy) gagal, mengalami timeout setelah 30 detik, atau menghasilkan nilai kosong, runner menjawab koneksi tersebut dengan `502 Bad Gateway` dan mencatat alasannya ke log. Runner menyamarkan stderr perintah tersebut di log itu dan tidak pernah mencatat nilai header ke log. Dengan `--proxy-authorization-command`, jalankan sendiri perintah tersebut di host untuk memastikan perintah itu mencetak seluruh nilai header ke stdout. Jika runner justru keluar saat startup dengan `could not start the proxy-authorization listener`, runner tidak dapat membuka listener loopback-nya.
* **Runner mencatat baris `Poll failed` yang berisi `rejecting the malformed poll response`**: runner menerima respons work-poll yang body-nya bukan JSON yang diharapkan oleh antrean. Salah satu penyebabnya adalah proxy yang mencegat atau captive portal yang menjawab dengan halamannya sendiri. Jika runner terhubung melalui proxy, konfigurasikan proxy agar meneruskan respons dari `api.anthropic.com` tanpa diubah.

  Runner menolak respons yang rusak tersebut, menghitungnya di bawah jenis `transport` dari [metrik](https://code.claude.com/docs/id/self-hosted-environments-reference.md#prometheus-metrics) `claude_code_self_hosted_runner_poll_errors_total`, dan mencoba ulang sesuai jadwal poll-gagal yang dijelaskan di [Siklus hidup sesi](https://code.claude.com/docs/id/self-hosted-environments#session-lifecycle). Runner tetap melayani sesi aktifnya. Sebelum v2.1.246, runner membaca respons semacam itu sebagai antrean kerja kosong, yang dapat mengakhiri sesi aktifnya atau membuatnya keluar.
* **Sesi gagal dengan `no longer exists on the remote` atau `couldn't find remote ref`**: runner membaca `couldn't find remote ref` dari git sebagai branch yang telah dihapus. Branch tersebut mungkin masih ada. Cari branch tersebut di host git Anda, dengan nama yang persis sama seperti di pesan, lalu periksa setiap penyebab yang sesuai:

  * **Branch sudah tidak ada**: pulihkan branch tersebut, seperti yang diminta oleh pesan.
  * **Branch ada dan Anda tidak mengelola runner**: kirimkan pesan lengkapnya kepada administrator runner dan beri tahu mereka bahwa branch tersebut ada.
  * **Branch ada dan runner memiliki hook `checkout`**: perintah git di hook Anda dapat mencetak `couldn't find remote ref` ke stderr sebelum hook keluar dengan kode bukan nol. Temukan perintah tersebut, lalu perbaiki ref yang dimintanya atau remote tempat perintah itu melakukan fetch. Runner meneruskan revisi yang diminta sesi ke hook Anda sebagai [`CLAUDE_RUNNER_REPO_REF`](https://code.claude.com/docs/id/self-hosted-environments-configuration#checkout).
  * **Branch ada dan runner tidak memiliki hook `checkout`**: runner melakukan fetch branch tersebut sendiri. Jika [`--git-host-rewrite`](#rewrite-git-urls-for-private-networks) berlaku untuk repositori tersebut, pastikan host tujuan penulisan ulangnya memiliki branch dengan nama yang sama, dan push branch tersebut ke sana jika belum ada.
  * **Branch ada dan runner lebih lama dari v2.1.301**: tingkatkan runner seperti yang dijelaskan di [Sematkan versi](#pin-the-version), sehingga kegagalan yang berbeda menampilkan kesalahannya sendiri. Sebelum v2.1.301, kegagalan lain yang memiliki `couldn't find remote ref` dalam output kesalahannya juga dilaporkan sebagai branch yang dihapus.

  Setelah runner ditingkatkan atau penyebabnya diperbaiki, coba ulang sesi tersebut atau mulai sesi baru. Jika tidak ada pemeriksaan ini yang menemukan penyebabnya, [laporkan masalah](#report-an-issue).

  Untuk sumber tempat sesi mendorong hasil, branch yang dihapus menggagalkan sesi. Untuk sumber git yang hanya dibaca oleh sesi, runner melewati sumber tersebut dan melanjutkan dengan sumber lainnya. Runner menggagalkan sesi ketika melewati sumber akan membuat sesi tidak memiliki repositori sama sekali. Sebelum v2.1.228, sesi semacam itu dimulai di direktori kosong.
* **Claude Tag terkadang tidak memposting balasannya di Slack**: pengingat balasan adalah [hook yang disediakan oleh control plane](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#how-each-session’s-config-is-assembled). Pastikan runner adalah Claude Code v2.1.229 atau yang lebih baru dengan `claude --version`, karena runner yang lebih lama mengabaikan hook yang disediakan; pastikan `python3 --version` berhasil di dalam image runner, karena hook tersebut menjalankan `python3`; dan pastikan tidak ada file pengaturan yang mematikan hook, baik `disableAllHooks` di `managed-settings.json`, `~/.claude/settings.json` milik pengguna runner, atau `.claude/settings.json` atau `.claude/settings.local.json` milik repositori, maupun `allowManagedHooksOnly` atau [`strictPluginOnlyCustomization`](https://code.claude.com/docs/id/settings-reference#strictpluginonlycustomization) yang mencakup hook di pengaturan terkelola.
* **Sesi dimulai tanpa salah satu repositorinya**: pada runner tanpa [hook `checkout`](https://code.claude.com/docs/id/self-hosted-environments-configuration#checkout), host git dapat menolak pemeriksaan akses runner untuk repositori yang hanya dibaca oleh sesi. Runner kemudian melewati repositori tersebut, mencatat baris `[runner:warn] could not access context source` yang menyebutkan penolakan tersebut, dan memulai sesi dengan repositori lainnya.

  Runner hanya melewati penolakan yang jelas: host menjawab bahwa repositori tidak ditemukan, git tidak menemukan kredensial untuk host tersebut, atau autentikasi gagal. Kegagalan jaringan, timeout, atau HTTP `403` tetap menggagalkan dimulainya sesi, begitu pula penolakan untuk repositori tempat sesi mendorong hasil. Runner tetap menggagalkan sesi yang akan tidak memiliki repositori sama sekali jika dilewati. Dengan [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), runner hanya melewati repositori yang ditolak oleh git proxy itu sendiri.

  Pemeriksaan akses dijalankan lagi setiap kali sesi dimulai pada runner, sehingga begitu identitas git runner memiliki akses baca, proses mulai berikutnya akan meng-clone repositori tersebut. Sebelum v2.1.274, setiap penolakan ini menggagalkan dimulainya sesi.
* **Sesi gagal dimulai dengan `This runner couldn't read`**: runner meminta repositori dari host git dengan [kredensial git](#configure-git)-nya dan mendapat jawaban tidak ditemukan. Repositori tersebut tidak ada, atau kredensial tersebut tidak dapat menjangkau repositori itu.
  * **Jika Anda mengelola runner**: berikan akses baca ke repositori tersebut kepada kredensial git runner, lalu mulai sesi baru.
  * **Jika Anda yang memulai sesi**: jika repositori tidak ada, mulai sesi baru dengan repositori yang benar. Jika runner tidak dapat menjangkau repositori tersebut, minta administrator runner untuk memberikan akses, lalu mulai sesi baru.
* **Sesi membutuhkan waktu beberapa menit untuk dimulai atau dilanjutkan**: salah satu penyebabnya adalah clone baru. Pada fleet yang memulai setiap runner pada disk kosong, tanpa [hook `checkout`](https://code.claude.com/docs/id/self-hosted-environments-configuration#checkout), runner meng-clone ulang repositori sesi dari awal sebelum memulai Claude Code. Sesi yang dilanjutkan pada disk kosong menunggu clone yang sama lagi.

  Untuk melewati clone baru, sediakan [checkout yang sudah dipanaskan sebelumnya](#reuse-a-pre-warmed-checkout). Jika Anda sudah menyediakannya, [pastikan runner menggunakannya](#confirm-that-the-runner-uses-your-clone). Ketika runner membuat clone sendiri dan keluar setelah satu sesi, turunkan [`CLAUDE_RUNNER_FETCH_DEPTH`](https://code.claude.com/docs/id/self-hosted-environments-reference#environment-variable-only-settings) dari default-nya sebanyak 50 commit untuk mengambil lebih sedikit riwayat.

  Jika Anda menggunakan hook, hook tersebut berjalan sebagai pengganti clone runner, jadi periksa apa yang dilakukan hook Anda. Pantau [metrik](https://code.claude.com/docs/id/self-hosted-environments-reference.md#prometheus-metrics) `claude_code_self_hosted_runner_session_init_duration_seconds` untuk melihat berapa lama proses mulai berlangsung.
* **Giliran gagal dengan 401**: ketika sebuah giliran berakhir dengan 401 atau 403 dari Anthropic API, runner mengambil [`CLAUDE_CODE_OAUTH_TOKEN`](https://code.claude.com/docs/id/self-hosted-environments-configuration#wrapper-scripts) baru dari Anthropic dan meneruskannya ke sesi. Giliran yang gagal tidak dicoba ulang. Token ini berumur pendek, dan runner merotasinya melalui stdin sesi.

  Ketika pengambilan gagal, runner mencatat baris `inference_token refresh failed` yang menyebutkan kapan runner akan mencoba ulang, dan runner terus mencoba ulang selama sesi berjalan.

  Jika setiap panggilan mulai gagal sekitar 30 menit setelah sesi dimulai, kemungkinan besar sebuah skrip wrapper telah memutus stdin sesi, sehingga rotasi token tidak dapat menjangkaunya; lihat [Biarkan stdin dan file descriptor 3 tetap terhubung](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#keep-stdin-and-file-descriptor-3-attached).

  Sebelum v2.1.274, runner berhenti mencoba ulang pengambilan yang gagal setelah beberapa percobaan dan menunggu pengambilan terjadwal berikutnya. Giliran yang gagal tidak memicu pengambilan, sehingga setiap giliran gagal dengan 401 hingga pengambilan terjadwal berikutnya.
* **Giliran gagal di claude.ai/code dengan `This model needs a newer version of Claude Code`**: versi Claude Code yang digunakan runner Anda untuk memulai sesi lebih lama daripada yang dibutuhkan model sesi tersebut, sehingga API [menolak permintaan tersebut](https://code.claude.com/docs/id/errors.md#claude-code-does-not-support-this-model). Tingkatkan runner ke versi yang diminta oleh pesan tersebut, atau yang lebih baru, seperti yang dijelaskan di [Sematkan versi](#pin-the-version). Sampai saat itu, pengguna dapat memilih model lain di dalam sesi.
* **Pod dihentikan di tengah drain**: naikkan `terminationGracePeriodSeconds` setidaknya ke nilai yang dicatat runner saat startup. Lihat [Waktu shutdown](#shutdown-timing).

Setelah logging diinisialisasi, runner menulis log siklus hidupnya, termasuk baris `[runner:fatal]`, ke stdout, dan output debug ke stderr, semuanya sebagai baris teks biasa, bukan JSON. Kegagalan direktori dasar yang dijelaskan dalam entri pemecahan masalah di atas dicetak ke stderr sebelum titik tersebut. Tangkap kedua stream dengan `--log-file`, yang juga memungkinkan `self-hosted-runner doctor` mengikutinya (tail), atau dengan pengumpulan log platform Anda.

Proses anak setiap sesi menulis log debug terpisah. Saat terjadi kegagalan, runner menampilkan bagian akhir log tersebut bersama sesi di claude.ai/code. Kecuali Anda memulai runner dengan [`--remove-session-state`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags), runner juga menyimpan log sesi yang gagal di disk dan mencetak path-nya di log runner.

### Ketika runner keluar

Jangan memulai ulang [runner on-demand](https://code.claude.com/docs/id/self-hosted-environments-configuration.md#on-demand-runners), karena work order-nya hanya sekali pakai. Runner yang keluar tepat setelah dimulai memerlukan penanganan yang berbeda dari runner yang keluar karena alasan lain.

* **Keluar normal**: runner telah menyelesaikan sesinya dan melakukan drain, mencapai waktu pensiunnya, atau diperintahkan untuk berhenti. Mulai ulang runner agar lingkungan memiliki kapasitas lagi. [Siklus hidup runner](https://code.claude.com/docs/id/self-hosted-environments#runner-lifecycle) menjelaskan jenis keluar ini.
* **Gagal dimulai**: runner tidak dapat dimulai dengan konfigurasi atau host yang diberikan kepadanya, sehingga runner keluar beberapa detik setelah dimulai, dan runner keluar dengan cara yang sama setiap kali Anda memulainya ulang. Memulainya ulang lebih cepat tidak membantu. Seseorang perlu membaca outputnya dan memperbaiki penyebabnya.
* **Kehilangan kontak**: runner yang tidak dapat menjangkau Anthropic lebih lama dari [lease](https://code.claude.com/docs/id/self-hosted-environments#session-lifecycle)-nya, misalnya saat host-nya dalam mode tidur, dapat dihapus dari lingkungan. Ketika runner yang telah dihapus terhubung kembali, runner tersebut keluar. Log-nya dapat menampilkan baris `[runner:fatal]` yang berisi `runner record gone server-side` atau, setelah gangguan yang lebih lama, [`poll auth failed`](https://code.claude.com/docs/id/self-hosted-environments-quickstart#set-up-an-environment-and-runner). Runner tidak mendaftar ulang dengan sendirinya, jadi mulai ulang runner tersebut.

Konfigurasikan supervisor Anda agar memulai ulang runner setiap kali runner keluar, menunggu lebih lama di antara proses mulai ulang ketika runner terus keluar tepat setelah dimulai, dan memberi tahu seseorang ketika hal itu terus terjadi.

Untuk produksi, minta supervisor Anda memulai ulang runner pada disk yang baru, di container atau VM baru, seperti yang direkomendasikan oleh [Perkuat deployment Anda](#harden-your-deployment).

#### Mengenali kegagalan dimulai

Ketika runner tidak dapat dimulai, runner mencetak baris yang menyebutkan alasannya, lalu keluar. Untuk sebagian besar penyebab, baris tersebut berisi `[runner:fatal]`. Untuk beberapa penyebab, baris tersebut justru diawali dengan `error:`, termasuk ketika runner tidak dapat mengurai flag-nya, tidak dapat membaca secret lingkungan, atau tidak dapat membuat atau menulis ke direktori dasar. Baris berikutnya kemudian merujuk ke `--help`.

Sebagian besar baris log diawali dengan timestamp dan `[self-hosted-runner]`, yang dihilangkan dalam contoh di bawah. Misalnya, runner yang dimulai dengan Anthropic git proxy dan kapasitas di atas satu akan mencetak baris seperti ini:

```text theme={null}
[runner:fatal] --use-anthropic-git-proxy requires --capacity 1 (the proxy URL is per-session and linked worktrees share origin). Omit --use-anthropic-git-proxy or set --capacity 1.
```

Cari baris tersebut di standard output dan standard error runner, di log container platform Anda, atau di file yang Anda atur dengan [`--log-file`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags). Runner mencetak baris `error:` sebelum membuka file log, jadi cari baris tersebut di terminal atau log container Anda, seperti yang dicatat di [Pemecahan masalah](#troubleshooting).

Hal-hal berikut juga membantu ketika Anda membaca kegagalan dimulai:

* **Tidak ada baris sama sekali**: runner yang dihentikan oleh host tidak mencetak keduanya. Jika output berakhir tanpa baris `[runner:fatal]` dan tanpa baris `error:`, periksa apakah host atau orkestrator Anda menghentikan proses tersebut, misalnya karena melebihi batas memori.
* **Exit code**: runner tidak mencadangkan exit code khusus untuk kesalahan yang berulang pada setiap proses mulai. Runner keluar dengan kode yang sama untuk kesalahan konfigurasi, seperti kombinasi flag yang tidak didukung, dan untuk kegagalan yang dapat hilang dengan sendirinya, seperti API yang tetap tidak dapat dijangkau selama retry yang dilakukan runner sendiri. Dasarkan keputusan untuk menunggu lebih lama pada seberapa cepat runner keluar, dan baca output runner untuk mengetahui alasannya.
* **Lingkungan yang tampak sehat**: beberapa langkah startup berjalan setelah runner mendaftar ke lingkungan Anda, seperti [`--configure-git`](#let-the-runner-configure-git) dan penyiapan kredensial Anthropic git proxy. Jika salah satu langkah tersebut gagal, lingkungan dapat terus mencantumkan runner tersebut selama beberapa menit setelah prosesnya keluar, dan halaman **Cloud environments** dapat menampilkan **Healthy** meskipun tidak ada runner yang mengambil pekerjaan. Jika sesi tetap dalam antrean di lingkungan yang tampak sehat, periksa apakah supervisor Anda sedang memulai ulang runner.

#### Memulai ulang dengan waktu tunggu yang bertambah

Cara Anda mendapatkan waktu tunggu yang bertambah bergantung pada supervisor Anda.

* **Kubernetes**: [Deployment](#kubernetes) di halaman ini tidak memerlukan perubahan. Setelah container keluar, kubelet secara default menunggu sebelum memulai ulang container, dan waktu tunggu bertambah pada setiap proses mulai ulang hingga batas atas. Waktu tunggu kembali dari awal setelah container berjalan selama beberapa waktu tanpa keluar.

  Kubelet menerapkan waktu tunggu yang sama setelah keluar normal ketika container hanya berjalan sebentar. Oleh karena itu, runner yang sering melakukan drain juga dapat menampilkan status `CrashLoopBackOff`, jadi baca outputnya sebelum Anda menyimpulkan bahwa runner tidak dapat dimulai. Perintah di bawah membaca output dari proses terakhir pada satu pod dari Deployment tersebut:

  ```bash theme={null}
  kubectl logs --previous -n claude-runners deploy/claude-runner
  ```

  Ketika proses terakhir merupakan kegagalan dimulai, baris `[runner:fatal]` atau `error:` berada di antara baris-baris terakhir output. Untuk membaca proses terakhir pod lain, sebutkan pod tersebut sebagai pengganti `deploy/claude-runner`.
* **Docker dan Docker Compose**: [resep Compose](#docker-compose) di halaman ini tidak memerlukan perubahan. Dengan `restart: always`, Docker menunggu lebih lama sebelum setiap proses mulai ulang container yang terus keluar, hingga batas atas. Ganti `<container>` dengan nama container pada perintah di bawah, yang membaca berapa kali Docker telah memulai ulang container tersebut:

  ```bash theme={null}
  docker inspect --format '{{.RestartCount}}' <container>
  ```

  Perintah tersebut mencetak sebuah angka. Angka yang terus bertambah berarti Docker terus memulai ulang runner.
* **Unit systemd**: secara default systemd menunggu `RestartSec` yang sama sebelum setiap proses mulai ulang dan tidak memperpanjangnya, sehingga unit dengan `Restart=always` memulai ulang runner yang tidak dapat dimulai pada interval yang sama setiap kali. Ketika proses mulai terjadi cukup cepat hingga mencapai rate limit mulai unit tersebut, yaitu lima kali mulai dalam 10 detik secara default, systemd berhenti memulai ulang unit tersebut. Unit tetap berhenti hingga seseorang memulainya lagi, yang diizinkan systemd setelah interval rate limit berlalu atau setelah `systemctl reset-failed`. Karena `RestartSec` berlaku untuk setiap proses mulai ulang, nilai yang lebih besar juga menunda proses mulai ulang setelah keluar normal. Pilih nilai yang menyeimbangkan keduanya, dan buat peringatan berdasarkan jumlah mulai ulang unit tersebut.
* **Loop shell atau supervisor Anda sendiri**: terapkan sendiri aturan yang sama. Mulailah dengan waktu tunggu lima detik. Setelah setiap proses yang berakhir dalam satu menit, gandakan waktu tunggu untuk proses mulai ulang berikutnya, hingga lima menit. Setelah proses yang berlangsung satu menit atau lebih, kembali ke lima detik.

#### Memeriksa mengapa runner terus keluar

Ketika runner telah keluar tepat setelah dimulai beberapa kali berturut-turut, berhentilah dan periksa hal-hal berikut sebelum memulainya ulang lagi.

* **Baris `[runner:fatal]` atau `error:` terakhir**: baris ini menyebutkan mengapa runner berhenti. [Pemecahan masalah](#troubleshooting) mencantumkan penyebab umumnya.
* **Kombinasi flag**: [Anthropic git proxy](#use-the-anthropic-git-proxy) memerlukan `--capacity 1`. Resep di halaman ini menggunakan kapasitas yang lebih tinggi, jadi turunkan kapasitas tersebut ketika Anda menambahkan proxy ke salah satunya.
* **Apa yang dapat dijangkau oleh lingkungan layanan**: jika runner dapat dimulai secara manual tetapi gagal di bawah supervisor Anda, bandingkan pengguna, direktori home, `PATH`, dan batas memori. `--configure-git` dan Anthropic git proxy memerlukan git di `PATH` dan `~/.gitconfig` yang dapat ditulisi.
* **Secret lingkungan**: jika Anda telah mencabut secret tersebut atau salah mengetiknya, runner mencetak baris yang berisi `RegisterRunner auth failed`.
* **Tab Activity lingkungan**: buka lingkungan dan pilih **Activity**. Jika runner baru terus muncul di sana dan tidak ada yang mengambil pekerjaan, supervisor Anda sedang memulai ulang runner.

Untuk diagnosis terpandu pada host runner, jalankan [subperintah doctor](#troubleshooting).

### Sesi diambil, tetapi Claude tidak pernah menjawab pesan pertamanya

Jika runner mengambil sesi tetapi Claude tidak pernah menjawab pesan pertama, dan runner Anda menjangkau `api.anthropic.com` melalui proxy, proxy tersebut mungkin penyebabnya. Anthropic mengirimkan pesan-pesan ke sesi melalui satu respons streaming yang tetap terbuka. Jika proxy Anda melakukan buffering terhadapnya, atau mengakhirinya sebelum meneruskan apa pun, pesan pertama tidak pernah tiba. Permintaan lain dari sesi tetap berhasil.

Untuk memperbaikinya, ubah cara proxy menangani permintaan ini:

```text theme={null}
GET https://api.anthropic.com/v1/code/sessions/<session-id>/worker/events/stream
```

* **Jangan lakukan buffering terhadap respons**: teruskan respons saat tiba. Content type-nya adalah `text/event-stream`.
* **Biarkan respons tetap terbuka**: respons dapat tetap terbuka hingga satu jam. Jika proxy memiliki idle timeout, atur jauh di atas 15 detik, karena Anthropic secara default mengirimkan keep-alive setidaknya sesering itu.

Kemudian mulai sesi baru dan periksa apakah Claude menjawab. Menaikkan timeout runner tidak membantu: Anda hanya menunggu lebih lama sebelum runner menghentikan sesi.

#### Memastikan penyebabnya di log runner

Langkah-langkah ini ditujukan untuk runner pada Claude Code v2.1.287 atau yang lebih baru, dengan `--log-level info` default. Runner yang lebih lama mencatat baris-baris ini lebih lambat, atau tidak sama sekali.

1. Tunggu hingga [`--startup-timeout-min`](https://code.claude.com/docs/id/self-hosted-environments-reference#runner-cli-flags) berlalu, 15 menit secara default. Runner kemudian menghentikan sesi dan mencatat baris yang berisi `no child output for` dan diakhiri dengan `— releasing`.
2. Cari `SDKStartup: SSE` di log runner. Baris-baris ini mengikuti baris `finished: interrupted`, beberapa baris setelah baris pada langkah 1. Sebagian besar tidak memiliki timestamp, sehingga pengumpul log dapat menampilkannya di tempat lain.
3. Temukan baris tersebut dalam tabel ini:

| Baris log berisi | Yang harus dilakukan |
| :- | :- |
| `SSE connect failed (no response in 30s)` atau `SSE liveness timeout` | Periksa apakah proxy meneruskan permintaan, meneruskan header dan data respons saat tiba, dan tidak memutus koneksi |
| `SSE connect failed (HTTP` dan sebuah kode status | Periksa log proxy itu sendiri. Jika proxy menjawab permintaan tersebut sendiri, izinkan permintaan melewatinya. |
| `SSE connect failed (` dan teks lainnya | Teks di dalam tanda kurung adalah kesalahannya. Cari koneksi yang sama di log proxy itu sendiri. |
| `SSE stream ended by server`, berulang kali | Periksa batas waktu proxy untuk respons yang berumur panjang. Satu baris seperti itu adalah hal yang normal. |
| `SSE reconnected after` | Cari salah satu baris lain setelahnya. Baris ini tidak berarti ada sesuatu yang telah mencapai sesi. |

Jika log tidak memiliki satu pun dari baris-baris ini, atau log proxy tidak menunjukkan masalah apa pun, [laporkan masalah](#report-an-issue).

#### Menghentikan sesi agar tidak mencoba lagi

Sesi tetap menahan pesan tersebut dalam status tertunda, dan runner yang dapat melayani pemilik sesi akan mengambil sesi tersebut lagi. Hingga Anda mengubah proxy, sesi menunggu dengan cara yang sama setiap kali. Untuk menghentikannya, arsipkan sesi tersebut di claude.ai/code.

## Apa selanjutnya

* [Sesuaikan sesi](https://code.claude.com/docs/id/self-hosted-environments-configuration.md): script wrapper, lifecycle hook, runner on-demand, server MCP, dan izin
* [Uji end to end](https://code.claude.com/docs/id/self-hosted-environments-testing.md): verifikasi gambar runner baru dari CI sebelum mempromosikannya
* [Referensi](https://code.claude.com/docs/id/self-hosted-environments-reference.md): setiap flag CLI, variabel lingkungan, dan metrik
