Follow Discord
Sweep 27 Oct 2026 · 19:04Z Build v2.1.312 531 read Stable v2.1.308 Latest v2.1.312 Next v2.1.312 Feeds RSS JSON llms.txt llms-full.txt Unofficial
One change · claude-code

Customize sessions in self-hosted environments changedself-hosted-environments-configuration

Nearest release: v2.1.310, published 20 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 24 Oct 2026 17:31 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 24 Oct 2026 17:37 UTC.

Upstream edited
Recorded here
Lines+62added
Lines−6removed
From line 32 where the diff opens
First seen 14 Aug 2026 this site's first read of the page
Recorded edits36to this page, all time

#### Fix re-opened sessions that stay Queued or show Failed ### Stop sessions that fail to fetch server-managed settings

The whole hunk

from line 32, old and new numbered
/
lines
from line 32
3232| `CLAUDE_CODE_REMOTE_SESSION_UUID` | The same session ID in canonical UUID form, for systems that key on UUIDs. |
3333| `CLAUDE_CODE_REMOTE_SLACK_THREAD_URL` | For a [Claude Tag](https://claude.com/docs/claude-tag/overview) session that belongs to one Slack thread, the link to that thread. Unset for other sessions, and can be unset for a thread session too. |
3434| `CLAUDE_CODE_REMOTE_SLACK_THREAD_TS` | For a Claude Tag session that belongs to one Slack thread, that thread's Slack timestamp, such as `1700000000.000100`. Can be unset, and can be set when `CLAUDE_CODE_REMOTE_SLACK_THREAD_URL` isn't, so check each variable on its own. |
35| `CLAUDE_CODE_REMOTE_SLACK_CHANNEL_ID` | For a Claude Tag session, the ID of the Slack channel or direct message that the session serves or that its thread is in, such as `C0123ABCDE`. A Slack timestamp identifies a message within one channel, so record this ID together with `CLAUDE_CODE_REMOTE_SLACK_THREAD_TS`. Unset for other sessions, and can be unset for a Claude Tag session too. |
3536| `CLAUDE_SESSION_INGRESS_TOKEN_FILE` | Absolute path to a per-session file holding the current session JWT, kept fresh across token refreshes. Shell subprocesses read it for their `Authorization` header when downloading attachments the user added to the session. `exec` preserves the variable automatically; a wrapper that rebuilds the child's environment must carry the variable over, or attachment downloads silently stop working. |
3637| `CLAUDE_CONFIG_DIR` | Per-session Claude config directory, written at session start from the snapshot of the runner host's config that the runner captures at startup; see [Permissions and tool approval](#permissions-and-tool-approval). Writes here are isolated to this session. The directory stays under `<base-dir>/_sessions/` after the session ends unless you start the runner with [`--remove-session-state`](https://code.claude.com/docs/en/self-hosted-environments-reference#runner-cli-flags); see [Reuse a pre-warmed checkout](https://code.claude.com/docs/en/self-hosted-environments-deploy.md#reuse-a-pre-warmed-checkout). |
3738| `CLAUDE_CODE_REMOTE_MCP_CONFIG` | Path to a file in `CLAUDE_CONFIG_DIR` holding the MCP server definitions that Anthropic's control plane delivered for this session. If your wrapper rebuilds the child's environment, carry this variable over. Without it, when the control plane updates the delivered servers, the child shuts down any stdio, HTTP, or SSE server your wrapper adds with [`--mcp-config`](https://code.claude.com/docs/en/cli-reference#cli-flags) that isn't in the update. Requires Claude Code v2.1.299 or later. |
from line 41
4041 
4142The wrapper also inherits the rest of the child's managed environment, including any server-provided environment variables. `exec` propagates all of it automatically; if your wrapper spawns the child another way, forward the full environment.
4243 
43`CLAUDE_CODE_REMOTE_SLACK_THREAD_URL` and `CLAUDE_CODE_REMOTE_SLACK_THREAD_TS` reach your wrapper or [`command` hook](#command). They also reach what the session runs, such as shell commands, git hooks, and Claude Code hooks. The `checkout`, `post-session`, and `spawn-runner` hooks don't receive them.
44You can read the `CLAUDE_CODE_REMOTE_SLACK_*` variables in your wrapper or [`command` hook](#command), and in what the session runs, such as shell commands, git hooks, and Claude Code hooks. The `checkout`, `post-session`, and `spawn-runner` hooks don't receive them.
4445 
4546### Give a default to variables that can be unset
4647 
47`CCR_SESSION_ACCOUNT_EMAIL`, `CLAUDE_RUNNER_CLIENT_PLATFORM`, `CLAUDE_CODE_REMOTE_SLACK_THREAD_URL`, and `CLAUDE_CODE_REMOTE_SLACK_THREAD_TS` can each be unset. If your script uses `set -u`, Bash stops with `unbound variable` when it expands one that's unset, so expand them with a default, such as `"${CCR_SESSION_ACCOUNT_EMAIL:-}"`.
48If your script uses `set -u`, expand `CCR_SESSION_ACCOUNT_EMAIL`, `CLAUDE_RUNNER_CLIENT_PLATFORM`, `CLAUDE_CODE_REMOTE_SLACK_THREAD_URL`, `CLAUDE_CODE_REMOTE_SLACK_THREAD_TS`, and `CLAUDE_CODE_REMOTE_SLACK_CHANNEL_ID` with a default, such as `"${CCR_SESSION_ACCOUNT_EMAIL:-}"`. Each can be unset, and under `set -u` Bash stops with `unbound variable` when it expands an unset one.
4849 
4950### Quote the arguments and variables your script expands
5051 
from line 139
138139| Variable | Description |
139140| :- | :- |
140141| `CLAUDE_RUNNER_REPO_URL` | Repository URL to clone, after any `--git-host-rewrite` and `--git-ssh-rewrite` have been applied |
141| `CLAUDE_RUNNER_REPO_REF` | Revision to check out, as the session requested it: a branch, tag, commit SHA, or full reference name such as `refs/pull/<number>/head`. Empty means the repository's default branch. |
142| `CLAUDE_RUNNER_REPO_REF` | Revision to check out, as the session requested it: a branch, tag, commit SHA, or full reference name such as `refs/heads/<branch>` or `refs/pull/<number>/head`. Empty means the repository's default branch. |
142143| `CLAUDE_RUNNER_CHECKOUT_PATH` | Absolute path where the working tree must be left |
143144| `CLAUDE_RUNNER_SESSION_ID` | Session ID in the tagged `session_...` form, for logging and correlation |
144145| `CLAUDE_RUNNER_SESSION_UUID` | The same session ID in canonical UUID form |
from line 320
319320 
320321The contract has four rules, whichever platform your hook provisions on:
321322 
3221. **Be idempotent on `CLAUDE_RUNNER_ORDER_ID`.** Redelivery of the same request must spawn at most one runner. Derive a deterministic resource name from the order ID and let your platform reject the duplicate. Don't key on `CLAUDE_RUNNER_SESSION_ID` instead. Every re-request for a session carries the same session ID with a new order ID, so a workload named or deduplicated by the session ID is created once and never again for that session.
3231. **Be idempotent on `CLAUDE_RUNNER_ORDER_ID`.** Redelivery of the same request must spawn at most one runner. Derive a deterministic resource name from the order ID and let your platform reject the duplicate. Don't key on `CLAUDE_RUNNER_SESSION_ID` instead. Every re-request for a session carries the same session ID with a new order ID. A workload named or deduplicated by the session ID isn't created again while the first one exists.
3233242. **Don't retry the workload.** One order ID means at most one created workload. If the runner never registers, Anthropic re-requests with a fresh order ID after `--expected-spawn-seconds`.
3243253. **Use the exit-code contract.** Exit with the status that matches the outcome:
325326 
326327 * **Exit 0**: submitted.
327 * **Exit 1, or still running when `--hook-timeout` ends**: retryable failure. The session backs off and is re-offered.
328 * **Exit 1, or still running when `--hook-timeout` ends**: retryable failure. The session backs off and is re-offered. After 10 failed requests in a row, it's blocked as it is after exit 2 or higher.
328329 * **Exit 2 or higher**: non-retryable failure. The session is blocked from spawning again until a user sends it a new message or an [Owner](https://code.claude.com/docs/en/cloud-environments.md#organization-shared-environments) selects **Retry** on it in the environment's **Activity** tab.
329330 
330331 While a session backs off, it shows **Queued** in the **Activity** tab with no spawn error, so read the hook's output in the orchestrator's log. Once blocked, the session shows **Failed**. Expand it to read the spawn error, taken from the last 512 characters of the hook's stderr. Write an actionable error to stderr, and no secrets. In a shell hook, [keep transient failures retryable](#keep-transient-failures-retryable-in-a-shell-hook).
from line 335
334335 
335336Everything the hook writes to stdout or stderr appears in the orchestrator's log with credentials automatically redacted. If sessions stay queued, check the orchestrator's `/healthz` body for queue counts, then open your environment's **Activity** tab on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments): expand a failed session there for its spawn error, and select **Retry** to re-request it.
336337 
337A session that stays queued with no spawn error in the **Activity** tab can mean the hook is keyed on the session ID. To confirm, check whether your platform has a workload for that session's first spawn request and none for the re-requests. If so, key the workload on `CLAUDE_RUNNER_ORDER_ID` instead.
338#### Fix re-opened sessions that stay Queued or show Failed
338339 
340If re-opened sessions stay **Queued** or show **Failed**, or your hook times out on them, one cause is a hook that names or deduplicates workloads by the session ID. A session is re-opened when its user sends it a message after its runner has exited. For example, take a hook that builds each workload's name like this:
341 
342```bash theme={null}
343name="runner-${CLAUDE_RUNNER_SESSION_ID}"
344```
345 
3461. At the session's first spawn request, the hook creates a workload with that name, and a runner starts
3472. The runner exits, for example after [`--release-idle-session-min`](#make-on-demand-runners-exit-when-idle), and the user later re-opens the session
3483. The session's next spawn request carries the same session ID and a new order ID, so the hook builds the same name
3494. If your platform still has the workload from the first request, because nothing [removed](#make-on-demand-runners-exit-when-idle) it when its runner exited, the hook creates no new workload and no runner starts
3505. Each later spawn request for the session ends the same way
351 
352Open your own hook and read what it does when the workload already exists, for example when your platform rejects the duplicate name. Find that behavior in the first column to see what the re-opened session shows in the **Activity** tab:
353 
354| When the workload exists, your hook | **Queued** changes to **Failed** | Spawn error |
355| :- | :- | :- |
356| Exits 0 without creating anything | After 10 such requests in a row | `runner did not register within ETA after max attempts` |
357| Exits 1 | After 10 such requests in a row | Your hook's stderr tail, or `exit 1` if it wrote nothing to stderr |
358| Exits 2 or higher | Right away | Your hook's stderr tail, or `exit` and the code, such as `exit 2`, if it wrote nothing to stderr |
359| Waits until `--hook-timeout` ends | After 10 such requests in a row | Your hook's stderr tail, or `spawn-runner hook timed out after 60000ms` if it wrote nothing to stderr |
360 
361In the last row's spawn error, `60000` is your `--hook-timeout` in milliseconds. When a hook waits until `--hook-timeout` ends, the orchestrator's log also gets a `timed out after` line.
362 
363To fix the hook, build each name or key from `CLAUDE_RUNNER_ORDER_ID`, as [rule 1 of the hook contract](#the-spawn-runner-hook) requires. In the example, the name becomes:
364 
365```bash theme={null}
366name="runner-${CLAUDE_RUNNER_ORDER_ID}"
367```
368 
369If your hook waits, also make it exit once it has submitted the workload, because a waiting hook [delays its orchestrator's next hooks](https://code.claude.com/docs/en/self-hosted-environments-reference.md#orchestrator-cli-flags). After you change the hook, check each stuck session's status in the **Activity** tab:
370 
371* **Queued**: the session gets its next spawn request on its own
372* **Failed**: select **Retry** on it as an Owner, or have its user send it a new message
373 
339374#### Make on-demand runners exit when idle
340375 
341376By default, a runner your hook starts doesn't exit when its session's turn finishes, so the workload you submitted keeps running. To make runners exit, have your hook pass these [runner flags](https://code.claude.com/docs/en/self-hosted-environments-reference.md#runner-cli-flags):
from line 790
755790Outside [Claude Tag](https://claude.com/docs/claude-tag/overview) sessions, a session in a self-hosted environment runs with [auto memory](https://code.claude.com/docs/en/memory.md#auto-memory) off by default. For instructions that should carry across sessions, use the `CLAUDE.md` in your runner image or in the repository.
756791 
757792The runner's snapshot of the host's `~/.claude/` leaves out the `projects/` directory. Auto memory's default storage location is under that directory. If you put memory files there, the runner doesn't seed them into sessions, and they don't turn auto memory on.
793 
794### Stop sessions that fail to fetch server-managed settings
795 
796To keep a session that fetches [server-managed settings](https://code.claude.com/docs/en/server-managed-settings.md) from starting when that fetch fails, set [`forceRemoteSettingsRefresh`](https://code.claude.com/docs/en/settings-reference#forceremotesettingsrefresh) to `true` in the [`managed-settings.json`](https://code.claude.com/docs/en/settings#where-settings-live) in your runner image. A reply from Anthropic's API that your organization has no server-managed settings counts as a successful fetch.
797 
798Before you add the key, check whether the environment runs Claude Tag channel sessions or sends model requests to Bedrock or Agent Platform:
799 
800* **Claude Tag channel sessions**: leave the key out on runners in an environment that an Owner [routes Claude Tag channels to](https://code.claude.com/docs/en/cloud-environments.md#set-the-environment-a-claude-tag-channel-uses). Anthropic's API refuses these sessions' request for server-managed settings, so with the key set they fail to start. Put their rules in the runner image's `managed-settings.json`, which [they still read](https://code.claude.com/docs/en/managed-settings.md#where-and-when-a-policy-applies). To use the key for your other sessions, run those in a separate environment.
801* **Bedrock or Agent Platform**: sessions on these providers [don't fetch server-managed settings](#what-differs-from-sessions-on-the-anthropic-api), so they start whether or not you set the key. Enforce your rules from the same `managed-settings.json` instead.
802 
803This `managed-settings.json` sets the key:
804 
805```json managed-settings.json theme={null}
806{
807 "forceRemoteSettingsRefresh": true
808}
809```
810 
811Set the key in that file even if you also set it in the claude.ai admin console. A new session has no [cached copy](https://code.claude.com/docs/en/server-managed-settings.md#fetch-and-caching-behavior) of your server-managed settings, so if you set the key only in the console, the session doesn't have it yet. If its fetch then fails, the session runs without those settings until a later fetch succeeds.
812 
813To confirm that a failed settings fetch is why a session didn't start, look at [Claude Code's last output](#keep-stdin-and-file-descriptor-3-attached) in the session or the runner's log. It includes `Your organization requires remote managed settings to load, but they could not be loaded.`
758814 
759815### Repository settings in sessions with several repositories
760816 
Feedback