Customize sessions in self-hosted environments changedself-hosted-environments-configuration
Nearest release: v2.1.287, published 10 hours before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 2 Oct 2026 03:56 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 2 Oct 2026 04:07 UTC.
Upstream edited
Recorded here
Lines+30added
Lines−6removed
From line
92
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits17to this page, all time
### Git configuration inside lifecycle hooks
The whole hunk
from line 92, old and new numbered
/
from line 92
9292
9393### checkout
9494
95Runs once per repository, in place of the runner's built-in clone and fetch. Use the hook to clone from a read-through mirror, seed a working tree from an archive, or apply per-session git auth. The runner sets:
95Runs once per repository, in place of the runner's built-in clone and fetch. Use the hook to clone from a read-through mirror, seed a working tree from an archive, or apply per-session git auth. The runner sets these variables, and may set other `CLAUDE_RUNNER_` variables that the table doesn't list:
9696
9797| Variable | Description |
9898| :- | :- |
from line 104
104104| `CLAUDE_RUNNER_API_BASE_URL` | Anthropic API base URL for session-scoped calls |
105105| `CLAUDE_RUNNER_CLIENT_PLATFORM` | The client surface that created the session, such as `web_claude_ai`, `desktop_app`, or `ios`. Unset when the session has no recorded or recognized surface. |
106106| `CLAUDE_CODE_SESSION_ACCESS_TOKEN` | The session access token, for session-scoped API calls |
107| `GIT_CONFIG_COUNT`, `GIT_CONFIG_KEY_n`, `GIT_CONFIG_VALUE_n` | Git settings the runner fixes for the git your hook runs. [Git configuration inside lifecycle hooks](#git-configuration-inside-lifecycle-hooks) describes them. Requires Claude Code v2.1.280 or later. |
107108
108109The script must leave a working tree at `CLAUDE_RUNNER_CHECKOUT_PATH` checked out at the requested revision. Detached HEAD is fine; the runner creates the session's working branch on top. The runner verifies the path contains a `.git` afterwards; if your hook materializes a non-git source such as Perforce or an unpacked tarball, set `CLAUDE_RUNNER_SKIP_GIT_VERIFY=1` in the runner's environment to skip that check. Git-based flows such as working-branch creation and pushing results require a git checkout, so export outcomes from non-git trees with a [`post-session` hook](#post-session).
109110
from line 135
134135| `CLAUDE_RUNNER_API_BASE_URL` | Anthropic API base URL for session-scoped calls |
135136| `CLAUDE_RUNNER_CLIENT_PLATFORM` | The client surface that created the session, such as `web_claude_ai`, `desktop_app`, or `ios`. Unset when the session has no recorded or recognized surface. Requires Claude Code v2.1.229 or later. |
136137| `CLAUDE_CODE_SESSION_ACCESS_TOKEN` | The session access token, for session-scoped API calls |
138| `GIT_CONFIG_COUNT`, `GIT_CONFIG_KEY_n`, `GIT_CONFIG_VALUE_n` | Git settings the runner fixes for the git your hook runs. [Git configuration inside lifecycle hooks](#git-configuration-inside-lifecycle-hooks) describes them. Requires Claude Code v2.1.280 or later. |
137139
138140`CLAUDE_RUNNER_EXIT_REASON` takes one of four values:
139141
from line 152
150152#!/usr/bin/env bash
151153set -u
152154IFS=':'
153# Pin config the session could have planted in the checkout's .git/config:
154155# -c overrides beat repo-local settings, blocking session-written fsmonitor,
155156# hook-path, and gpg-program config from executing code with the hook's
156# privileges. Repo-local credential.helper, core.sshCommand, and pushurl
157# still apply; if the hook holds credentials the session didn't, pin the
158# push URL and helper too (see the note below the script).
157# privileges. -c commit.gpgsign=false also leaves these rescue commits
158# unsigned under --configure-git.
159# Repo-local credential.helper and pushurl still apply, and on a runner
160# before v2.1.280 so does core.sshCommand; if the hook holds credentials
161# the session didn't, see the note below the script.
159162g() { git -c core.fsmonitor=false -c core.hooksPath=/dev/null \
160163 -c commit.gpgsign=false "$@"; }
161164for ws in $CLAUDE_RUNNER_WORKSPACE_PATHS; do
from line 170
167170done
168171```
169172
170The hook pushes with whatever git credentials are available in its own environment on the runner host. Under the [no-credentials-in-the-image posture](/docs/en/self-hosted-environments-deploy#configure-git), including when the built-in clone goes through the Anthropic git proxy, there are none, so mint a short-lived push credential inside the hook before pushing: exchange the session token the hook receives in `CLAUDE_CODE_SESSION_ACCESS_TOKEN` with your own token service, verifying it as [Verify session identity](/docs/en/self-hosted-environments-identity) describes. When the hook holds a credential the session didn't, also pin where it pushes: replace `origin` with an operator-supplied URL and pass `-c credential.helper=` plus your own helper, so repo-local config the session wrote can't redirect the credentialed push.
173The hook pushes with whatever git credentials are available in its own environment on the runner host. Under the [no-credentials-in-the-image posture](/docs/en/self-hosted-environments-deploy#configure-git), including when the built-in clone goes through the Anthropic git proxy, there are none, so mint a short-lived push credential inside the hook before pushing: exchange the session token the hook receives in `CLAUDE_CODE_SESSION_ACCESS_TOKEN` with your own token service, verifying it as [Verify session identity](/docs/en/self-hosted-environments-identity) describes. When the hook holds a credential the session didn't, replace `origin` with an operator-supplied URL and pass `-c credential.helper=` plus your own helper. [Git configuration inside lifecycle hooks](#git-configuration-inside-lifecycle-hooks) describes what session-written configuration can still affect.
171174
172175#### Hook timing when the runner releases a session
173176
from line 182
179182This applies whenever the runner releases a session: at the idle timeout, at the [`--retire-at`](/docs/en/self-hosted-environments-reference#runner-cli-flags) time, and, on a runner on v2.1.260 or later, at a session's [`--kill-session-after-min`](/docs/en/self-hosted-environments-reference#runner-cli-flags) limit. A session whose turn has ended and that holds only background tasks counts as idle here. Before v2.1.236, the runner released the session first and then ran this hook in both cases.
180183
181184During a `SIGTERM` drain, the runner holds the session lease until the hook finishes; see [Shutdown timing](/docs/en/self-hosted-environments-deploy#shutdown-timing).
185
186### Git configuration inside lifecycle hooks
187
188The `checkout` and `post-session` hooks run with the session's access token in their environment, and the git they run reads configuration files that sessions can write, such as `~/.gitconfig` and a checkout's `.git/config`. Before either hook runs, the runner sets git settings in the hook's environment, including the ones below, as `GIT_CONFIG_COUNT`/`GIT_CONFIG_KEY_n`/`GIT_CONFIG_VALUE_n` pairs and git environment variables. Git ranks those above every configuration file, and they apply only to the git your hooks run, not to the session's own git. At startup, the runner prints a `[runner:git] lifecycle hooks:` line that shows the hooks path, allowed protocols, gpg programs, and signing mode in effect. Requires Claude Code v2.1.280 or later.
189
190* **Git hooks**: unless you supply a value, `core.hooksPath` is `/dev/null`, so git skips the hooks in a repository's `.git/hooks` and any hooks directory that `~/.gitconfig` names. To supply one, export `core.hooksPath` as a `GIT_CONFIG_KEY_n`/`GIT_CONFIG_VALUE_n` pair in the runner's environment. The runner also reads `core.hooksPath` from the system git configuration, and uses it only when the runner's user can't write that file, the directory it names, or the hook files in it. When the runner ignores a value, a `[runner:warn]` line at startup names the value and the reason.
191* **File system monitor**: `core.fsmonitor` is empty, so git in your hook doesn't run a monitor program that a configuration file names.
192* **Remote protocols**: `GIT_ALLOW_PROTOCOL` is `https:http:ssh`. A clone, fetch, or push that uses a local path, a `file://` URL, or a `git://` URL fails with `fatal: transport 'file' not allowed` or `fatal: transport 'git' not allowed`.
193* **SSH command and credential prompt**: git in your hook ignores `core.sshCommand` and `core.askPass` from configuration files. To use your own SSH command, set `GIT_SSH_COMMAND` in the runner's environment. To use a credential prompt program, set `GIT_ASKPASS` there. Sessions inherit the runner's environment, so both variables also reach the session's own git. Don't put a credential in either.
194* **gpg programs**: `gpg.program`, `gpg.openpgp.program`, `gpg.x509.program`, and `gpg.ssh.program` are paths the runner sets, never values from a configuration file.
195* **Commit signing**: with [`--configure-git`](/docs/en/self-hosted-environments-deploy#let-the-runner-configure-git), commits you make from a hook are signed as the session. Without the flag, `commit.gpgsign` and `tag.gpgsign` are `false`.
196
197To change one of these settings, use the runner's environment or a `git -c` option inside the hook:
198
199* **Configuration pairs**: a `GIT_CONFIG_KEY_n`/`GIT_CONFIG_VALUE_n` pair you export in the runner's environment replaces the runner's value for the same key. Number your pairs from `0` and set `GIT_CONFIG_COUNT` to how many there are. When the last pair the count announces is missing, the runner ignores all of your pairs and logs a `[runner:warn]` line at startup.
200* **Git environment variables**: the runner leaves `GIT_ALLOW_PROTOCOL`, `GIT_SSH_COMMAND`, and `GIT_ASKPASS` as you set them in its environment.
201* **`git -c` options**: a `git -c` option inside the hook overrides a `GIT_CONFIG_KEY_n` pair, the runner's or yours. It doesn't change `GIT_ALLOW_PROTOCOL`, `GIT_SSH_COMMAND`, or `GIT_ASKPASS`, which git reads ahead of any configuration.
202
203Git in your hook still reads every setting the runner doesn't set, such as credential helpers, `url.*.insteadOf` rewrites, and filter drivers, from every configuration file, including the ones sessions can write. A credential helper or filter driver named in one of those files runs as a program with your hook's privileges, and configuration in those files can still change where a push from your hook goes, including a push to a URL you pass on the command line.
204
205Before v2.1.280, the runner set none of these settings, and under `--configure-git` a commit made from a hook failed unless the hook passed `-c commit.gpgsign=false`.
182206
183207### command
184208
No line in this hunk matches that.