# Enterprise Admin

> Every page below, concatenated. Each section gives the page title, its upstream source and its path in this repository.

---

## Set up Claude Code for your organization - Claude Code Docs

Source: https://code.claude.com/docs/en/admin-setup
Path: 13-Enterprise-Admin/admin-setup.md

## On this page

- [Choose your API provider](#choose-your-api-provider)
- [Decide how settings reach devices](#decide-how-settings-reach-devices)
  - [WSL sessions in Claude Code Desktop](#wsl-sessions-in-claude-code-desktop)
- [Decide what to enforce](#decide-what-to-enforce)
  - [Connected GitHub accounts](#connected-github-accounts)
- [Set up usage visibility](#set-up-usage-visibility)
- [Review data handling](#review-data-handling)
- [Verify and onboard](#verify-and-onboard)
- [Next steps](#next-steps)

Setup and access

# Set up Claude Code for your organization

Copy pageCopy page

A decision map for administrators deploying Claude Code, covering API providers, managed settings, policy enforcement, usage monitoring, and data handling.

Copy pageCopy page

Claude Code enforces organization policy through managed settings that take precedence over local developer configuration. You deliver those settings from the Claude admin console, your mobile device management (MDM) system, or a file on disk. The settings control which tools, commands, servers, and network destinations Claude can reach. This page walks through the deployment decisions in order. Each row links to the section below and to the reference page for that area.

SSO, SCIM provisioning, and seat assignment are configured at the Claude account level. See the [Claude Enterprise Administrator Guide](https://claude.com/resources/tutorials/claude-enterprise-administrator-guide) and [seat assignment](https://support.claude.com/en/articles/11845131-use-claude-code-with-your-team-or-enterprise-plan) for those steps.

| Decision                                                                | What you’re choosing                                | Reference                                                                                                                                                                                         |
|:------------------------------------------------------------------------|:----------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Choose your API provider](#choose-your-api-provider)                   | Where Claude Code authenticates and how it’s billed | [Authentication](../02-Claude-Code-CLI/authentication.md), [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md), [Google Cloud’s Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md), [Microsoft Foundry](../02-Claude-Code-CLI/microsoft-foundry.md) |
| [Decide how settings reach devices](#decide-how-settings-reach-devices) | How managed policy reaches developer machines       | [Server-managed settings](server-managed-settings.md), [Delivery mechanisms](managed-settings.md#delivery-mechanisms)                                                                 |
| [Decide what to enforce](#decide-what-to-enforce)                       | Which tools, commands, and integrations are allowed | [Permissions](../02-Claude-Code-CLI/permissions.md), [Sandboxing](../02-Claude-Code-CLI/sandboxing.md)                                                                                                                            |
| [Set up usage visibility](#set-up-usage-visibility)                     | How you track spend and adoption                    | [Analytics](analytics.md), [Monitoring](monitoring-usage.md), [Costs](../17-Billing-Plans/costs.md)                                                                                                 |
| [Review data handling](#review-data-handling)                           | Data retention and compliance posture               | [Data usage](data-usage.md), [Security](security.md)                                                                                                                                  |


[​](#choose-your-api-provider)

Choose your API provider

Claude Code connects to Claude through one of several API providers. Your choice affects billing, authentication, which compliance posture you inherit, and which Claude Code features your developers can use.

| Provider                      | Choose this when                                                                                                                      |
|:------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------|
| Claude for Teams / Enterprise | You want Claude Code and claude.ai under one per-seat subscription with no infrastructure to run. This is the default recommendation. |
| Claude Console                | You’re API-first or want pay-as-you-go billing                                                                                        |
| Amazon Bedrock                | You want to inherit existing AWS compliance controls and billing                                                                      |
| Google Cloud’s Agent Platform | You want to inherit existing GCP compliance controls and billing                                                                      |
| Microsoft Foundry             | You want to inherit existing Azure compliance controls and billing                                                                    |

Some Claude Code features require a claude.ai account. [Cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md), [cloud scheduled tasks](../02-Claude-Code-CLI/web-scheduled-tasks.md), [Code Review](../02-Claude-Code-CLI/code-review.md), [Remote Control](../02-Claude-Code-CLI/remote-control.md), and the [Chrome extension](../03-IDE-Integrations/chrome.md) aren’t available through Console API keys or cloud-provider credentials alone. If you deploy through Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry, plan whether developers also need Claude for Teams or Enterprise seats. Each feature page lists its plan requirements. For the full provider comparison covering authentication, regions, and feature parity, see the [enterprise deployment overview](../02-Claude-Code-CLI/third-party-integrations.md). Each provider’s auth setup is in [Authentication](../02-Claude-Code-CLI/authentication.md). Proxy and firewall requirements in [Network configuration](network-config.md) apply regardless of provider. If you want a single endpoint in front of multiple providers or centralized request logging, see [LLM gateway](llm-gateway.md).


[​](#decide-how-settings-reach-devices)

Decide how settings reach devices

Managed settings define organization policy. Claude Code checks the four sources in the table below in priority order. [How Claude Code combines managed sources](managed-settings.md#precedence-within-the-managed-tier) says which of them apply, what a policy helper changes, and how to compose every source. The table is the decision map.

[TABLE]

Claude Code fetches server-managed settings at startup and refreshes them hourly during the session, with no endpoint infrastructure to deploy. Delivery through the claude.ai admin console requires a Claude for Teams or Enterprise plan. Deployments on Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry can get the same remote delivery by running a [Claude apps gateway](claude-apps-gateway.md), or use one of the file-based or OS-level mechanisms instead. If your organization mixes providers, configure [server-managed settings](server-managed-settings.md) for claude.ai users plus a [file-based or plist/registry fallback](managed-settings.md#delivery-mechanisms) so other users still receive managed policy. The plist and HKLM registry locations work with any provider and resist tampering because they require admin privileges to write. The Windows user registry at HKCU is writable without elevation, so treat it as a convenience default rather than an enforcement channel. By default, WSL reads only the Linux file path at `/etc/claude-code`. To extend your Windows registry and `C:\Program Files\ClaudeCode` policy to WSL on the same machine, set [`wslInheritsWindowsSettings: true`](../02-Claude-Code-CLI/settings-reference.md#wslinheritswindowssettings) in either of those admin-only Windows sources. Whichever mechanism you choose, managed values take precedence over user and project settings, apart from a few security-sensitive [exceptions](../02-Claude-Code-CLI/settings.md#exceptions-to-managed-settings-precedence). Array settings such as `permissions.allow` and `permissions.deny` merge entries from all sources, so developers can extend managed lists but not remove from them. For `fallbackModel`, `availableModels`, and [`modelPicker`](../02-Claude-Code-CLI/settings-reference.md#modelpicker), the managed value replaces lower layers rather than merging.


[​](#wsl-sessions-in-claude-code-desktop)

WSL sessions in Claude Code Desktop

On Windows, [Claude Code Desktop can run Code sessions inside a WSL 2 distribution](../16-Mobile-Desktop/desktop-wsl.md). The session’s Claude Code process runs inside the distribution, so it resolves managed settings through the WSL discovery path above: Windows-only sources don’t reach it unless `wslInheritsWindowsSettings: true` is deployed. Claude Desktop turns off WSL sessions by default on devices it detects as organization-managed, for example when `C:\Program Files\ClaudeCode\managed-settings.json` exists. To turn them on, deploy a Windows registry policy, which requires Claude Desktop v1.19367.0 or later:

- Create a value named `disableWslSessions` under `HKLM\SOFTWARE\Policies\Claude` and set it to the `REG_SZ` string `false` or the `REG_DWORD` `0`. This value sits under the Claude Desktop policy key, separate from the `ClaudeCode` key that carries managed settings. Deploy the value under HKLM, which requires administrator privileges to write. A value under HKCU doesn’t enable WSL sessions.
- Leave `C:\Program Files\ClaudeCode\managed-settings.json` in place if you deploy it. Once `disableWslSessions` is `false` under HKLM, Desktop allows WSL sessions even though that file is present.

Desktop reads the policy each time a WSL session starts, so you don’t need to restart the app after deploying it. If a device still refuses WSL sessions, open **Help \> Troubleshooting \> Show Logs in File Explorer** in Claude Desktop on that device, which saves a copy of its log folder to Downloads. Search `main.log` in that copy for `[wslPolicyGate] denying WSL session`. The reason for the denial follows in parentheses, such as `(cli-file-present)`. After WSL sessions are enabled, extend your managed settings to them:

- Deploy `wslInheritsWindowsSettings: true` through the HKLM registry or the `C:\Program Files\ClaudeCode` file so WSL sessions inherit the same policy as host sessions.
- Verify by running `/status` inside a WSL session and reading the `Setting sources` line. To interpret what it lists, see [Read the source in /status](managed-settings.md#read-the-source-in-/status).

Processes inside the WSL 2 utility VM aren’t visible to Windows-side endpoint detection sensors. To observe in-distro process and file activity, check your endpoint detection vendor’s WSL guidance for a Linux sensor you can run inside the distribution and the exclusions it needs. Claude Code’s [OpenTelemetry tool-execution telemetry](monitoring-usage.md) is emitted identically for WSL and native sessions.


[​](#decide-what-to-enforce)

Decide what to enforce

Managed settings can lock down tools, sandbox execution, restrict MCP servers and plugin sources, and control which hooks run. Each row is a control surface with the setting keys that drive it.

| Control                                                                              | What it does                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Key settings                                                                                                                            |
|:-------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------|
| [Permission rules](../02-Claude-Code-CLI/permissions.md)                                             | Allow, ask, or deny specific tools and commands                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `permissions.allow`, `permissions.deny`                                                                                                 |
| [Permission lockdown](../02-Claude-Code-CLI/permissions.md#managed-only-settings)                    | Make managed settings the [only settings source of permission rules](../02-Claude-Code-CLI/settings-reference.md#allowmanagedpermissionrulesonly). Disable `--dangerously-skip-permissions`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `allowManagedPermissionRulesOnly`, `permissions.disableBypassPermissionsMode`                                                           |
| [Starting permission mode](../02-Claude-Code-CLI/permission-modes.md#which-mode-a-session-starts-in) | Choose the permission mode your developers’ terminal sessions start in instead of the built-in starting permission mode, or remove auto mode. [Switch permission modes](../02-Claude-Code-CLI/permission-modes.md#switch-permission-modes) lists when the VS Code extension reads a `defaultMode` you set                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `permissions.defaultMode`, `permissions.disableAutoMode`                                                                                |
| [Sandboxing](../02-Claude-Code-CLI/sandboxing.md)                                                    | OS-level filesystem and network isolation with domain allowlists                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `sandbox.enabled`, `sandbox.network.allowedDomains`                                                                                     |
| [Managed policy CLAUDE.md](../02-Claude-Code-CLI/memory.md#deploy-organization-wide-claude-md)       | Organization-wide behavioral guidance for Claude rather than a client-enforced rule                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | File at the managed policy path                                                                                                         |
| [MCP server control](../06-MCP-Tools/General/managed-mcp.md)                                           | Restrict which MCP servers users can add or connect to, deploy a fixed set, or provide remote servers to every user alongside their own                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `allowedMcpServers`, `deniedMcpServers`, `allowManagedMcpServersOnly`, `managedMcpServers`, or a deployed `managed-mcp.json` file       |
| [Plugin marketplace control](../08-Plugins-Skills/plugins-org.md#restrict-what-users-can-install)   | Restrict which marketplace sources users can add and install from, reject the CLI flags that sideload plugins, agents, and MCP servers for a single run, block [`command` plugin sources](../08-Plugins-Skills/plugins-marketplace-reference.md#command-plugin-source), and allowlist which marketplaces’ plugins can be suggested                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `strictKnownMarketplaces`, `blockedMarketplaces`, `disableSideloadFlags`, `disableCommandPluginSources`, `pluginSuggestionMarketplaces` |
| [Customization lockdown](../02-Claude-Code-CLI/settings-reference.md#strictpluginonlycustomization)  | Block skills, agents, hooks, and MCP servers from user and project sources, so they can only come from plugins or managed settings. Locking skills also stops the [skills your developers enable on claude.ai](../08-Plugins-Skills/skills.md#where-synced-skills-load) from syncing                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `strictPluginOnlyCustomization`                                                                                                         |
| [Disable claude.ai sync](../02-Claude-Code-CLI/settings-reference.md#syncclaudeaiskills)             | Stop Claude Code from loading the [skills](../08-Plugins-Skills/skills.md#how-synced-skills-behave) and [plugins](../08-Plugins-Skills/plugins-loading.md#synced-plugins) your developers enable on claude.ai. If you turn off Skills for your organization on claude.ai, Claude Code stops syncing both, and on v2.1.273 or later it also removes the ones it already synced. To stop either one without turning Skills off, set its key to `false` in managed settings                                                                                                                                                                                                                                                                                                                                                                                                                                                        | `syncClaudeAiSkills`, `syncClaudeAiPlugins`                                                                                             |
| [Hook restrictions](../02-Claude-Code-CLI/settings-reference.md#allowmanagedhooksonly)               | Restrict which hooks run and restrict HTTP hook URLs; see [what runs under `allowManagedHooksOnly`](../02-Claude-Code-CLI/settings-reference.md#what-runs-under-allowmanagedhooksonly) for the full effect list                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `allowManagedHooksOnly`, `allowedHttpHookUrls`                                                                                          |
| [Login enforcement](../02-Claude-Code-CLI/settings-reference.md#forceloginmethod)                    | Restrict login to a specific method or Anthropic organization. The method restriction applies across the VS Code extension, Agent SDK, `claude setup-token`, and `/install-github-app`, and the terminal’s interactive login screen, reached by `/login` or first-run onboarding, pre-selects the method without enforcing it; Claude Code verifies the organization for claude.ai account logins in the terminal, VS Code extension, and Agent SDK, and doesn’t check it for Claude Console logins or for [gateway](claude-apps-gateway.md) sign-in. Before v2.1.212, only terminal logins applied either key. When set, sessions authenticated by `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `apiKeyHelper` are blocked at startup; cloud provider sessions aren’t affected unless one of those credentials, or an API key saved by an earlier Claude Console login, is also present | `forceLoginMethod`, `forceLoginOrgUUID`                                                                                                 |
| [Provider restrictions](../02-Claude-Code-CLI/settings-reference.md#allowedproviders)                | Limit which API providers a machine may use. A session on a provider that isn’t listed is refused at startup, at login, and when it next contacts the API. Requires Claude Code v2.1.285 or later                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `allowedProviders`                                                                                                                      |
| [Disable agent view](../09-Agents-Patterns/agent-view.md#how-background-sessions-are-hosted)         | Turn off `claude agents`, `--bg`, `/background`, and the on-demand supervisor                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `disableAgentView`                                                                                                                      |
| [Configure the corporate launcher](corporate-launcher.md)                      | Prefix the [background-agent supervisor](../09-Agents-Patterns/agent-view.md#how-background-sessions-are-hosted), its workers, and the [other covered background processes](corporate-launcher.md#what-the-launcher-covers) with a required corporate launcher instead of turning agent view off                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `processWrapper`                                                                                                                        |
| [Model restrictions](../02-Claude-Code-CLI/model-config.md#restrict-model-selection)                 | `availableModels` filters which models appear in the picker. Adding `enforceAvailableModels` also constrains the auto-selected default model. See [surface coverage](../02-Claude-Code-CLI/model-config.md#surface-coverage) for how this setting reaches the CLI, web, and IDE                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `availableModels`, `enforceAvailableModels`                                                                                             |
| [Effort cap](../02-Claude-Code-CLI/settings-reference.md#maxeffortlevel)                             | Cap the [effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) for every model or per model, on every provider                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `maxEffortLevel`                                                                                                                        |
| [Version floor](../02-Claude-Code-CLI/settings-reference.md#minimumversion)                          | Prevent auto-update from installing below an org-wide minimum                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `minimumVersion`                                                                                                                        |
| [Required version range](../02-Claude-Code-CLI/settings-reference.md#requiredminimumversion)         | Refuse to start at all when the running version is outside an org-approved range. Stronger than `minimumVersion`, which only blocks downgrades                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | `requiredMinimumVersion`, `requiredMaximumVersion`                                                                                      |
| [Telemetry opt-out](data-usage.md#telemetry-services)                          | Turn off Anthropic-bound usage metrics, error reports, and surveys on every device                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `env` with `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` set to `1`; the linked section lists the per-category variables                   |

If your members sign in through claude.ai or the Anthropic API and you’re on a Claude Enterprise plan, you can also govern models from your organization’s admin settings without deploying anything:

- [Organization model restrictions](../02-Claude-Code-CLI/model-config.md#organization-model-restrictions): disable individual models. Enforced server-side.
- [Organization default model](../02-Claude-Code-CLI/model-config.md#organization-default-model): set which model new sessions start on. Members can still switch models. To return them to your default at launch, turn on the override that section describes. To limit which models they can pick, use [organization model restrictions](../02-Claude-Code-CLI/model-config.md#organization-model-restrictions).
- [Organization effort limits](../02-Claude-Code-CLI/model-config.md#organization-effort-limits): cap effort levels per role. Enforced server-side.

None of these controls reach sessions on Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, or [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md). On those providers, use managed settings instead: `availableModels` for restrictions, `model` for a default, and [`maxEffortLevel`](../02-Claude-Code-CLI/settings-reference.md#maxeffortlevel) for an effort cap. [Cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) have their own admin surfaces on claude.ai:

- **Cloud environments page**: Owners create [organization-shared environments](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) that set the [network access level](../02-Claude-Code-CLI/cloud-environments.md#network-access), environment variables, and setup script for members’ cloud sessions.
- **Default environment**: Owners choose the organization’s default environment separately, at [claude.ai/admin-settings/claude-code](https://claude.ai/admin-settings/claude-code).
- **Git providers page**: see [Connected GitHub accounts](#connected-github-accounts) for the GitHub accounts linked to your organization.

Permission rules and sandboxing cover different layers. Denying WebFetch blocks Claude’s fetch tool, but if Bash is allowed, `curl` and `wget` can still reach any URL. Sandboxing closes that gap with a network domain allowlist enforced at the OS level. For the threat model these controls defend against, see [Security](security.md).


[​](#connected-github-accounts)

Connected GitHub accounts

On Team and Enterprise plans, the GitHub section of [**Organization settings \> Git providers**](https://claude.ai/admin-settings/source-control) lists the GitHub organizations and personal accounts linked to your Claude organization through the [Claude GitHub App](https://github.com/apps/claude). Claude Code, [Claude Tag](https://claude.com/docs/claude-tag/admins/configure-github), and Claude Security share the list. Opening the page requires an admin role in your Claude organization. An admin or a member can link an account:

- **Admin connection**: an admin clicks **Connect** in that section, or **Add organization** once an account is connected, and installs the Claude GitHub App on a GitHub organization. Linking an organization this way requires someone who is both an owner of the GitHub organization and an admin of your Claude organization.
- **Member connection**: when a member connects their GitHub account to Claude, for example while [setting up cloud sessions](../02-Claude-Code-CLI/web-quickstart.md#connect-github), Claude links the GitHub accounts that member owns where the Claude GitHub App is already installed. That can include their personal account and GitHub organizations they own.

A row marked **Not linked** comes from your own GitHub sign-in. It’s an account you can see on GitHub where the Claude GitHub App is installed. To unlink an account from your Claude organization, open its row’s menu and select **Unlink from this workspace**. Unlinking leaves the Claude GitHub App installed on GitHub, and the account is linked again the next time one of its owners connects GitHub to Claude. To keep it from being linked again, uninstall the Claude GitHub App from that account on GitHub. On Enterprise plans, the [Compliance API](../04-API-Reference/Admin/compliance-activities-list.md) activity types for linking and unlinking are `github_app_installation_linked` and `github_app_installation_unlinked`.


[​](#set-up-usage-visibility)

Set up usage visibility

Choose monitoring based on what you need to report on. The dashboards, APIs, and spend controls differ between Claude for Teams or Enterprise plans and Claude Console organizations, so check the Availability column before you plan your reporting around a capability.

| Capability             | What you get                                                                                                            | Availability                                                                                                                                                                                                                                              | Where to start                                             |
|:-----------------------|:------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------|
| Usage monitoring       | OpenTelemetry export of sessions, tools, and tokens                                                                     | All providers                                                                                                                                                                                                                                             | [Monitoring usage](monitoring-usage.md)              |
| Analytics dashboard    | Adoption and contribution metrics with a leaderboard on Teams / Enterprise; per-user usage and spend metrics on Console | Teams / Enterprise at [claude.ai/analytics](https://claude.ai/analytics/claude-code), Console at [platform.claude.com/claude-code](../04-API-Reference/Other/playground.md)                                                                               | [Analytics](analytics.md)                            |
| Programmatic reporting | Per-user usage and cost data over an API                                                                                | [Enterprise Analytics API](../04-API-Reference/Admin/platform-claude-com-http-beta-organization-analytics.md) for Enterprise, [Claude Code Analytics API](../04-API-Reference/Guides/build-with-claude-claude-code-analytics-api.md) for Console                              | [Costs](../17-Billing-Plans/costs.md#manage-costs-for-your-organization) |
| Spend controls         | Spend limits and rate limits                                                                                            | Organization settings for Teams / Enterprise, workspace limits for Console; on third-party clouds, cloud budget controls or a [Claude apps gateway](claude-apps-gateway.md) with per-user [spend limits](claude-apps-gateway-spend-limits.md) | [Costs](../17-Billing-Plans/costs.md#manage-costs-for-your-organization) |

On Teams and Enterprise, per-user usage and spend numbers come from the [spend report](../17-Billing-Plans/view-usage-analytics-for-team-and-enterprise-plans.md) in your organization’s analytics settings, not the analytics dashboard. Cloud providers expose spend through AWS Cost Explorer, GCP Billing, or Azure Cost Management. For planning enterprise budgets across Claude chat, Claude Code, and Cowork, see the [Claude Enterprise consumption guide](claude-enterprise-consumption-guide.md).


[​](#review-data-handling)

Review data handling

On Team, Enterprise, Claude API, and cloud provider plans, Anthropic doesn’t train models on your code or prompts. Your API provider determines retention and compliance posture.

| Topic                     | What to know                                                                                                                                         | Where to start                                                                         |
|:--------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------|
| Data usage policy         | What Anthropic collects, how long it’s retained, what’s never used for training                                                                      | [Data usage](data-usage.md)                                                      |
| Zero Data Retention (ZDR) | Nothing stored after the request completes. Available to qualified accounts on Claude for Enterprise                                                 | [Zero data retention](../02-Claude-Code-CLI/zero-data-retention.md)                                    |
| HIPAA configuration       | For Claude for Enterprise organizations that have HIPAA enabled. Some Claude Code (local mode) features are turned off and others are off by default | [Set up Claude Code (local mode) for a HIPAA-ready organization](../02-Claude-Code-CLI/hipaa-setup.md) |
| Security architecture     | Network model, encryption, authentication, audit trail                                                                                               | [Security](security.md)                                                          |

If you need request-level audit logging or to route traffic by data sensitivity, we recommend you place a gateway between developers and your provider: a self-hosted [Claude apps gateway](claude-apps-gateway.md) records a per-request audit log with IdP identity, or you can use another [LLM gateway](llm-gateway.md). Sessions that go through a gateway aren’t eligible for the HIPAA configuration. [Check how developers sign in and connect](../02-Claude-Code-CLI/hipaa-setup.md#check-how-developers-sign-in-and-connect) lists which sign-in and connection methods are. For regulatory requirements and certifications, see [Legal and compliance](legal-and-compliance.md).


[​](#verify-and-onboard)

Verify and onboard

After configuring managed settings, have a developer run `/status` inside Claude Code. On the **Status** tab, the `Setting sources` line shows `Enterprise managed settings` followed by the source that won in parentheses; [Verify enforcement](managed-settings.md#verify-enforcement) lists the labels. Share these resources to help developers get started:

- [Quickstart](../01-Getting-Started/quickstart.md): first-session walkthrough from install to working with a project
- [Common workflows](../02-Claude-Code-CLI/common-workflows.md): patterns for everyday tasks like code review, refactoring, and debugging
- [Claude Code 101](https://academy.claude.com/courses/claude-code-101) and [Claude Code in Action](https://academy.claude.com/courses/claude-code-in-action): free self-paced courses on [Claude Academy](https://academy.claude.com/)

For login issues, point developers to [authentication troubleshooting](../02-Claude-Code-CLI/troubleshoot-install.md#login-and-authentication). The most common fixes are:

- Run `/logout` then `/login` to switch accounts
- Run `claude update` if the enterprise auth option is missing
- Restart the terminal after updating

If a developer sees “You haven’t been added to your organization yet,” their seat doesn’t include Claude Code access and needs to be updated in the admin console.


[​](#next-steps)

Next steps

With provider and delivery mechanism chosen, move on to detailed configuration:

- [Server-managed settings](server-managed-settings.md): deliver managed policy from the Claude admin console
- [All settings](../02-Claude-Code-CLI/settings-reference.md): every setting key, with where it goes and an example
- [Which value Claude Code uses](../02-Claude-Code-CLI/settings.md#which-value-claude-code-uses): the precedence rules across managed, project, local, and user settings
- [Monorepos and large repos](../02-Claude-Code-CLI/large-codebases.md): per-directory configuration patterns for organizations deploying into a monorepo
- [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md), [Google Cloud’s Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md), [Microsoft Foundry](../02-Claude-Code-CLI/microsoft-foundry.md): provider-specific deployment
- [Claude Enterprise Administrator Guide](https://claude.com/resources/tutorials/claude-enterprise-administrator-guide): SSO, SCIM, seat management, and rollout playbook

---

## Track team usage with analytics - Claude Code Docs

Source: https://code.claude.com/docs/en/analytics
Path: 13-Enterprise-Admin/analytics.md

## On this page

- [Access analytics for Team and Enterprise](#access-analytics-for-team-and-enterprise)
  - [Enable contribution metrics](#enable-contribution-metrics)
  - [Review summary metrics](#review-summary-metrics)
  - [Explore the charts](#explore-the-charts)
  - [Track adoption](#track-adoption)
  - [Measure PRs per user](#measure-prs-per-user)
  - [View pull requests breakdown](#view-pull-requests-breakdown)
  - [Find top contributors](#find-top-contributors)
  - [PR attribution](#pr-attribution)
  - [Attribution process](#attribution-process)
  - [Time window](#time-window)
  - [Excluded files](#excluded-files)
  - [Attribution notes](#attribution-notes)
  - [Get the most from analytics](#get-the-most-from-analytics)
  - [Monitor adoption](#monitor-adoption)
  - [Measure ROI](#measure-roi)
  - [Identify power users](#identify-power-users)
  - [Access data programmatically](#access-data-programmatically)
- [Access analytics for API customers](#access-analytics-for-api-customers)
  - [View team insights](#view-team-insights)
- [Related resources](#related-resources)

Usage and costs

# Track team usage with analytics

Copy pageCopy page

View Claude Code usage metrics, track adoption, and measure engineering velocity in the analytics dashboard.

Copy pageCopy page

Claude Code provides analytics dashboards to help organizations understand developer usage patterns, track contribution metrics, and measure how Claude Code impacts engineering velocity. Access the dashboard for your plan:

| Plan                          | Dashboard URL                                                              | Includes                                                                              | Read more                                            |
|-------------------------------|----------------------------------------------------------------------------|---------------------------------------------------------------------------------------|------------------------------------------------------|
| Claude for Teams / Enterprise | [claude.ai/analytics/claude-code](https://claude.ai/analytics/claude-code) | Usage metrics, contribution metrics with GitHub integration, leaderboard, data export | [Details](#access-analytics-for-team-and-enterprise) |
| API (Claude Console)          | [platform.claude.com/claude-code](../04-API-Reference/Other/playground.md) | Usage metrics, spend tracking, team insights                                          | [Details](#access-analytics-for-api-customers)       |

Code Review has its own dashboard. For review activity and spend, see [View usage](../02-Claude-Code-CLI/code-review-costs.md#view-usage).


[​](#access-analytics-for-team-and-enterprise)

Access analytics for Team and Enterprise

Navigate to [claude.ai/analytics/claude-code](https://claude.ai/analytics/claude-code). Admins and Owners can view the dashboard. The Team and Enterprise dashboard includes:

- **Usage metrics**: lines of code accepted, suggestion accept rate, daily active users and sessions
- **Contribution metrics**: PRs and lines of code shipped with Claude Code assistance, with [GitHub integration](#enable-contribution-metrics)
- **Leaderboard**: top contributors ranked by Claude Code usage
- **Data export**: download contribution data as CSV for custom reporting

For per-user token counts and cost estimates, configure [OpenTelemetry export](monitoring-usage.md), or export the [spend report](../17-Billing-Plans/view-usage-analytics-for-team-and-enterprise-plans.md) from your organization’s analytics settings, which lists token usage and estimated usage-credit spend per user and per model.


[​](#enable-contribution-metrics)

Enable contribution metrics

Contribution metrics are in public beta and available on Claude for Teams and Claude for Enterprise plans. These metrics only cover users within your claude.ai organization. Usage through the Claude Console API or third-party integrations is not included.

Usage and adoption data is available for all Claude for Teams and Claude for Enterprise accounts. Contribution metrics require additional setup to connect your GitHub organization. You need the Owner role to configure analytics settings. A GitHub admin must install the GitHub app.

Contribution metrics are not available for organizations with [Zero Data Retention](../02-Claude-Code-CLI/zero-data-retention.md) enabled. The analytics dashboard will show usage metrics only.

1

Install the GitHub app

A GitHub admin installs the Claude GitHub app on your organization’s GitHub account at [github.com/apps/claude](https://github.com/apps/claude).

2

Enable Claude Code analytics

A Claude Owner navigates to [claude.ai/admin-settings/claude-code](https://claude.ai/admin-settings/claude-code) and enables the Claude Code analytics feature.

3

Enable GitHub analytics

On the same page, enable the “GitHub analytics” toggle.

4

Authenticate with GitHub

Complete the GitHub authentication flow and select which GitHub organizations to include in the analysis.

Data typically appears within 24 hours after enabling, with daily updates. If no data appears, you may see one of these messages:

- **“GitHub app required”**: install the GitHub app to view contribution metrics
- **“Data processing in progress”**: check back in a few days and confirm the GitHub app is installed if data doesn’t appear

Contribution metrics cover repositories hosted on github.com. For repositories on [GitHub Enterprise Server](../02-Claude-Code-CLI/github-enterprise-server.md), the analytics dashboard shows usage metrics only.


[​](#review-summary-metrics)

Review summary metrics

These metrics are deliberately conservative and represent an underestimate of Claude Code’s actual impact. Only lines and PRs where there is high confidence in Claude Code’s involvement are counted.

The dashboard displays these summary metrics at the top:

- **PRs with CC**: total count of merged pull requests that contain at least one line of code written with Claude Code
- **Lines of code with CC**: total lines of code across all merged PRs that were written with Claude Code assistance. Only “effective lines” are counted: lines with more than 3 characters after normalization, excluding empty lines and lines with only brackets or trivial punctuation.
- **PRs with Claude Code (%)**: percentage of all merged PRs that contain Claude Code-assisted code
- **Suggestion accept rate**: percentage of times users accept Claude Code’s code editing suggestions, including Edit, Write, and NotebookEdit tool usage
- **Lines of code accepted**: total lines of code written by Claude Code that users have accepted in their sessions. This excludes rejected suggestions and does not track subsequent deletions.


[​](#explore-the-charts)

Explore the charts

The dashboard includes several charts to visualize trends over time.


[​](#track-adoption)

Track adoption

The Adoption chart shows daily usage trends:

- **users**: daily active users
- **sessions**: number of active Claude Code sessions per day


[​](#measure-prs-per-user)

Measure PRs per user

This chart displays individual developer activity over time:

- **PRs per user**: total number of PRs merged per day divided by daily active users
- **users**: daily active users

Use this to understand how individual productivity changes as Claude Code adoption increases.


[​](#view-pull-requests-breakdown)

View pull requests breakdown

The Pull requests chart shows a daily breakdown of merged PRs:

- **PRs with CC**: pull requests containing Claude Code-assisted code
- **PRs without CC**: pull requests without Claude Code-assisted code

Toggle to **Lines of code** view to see the same breakdown by lines of code rather than PR count.


[​](#find-top-contributors)

Find top contributors

The Leaderboard shows the top 10 users ranked by contribution volume. Toggle between:

- **Pull requests**: shows PRs with Claude Code vs All PRs for each user
- **Lines of code**: shows lines with Claude Code vs All lines for each user

Click **Export all users** to download complete contribution data for all users as a CSV file. The export includes all users, not just the top 10 displayed.


[​](#pr-attribution)

PR attribution

When contribution metrics are enabled, Claude Code analyzes merged pull requests to determine which code was written with Claude Code assistance. This is done by matching Claude Code session activity against the code in each PR.


[​](#attribution-process)

Attribution process

When a pull request is merged:

1.  Added lines are extracted from the PR diff
2.  Claude Code sessions that edited matching files within a time window are identified
3.  PR lines are matched against Claude Code output using multiple strategies
4.  Metrics are calculated for AI-assisted lines and total lines

Before comparison, lines are normalized: whitespace is trimmed, multiple spaces are collapsed, quotes are standardized, and text is converted to lowercase. Merged pull requests containing Claude Code-assisted lines are labeled as `claude-code-assisted` in GitHub.


[​](#time-window)

Time window

Sessions from 21 days before to 2 days after the PR merge date are considered for attribution matching.


[​](#excluded-files)

Excluded files

Certain files are automatically excluded from analysis because they are auto-generated:

- Lock files: package-lock.json, yarn.lock, Cargo.lock, and similar
- Generated code: Protobuf outputs, build artifacts, minified files
- Build directories: dist/, build/, node_modules/, target/
- Test fixtures: snapshots, cassettes, mock data
- Lines over 1,000 characters, which are likely minified or generated


[​](#attribution-notes)

Attribution notes

Keep these additional details in mind when interpreting attribution data:

- Code substantially rewritten by developers, with more than 20% difference, is not attributed to Claude Code
- Sessions outside the 21-day window are not considered
- The algorithm does not consider the PR source or destination branch when performing attribution


[​](#get-the-most-from-analytics)

Get the most from analytics

Use contribution metrics to demonstrate ROI, identify adoption patterns, and find team members who can help others get started.


[​](#monitor-adoption)

Monitor adoption

Track the Adoption chart and user counts to identify:

- Active users who can share best practices
- Overall adoption trends across your organization
- Dips in usage that may indicate friction or issues


[​](#measure-roi)

Measure ROI

Contribution metrics help answer “Is this tool worth the investment?” with data from your own codebase:

- Track changes in PRs per user over time as adoption increases
- Compare PRs and lines of code shipped with vs. without Claude Code
- Use alongside [DORA metrics](https://dora.dev/), sprint velocity, or other engineering KPIs to understand changes from adopting Claude Code


[​](#identify-power-users)

Identify power users

The Leaderboard helps you find team members with high Claude Code adoption who can:

- Share prompting techniques and workflows with the team
- Provide feedback on what’s working well
- Help onboard new users


[​](#access-data-programmatically)

Access data programmatically

On the Enterprise plan, the [Claude Enterprise Analytics API](../04-API-Reference/Admin/platform-claude-com-http-beta-organization-analytics.md) returns per-user engagement, usage, and cost reports for your organization across Claude surfaces, including Claude Code. A Primary Owner creates a key with the `read:analytics` scope at [claude.ai/analytics/api-keys](https://claude.ai/analytics/api-keys). The API is not available on the Teams plan. To query contribution data through GitHub instead, search for PRs labeled with `claude-code-assisted`.


[​](#access-analytics-for-api-customers)

Access analytics for API customers

API customers using the Claude Console can access analytics at [platform.claude.com/claude-code](../04-API-Reference/Other/playground.md). You need the UsageView permission to access the dashboard, which is granted to Developer, Billing, Admin, Owner, and Primary Owner roles. To pull the same daily per-user metrics programmatically, use the [Claude Code Analytics API](../04-API-Reference/Guides/build-with-claude-claude-code-analytics-api.md) with an Admin API key.

Contribution metrics with GitHub integration are not currently available for API customers. The Console dashboard shows usage and spend metrics only.

The Console dashboard displays:

- **Lines of code accepted**: total lines of code written by Claude Code that users have accepted in their sessions. This excludes rejected suggestions and does not track subsequent deletions.
- **Suggestion accept rate**: percentage of times users accept code editing tool usage, including Edit, Write, and NotebookEdit tools.
- **Activity**: daily active users and sessions shown on a chart.
- **Spend**: daily API costs in dollars alongside user count.


[​](#view-team-insights)

View team insights

The team insights table shows per-user metrics:

- **Members**: all users who have authenticated to Claude Code. API key users display by key identifier, OAuth users display by email address.
- **Spend this month**: per-user total API costs for the current month.
- **Lines this month**: per-user total of accepted code lines for the current month.

Spend figures in the Console dashboard are estimates for analytics purposes. For actual costs, refer to your billing page.


[​](#related-resources)

Related resources

- [Monitoring with OpenTelemetry](monitoring-usage.md): export real-time metrics and events to your observability stack
- [Manage costs effectively](../17-Billing-Plans/costs.md): set spend limits and optimize token usage
- [Permissions](../02-Claude-Code-CLI/permissions.md): configure roles and permissions

---

## Business Associate Agreements (BAA) for Commercial Customers | Claude Help Center

Source: https://support.claude.com/en/articles/8114513-business-associate-agreements-baa-for-commercial-customers
Path: 13-Enterprise-Admin/business-associate-agreements-baa-for-commercial-customers.md

# Business Associate Agreements (BAA) for Commercial Customers


*This article is about our commercial products such as Claude for Work and the Anthropic API. For our consumer products such as Claude Free, Pro, Max and when accounts from those plans use Claude Code, see **[here](https://privacy.claude.com/en/collections/10663362-consumers)**.*

*For Claude Enterprise features to be covered under a Business Associate Agreement (BAA), the Primary Owner of the organization must activate HIPAA compliance in the HIPAA-ready Claude Enterprise organization settings under “Data and privacy” and accept Anthropic's BAA. Standard Claude Enterprise plans do not include BAA coverage without action from a Primary Owner. For Claude Code (local mode) and Claude Cowork (local mode) to be covered, the Primary Owner must also apply the HIPAA configuration to both products. See **[Use Claude Code (local mode) and Claude Cowork (local mode) on a HIPAA-ready Enterprise plan](https://support.claude.com/en/articles/17318731)**.*

Anthropic provides a BAA covering our HIPAA-ready services, such as use of our first-party API, Claude in Microsoft Foundry, or Enterprise plans. Claude Enterprise Primary Owners can accept the BAA directly when activating HIPAA compliance in the organization settings under “Data and privacy.”

**Important:** To use the 1P API with PHI, your organization’s Primary Owner will need to sign a BAA and then reach out to your Anthropic contact or our **[Sales team](https://claude.com/contact-sales)** to get this turned on.

For clarity, the BAA only covers the single organization that accepted it, and excludes features such as Claude Console, Cowork in the cloud, or features currently in beta such as Claude in Office, Claude Design, Claude Slides, and Claude Docs. As part of the BAA, customers of Anthropic’s HIPAA-ready services are subject to certain configuration requirements and limitations on what features/integrations are available.

Not all API features are covered. See the **[Implementation Guide](https://trust.anthropic.com/resources?s=l1wrssd9hsbi4gak0tp5a6&name=%5Banthropic%5D-hipaa-ready-offering-implementation-guide)** for the full list of eligible and non-eligible features.

**Important: [Covered Models](../15-Claude-AI-Features/covered-models.md)** require 30-day data retention and aren't available with zero data retention (ZDR) enabled unless Anthropic has expressly authorized it for your organization. With the **[HIPAA configuration applied to Claude Code (local mode) and Claude Cowork (local mode)](https://support.claude.com/en/articles/17318731)** on a HIPAA-ready Enterprise plan, members can use Covered Models in both products under the BAA. Claude Code can't use Covered Models while your organization has ZDR for Claude Code, unless Anthropic has expressly authorized it for your organization.

Below is a breakdown of what’s covered under the BAA, by feature and product surface. This article covers:

- Claude Enterprise features

- Claude Code and Claude Cowork features

- API features

- Messages API features

- Product BAA coverage by surface

See **[Manage local session data](../02-Claude-Code-CLI/hipaa-setup.md#manage-local-session-data)** for what Claude Code stores on a member's computer and how to delete it. For Cowork, see **[Manage Cowork data on each computer](https://claude.com/docs/cowork/hipaa-setup#manage-cowork-data-on-each-computer)**.

## What’s covered under Anthropic’s BAA

### Claude Enterprise features

[TABLE]

\*Covered under versions of the BAA accepted after 12/2/25

### Claude Code and Claude Cowork features

Use these tables to check whether you can use a Claude Code or Cowork feature to process protected health information (PHI).

The tables apply to a Claude Enterprise organization that has the HIPAA configuration applied. The HIPAA configuration is an organization setting that brings Claude Code (local mode) and Cowork (local mode) under your Business Associate Agreement (BAA) with Anthropic. "Local mode" means a session that runs on a member's own computer, in one of these places:

- Claude Code in the terminal

- Claude Code in the Code tab of Claude Desktop

- Cowork in Claude Desktop

**Note:**

- If your organization hasn't applied the configuration, see **Coverage without the HIPAA configuration**.

- If you want to apply the configuration, see **[Use Claude Code (local mode) and Cowork (local mode) on a HIPAA-ready Enterprise plan](https://support.claude.com/en/articles/17318731)**.

- If you need the contractual list of Eligible Services, see the **[Implementation Guide for HIPAA Entities](https://trust.anthropic.com/resources?s=l1wrssd9hsbi4gak0tp5a6&name=%5Banthropic%5D-hipaa-ready-offering-implementation-guide)** on the Anthropic Trust Center.

Each table has two columns for every feature. "Availability" says whether members can use the feature. "Coverage under your BAA" says whether your BAA covers it. Check both, because a feature can be available and still not be covered.

**Important:** Process PHI only with a feature marked ✅ **Covered**.

**Availability and coverage values**

Some cells in the "Availability" column add a condition, or name the settings page where an Owner of your organization turns the feature on. Every cell starts with one of these values:

- **Available:** Members can use the feature without any action from an Owner. A feature inside Cowork or the Code tab becomes available after an Owner turns on Cowork or the Code tab.

- **Off until an Owner turns it on:** The feature is off by default. An Owner can turn it on in **Organization settings**.

- **Not available:** The feature is off, and no setting turns it on.

Every cell in the "Coverage under your BAA" column starts with one of these values:

- ✅ **Covered:** Your BAA covers the feature, so members can use it to process PHI. Covered means only that your BAA applies. Whether to allow a feature, such as bypass permissions mode, is your organization's decision.

- ⚠️ **Not covered:** Members can use the feature, right away or after an Owner turns it on, but your BAA doesn't cover it. Keep PHI out of it.

- ❌ **Not available:** The feature isn't available while the HIPAA configuration is applied, so the question of coverage doesn't come up.

Some features that aren't covered are available without any action from an Owner, such as the VS Code and JetBrains extensions and local MCP servers. If you're an Owner, tell members to keep PHI out of every feature marked both **Available** and **Not covered** in the tables below.

**Where members run Claude Code and Cowork**

The table lists the three places that count as local mode, and the other ways members can run Claude Code.

[TABLE]

**Cloud and remote features**

The table lists the features that run a session in the cloud or control a session remotely. None of them are available while the HIPAA configuration is applied.

[TABLE]

**Tools, models, and memory in a session**

The table lists what Claude and members can use inside a Claude Code (local mode) or Cowork (local mode) session.

[TABLE]

**Permission modes**

The table lists the modes that let Claude act with fewer approvals from the member.

[TABLE]

**Connectors, MCP servers, and desktop extensions**

The table lists the features that connect Claude to other tools. Your BAA doesn't cover any of them, even after an Owner turns them on.

[TABLE]

**Skills and plugins**

The table lists each kind of skill and plugin. Several rows depend on one setting named **Skills**, which an Owner turns on or off in **Organization settings \> Plugins & skills**, in the "Policy" tab.

Skill and plugin security scanning isn't available while the HIPAA configuration is applied. Review each skill and plugin before you provide it to members.

[TABLE]

**Other Claude products**

The table lists Claude products other than Claude Code and Cowork. Your BAA doesn't cover any of them.

[TABLE]

**Export, sharing, and feedback**

The table lists features that send session content out of Claude Code or Cowork. None of them are available while the HIPAA configuration is applied. OpenTelemetry export stays available (see **Monitoring and audit**).

[TABLE]

**Monitoring and audit**

The table lists the ways your organization can review sessions and usage.

[TABLE]

**Coverage without the HIPAA configuration**

The table shows what your BAA covers in an organization that has enabled HIPAA but hasn't applied the HIPAA configuration. Members can still use Claude Code and Cowork in that organization where an Owner has them turned on.

[TABLE]

### API features (on a HIPAA ready API org)

[TABLE]

Data retention periods within a HIPAA-Enabled API Organization are described in Anthropic’s **[public documentation](../04-API-Reference/Other/manage-claude-api-and-data-retention.md)**.

### Messages API features

The Messages API is covered as an Eligible Service under your BAA. The following Messages API features are covered under your BAA. Messages API features not listed below are not covered under your BAA.

[TABLE]

\*Covered under versions of the BAA accepted after 4/1/26

## Product BAA coverage by surface

In this table, "the HIPAA configuration" is the configuration that the Primary Owner applies to Claude Code (local mode) and Cowork (local mode). Applying it is a separate step from enabling HIPAA. See **[Use Claude Code (local mode) and Cowork (local mode) on a HIPAA-ready Enterprise plan](https://support.claude.com/en/articles/17318731)**.

[TABLE]

Please see our **[Trust Portal](https://trust.anthropic.com/resources?s=rgirr4qe8u7ek8c2igx3&name=claude-for-enterprise-hipaa-ready-offering-implementation-guide)** for more information about our compliance commitments.

---

## Champion kit - Claude Code Docs

Source: https://code.claude.com/docs/en/champion-kit
Path: 13-Enterprise-Admin/champion-kit.md

## On this page

- [The champion role](#the-champion-role)
  - [What this should cost you](#what-this-should-cost-you)
- [Share what you discover](#share-what-you-discover)
  - [What is worth sharing](#what-is-worth-sharing)
  - [Where to share it](#where-to-share-it)
  - [The format that works](#the-format-that-works)
- [Be the person people ask](#be-the-person-people-ask)
  - [Answer with a prompt rather than an explanation](#answer-with-a-prompt-rather-than-an-explanation)
  - [Point at the feature rather than the documentation](#point-at-the-feature-rather-than-the-documentation)
  - [Questions you are likely to hear](#questions-you-are-likely-to-hear)
- [Grow the circle](#grow-the-circle)
  - [Patterns that tend to work](#patterns-that-tend-to-work)
  - [Thirty-day playbook](#thirty-day-playbook)
  - [When someone wants to go deeper](#when-someone-wants-to-go-deeper)
- [Respond to common concerns](#respond-to-common-concerns)
- [Quick-reference sheet](#quick-reference-sheet)

Adoption

# Champion kit

Copy pageCopy page

A playbook for engineers advocating Claude Code internally: what to share, how to answer questions, and how to grow adoption on your team.

Copy pageCopy page

This page is for individual engineers who are already using Claude Code and want to help their team adopt it. It covers what to share, how to answer the questions you will get, a thirty-day playbook, and responses to common concerns. Adoption of a developer tool rarely happens because of a rollout announcement. It happens because someone on the team begins using the tool well, talks about it openly, and makes it easy for others to follow. The work you do as a champion has a disproportionate effect: every example you share shortens the learning curve for the engineers who come after you, and every question you answer in public turns one person’s experience into something the whole team can build on.


[​](#the-champion-role)

The champion role

The role consists of three behaviors that reinforce one another.

| Behavior                 | What it looks like in practice                                                                                                                                                           | Why it matters                                                                                                                                                                          |
|--------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Share what you discover  | Post the prompts, screenshots, and small wins from your own work in the places your team already reads, such as an engineering channel, a standup thread, or a pull-request description. | Examples drawn from your own codebase are more persuasive than any external documentation, because colleagues can see exactly how the tool applies to the problems they share with you. |
| Be the person people ask | When a colleague asks how you accomplished something, respond with the actual prompt you used so they can apply it directly to their own task.                                           | A concrete, runnable example removes the gap between curiosity and a first successful use, which is where most adoption efforts stall.                                                  |
| Grow the circle          | Establish a small number of lightweight, recurring habits, such as a dedicated channel or a weekly thread, so that momentum continues even when your attention is elsewhere.             | Adoption that depends on a single person is fragile. Adoption that is carried by shared habits continues to compound on its own.                                                        |


[​](#what-this-should-cost-you)

What this should cost you

Set expectations with yourself and with your lead. The activities below are intended to fit inside a normal working week, and the role should remain a multiplier on your existing work rather than an additional support responsibility.

| Activity                                | Time per week    | Guidance                                                                                                                  |
|-----------------------------------------|------------------|---------------------------------------------------------------------------------------------------------------------------|
| Posting wins and prompts                | About 15 minutes | Capture these in the moment with a screenshot and one or two sentences; avoid turning them into formal write-ups.         |
| Answering questions in a shared channel | About 20 minutes | Answer publicly once, then link back to that answer when the question recurs.                                             |
| Hosting a weekly show-and-tell thread   | About 5 minutes  | You post the opening prompt; the team supplies the content.                                                               |
| Optional pairing or walkthroughs        | 0 to 30 minutes  | Reserve this for colleagues who are blocked, and offer the [Quickstart](../01-Getting-Started/quickstart.md) link before scheduling time. |


[​](#share-what-you-discover)

Share what you discover

Your own experience is the most persuasive material your colleagues will encounter, because it is specific to the codebase, workflows, and problems you all share. Documentation tells people what is possible; your posts show them what is actually working in your environment.


[​](#what-is-worth-sharing)

What is worth sharing

The most useful posts describe a technique a colleague can reuse tomorrow rather than an outcome that is already complete. Techniques compound as they spread through a team; status updates do not. Examples of reusable techniques:

- “I learned that @-mentioning a directory works. Pointing it at `@src/components/` and asking which were missing tests surfaced two I had overlooked.”
- “Plan mode (`Shift+Tab`) lays out the proposed changes first, which is why I am comfortable using it on shared code.”
- “I configured a Stop hook so I receive a desktop notification when a long task completes. Configuration is in the thread.”
- “Running `/init` generates a `CLAUDE.md` from the repository so the assistant stops re-asking about our conventions.”


[​](#where-to-share-it)

Where to share it

Post wherever your team already reads. The goal is to place examples in the path of normal work rather than to create a destination.

| Location                                        | Best suited for                                                            | Recommended format                                                                          |
|-------------------------------------------------|----------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
| A `#claude-code` or general engineering channel | Discoveries, prompts, and “today I learned” moments                        | A screenshot accompanied by one or two sentences of context                                 |
| Pull-request descriptions                       | Demonstrating the approach on real code that reviewers are already reading | A single line such as “Claude and I did this refactor; happy to walk through the approach.” |
| Standups or weekly written updates              | Normalizing usage with leads and skip-level managers                       | One sentence describing one concrete outcome                                                |
| Team wiki or internal documentation             | Durable patterns, custom skills, and `CLAUDE.md` examples                  | A short page, linked from the channel topic so it remains discoverable                      |


[​](#the-format-that-works)

The format that works

A screenshot accompanied by a single line of context, or a brief before-and-after description, is generally the right level of detail. Keep each post short enough that someone scrolling past still absorbs the point. A long write-up tends to be saved for later and forgotten, whereas a short post with a screenshot tends to be copied and tried. The example posts below illustrate tone and length; adapt them rather than copying verbatim.

```python
Learned today that @-mentioning a directory works. I pointed it at
@src/components/ and asked which components were missing tests, and it
surfaced two I had forgotten about.
```

```python
I configured a Stop hook so I receive a desktop notification when a long
task completes. I started a refactor, stepped away, and was notified when
it finished. Configuration is in the thread.
```

```python
Plan mode is the reason I am comfortable using this on code that matters.
Press Shift+Tab until you see "plan"; it lays out the changes it proposes
without editing your source.
```


[​](#be-the-person-people-ask)

Be the person people ask

Once you have shared a few examples, questions will follow. This is where a champion helps the most, because a good answer to one person frequently unblocks several others who are watching the same channel.


[​](#answer-with-a-prompt-rather-than-an-explanation)

Answer with a prompt rather than an explanation

When a colleague asks how you accomplished something, the most useful response is the prompt you actually used. They will learn more from running that prompt against their own problem than from any description you could write, and it gives them something they can act on immediately.

```python
Colleague: How did you get it to find that race condition?

Champion: I asked, "The test in @tests/scheduler.test.ts is flaky, figure
out why," and it traced two unjoined promises in the scheduler. Try the
same phrasing on your test.
```


[​](#point-at-the-feature-rather-than-the-documentation)

Point at the feature rather than the documentation

A response such as “Try plan mode, press `Shift+Tab` until you see it” is more useful in the moment than a link to the documentation. If the person needs more depth later they will find it on their own; right now they need the single thing that unblocks them.


[​](#questions-you-are-likely-to-hear)

Questions you are likely to hear

| Question                                           | Suggested response                                                                                                                                                                                                           | Follow-up resource                                                |
|----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------|
| ”What should I try it on first?”                   | Recommend a real but contained task, ideally a bug or chore the person has been postponing because it is tedious rather than difficult.                                                                                      | [Common workflows](../02-Claude-Code-CLI/common-workflows.md)                     |
| ”How do I trust it with my code?”                  | Introduce plan mode: pressing `Shift+Tab` cycles into it, and Claude researches and proposes changes without editing your source.                                                                                            | [Permissions](../02-Claude-Code-CLI/permissions.md)                               |
| ”Is the setup worth the effort?”                   | Installation takes roughly two minutes, runs in the terminal, and requires no IDE extension. Running `/init` once is sufficient to begin working.                                                                            | [Quickstart](../01-Getting-Started/quickstart.md)                                 |
| ”It produced an incorrect result.”                 | Encourage them to provide the failure back to Claude. Pasting the error message or failing test is far more effective than rephrasing the original request.                                                                  | [Common workflows](../02-Claude-Code-CLI/common-workflows.md)                     |
| ”It does not understand our codebase conventions.” | Suggest running `/init` to generate a `CLAUDE.md` file, then adding the team’s conventions, test commands, and any directories that should be avoided.                                                                       | [Memory](../02-Claude-Code-CLI/memory.md)                                         |
| ”Is this just autocomplete?”                       | Offer a brief demonstration in which Claude explains an unfamiliar file, traces a bug across services, or drafts a migration plan. These tasks require reasoning across the repository rather than completing a single line. | A two-minute live demonstration                                   |
| ”What about security and data handling?”           | Refer this question to your administrator. Your organization’s deployment and data-handling policy is already configured, and champions should not improvise this answer.                                                    | [Security](security.md) · [Data usage](data-usage.md) |


[​](#grow-the-circle)

Grow the circle

The objective is to establish a small number of lightweight habits that allow momentum to continue after you have stopped actively driving it. You don’t need to build a program or own a rollout. When questions in the channel are being answered by people other than you, the role has done its job.


[​](#patterns-that-tend-to-work)

Patterns that tend to work

| Pattern                                    | How to run it                                                                                                                                                                                                                                     | Effort required                            |
|--------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|
| A dedicated channel                        | Create a `#claude-code` channel (or a recurring thread in an existing one), pin the [Quickstart](../01-Getting-Started/quickstart.md) link and one strong example, and answer questions publicly so each answer benefits everyone watching.                       | About five minutes to set up, then ambient |
| A weekly show-and-tell thread              | Each Friday, post “What did Claude help you with this week?” No preparation, slides, or meeting are required; screenshots and short descriptions are sufficient.                                                                                  | About two minutes per week                 |
| Share a custom skill                       | Post your most useful `.claude/skills/<name>/SKILL.md` file, for example a `/ship` skill that runs tests and lint before committing, with a one-line description. Because skills are plain Markdown, colleagues can adopt them immediately.       | About five minutes per skill               |
| Generate a setup guide from your own usage | Run `/team-onboarding` in a project you have spent real time in. Claude scans your recent sessions, commands, and MCP servers, then produces a guide a new teammate can paste as their first message to replay your setup. Pin it in the channel. | About two minutes                          |
| Pair on a first task                       | Offer a single fifteen-minute pairing session to anyone getting started. One successful outcome on their own code is more persuasive than any presentation.                                                                                       | About fifteen minutes per person           |
| Identify the next champion                 | The colleague who asks you the most questions is usually ready to take on this role. Forward them this page and divide the channel responsibilities between you.                                                                                  | Negligible                                 |


[​](#thirty-day-playbook)

Thirty-day playbook

If a loose plan is helpful, the sequence below reflects what tends to work across most teams. Adjust freely to fit your context.

1

Week 1: Seed the channel

Create the channel, pin the [Quickstart](../01-Getting-Started/quickstart.md), and post two or three of your own examples with the prompts included.**Signal that it is working:** a few colleagues react or reply, and at least one question is asked in the channel.

2

Week 2: Start the rhythm

Start the weekly show-and-tell thread, answer every question publicly, and share one custom skill or `CLAUDE.md` snippet.**Signal that it is working:** someone other than you posts an example of their own.

3

Week 3: Pair and consolidate

Offer two or three short pairing sessions and consolidate the most common questions and answers into a pinned FAQ message.**Signal that it is working:** you see repeat usage, with the same colleagues returning rather than trying once and stopping.

4

Week 4: Hand off

Identify a second champion and share a brief summary of what is working and what is not with your lead or administrator.**Signal that it is working:** questions in the channel are being answered by people other than you.


[​](#when-someone-wants-to-go-deeper)

When someone wants to go deeper

You are the warm introduction rather than the onboarding program. When a colleague moves past “should I try this” into “how do I become effective with it,” point them to the [Quickstart](../01-Getting-Started/quickstart.md) and [Common workflows](../02-Claude-Code-CLI/common-workflows.md) pages. They contain short sections covering the features that are useful but difficult to discover on your own.


[​](#respond-to-common-concerns)

Respond to common concerns

Healthy skepticism is expected; engineers should be cautious about tools that touch their code. The most effective response is rarely to argue the general case. Instead, acknowledge the concern, offer a brief reframe, and propose one concrete demonstration on the person’s own code. Most concerns are resolved by a single successful experience.

| Concern                                       | Suggested response                                                                                                                                                                           | Evidence to offer                                         |
|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------|
| ”I am faster without it.”                     | That is likely true for code the person writes routinely. Suggest trying it on the work they tend to avoid: legacy files, unfamiliar services, or test scaffolding, where it helps the most. | Time one tedious task both ways and compare.              |
| ”I do not trust AI to touch production code.” | Agree that no change should land unread. Suggest plan mode to see the proposed changes first, then review the diff to the same standard as any pull request.                                 | Demonstrate plan mode on a real file.                     |
| ”It will make junior engineers weaker.”       | Used well, it is an effective explainer. Encourage junior engineers to ask Claude to explain a file and its call sites before asking it to change anything.                                  | Run “Explain @file and where it is called from” together. |
| ”I tried it once and it hallucinated.”        | This is usually a context problem rather than a model problem. @-mentioning the relevant files, running `/init`, and providing the actual error output typically resolves it.                | Re-run their original prompt with proper `@`-context.     |
| ”We do not have time to learn another tool.”  | Claude Code is a terminal command rather than a platform. If it does not return value within the first session, it is reasonable to set it aside.                                            | A two-minute install followed by one real bug.            |


[​](#quick-reference-sheet)

Quick-reference sheet

The techniques below are the ones that most reliably move someone from a first trial to daily use. Pin this table in a channel or share it on its own.

| Technique                        | How to apply it                                                                                                                                                       |
|----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Provide the right context        | Use `@file` or `@directory/` references, or paste the error or log output directly. Supplying relevant context is more effective than elaborate prompting.            |
| Review the plan before the edit  | Press `Shift+Tab` to enter plan mode. Claude researches and proposes changes without editing your source.                                                             |
| Teach it your repository         | Run `/init` to generate a `CLAUDE.md` file, then add your conventions, test commands, and any directories that should not be modified. See [Memory](../02-Claude-Code-CLI/memory.md). |
| Reuse a workflow                 | Save a `SKILL.md` file in `.claude/skills/<name>/` to create a `/name` skill that the entire team can use. See [Skills](../08-Plugins-Skills/skills.md).                             |
| Stay informed during long tasks  | Configure a Stop hook to receive a desktop notification when a long-running task completes. See [Hooks](../07-Hooks/hooks-guide.md).                                        |
| Recover from an incorrect result | Rather than rephrasing the request, paste the failing test or stack trace back to Claude and ask it to address that specific failure.                                 |
| Keep edits surgical              | Ask for a diff, or specify “only change X.” Claude respects scope when scope is stated.                                                                               |

Claude Code is updated frequently. Verify version-specific details against the [documentation home page](../02-Claude-Code-CLI/code-home.md) before distributing this material internally.

---

## Claude apps gateway configuration - Claude Code Docs

Source: https://code.claude.com/docs/en/claude-apps-gateway-config
Path: 13-Enterprise-Admin/claude-apps-gateway-config.md

## On this page

- [File structure](#file-structure)
- [Secret expansion](#secret-expansion)
- [Required sections](#required-sections)
  - [listen](#listen)
  - [oidc](#oidc)
  - [Certificate client authentication](#certificate-client-authentication)
  - [Rotate the client certificate](#rotate-the-client-certificate)
  - [IdP requests through a forward proxy](#idp-requests-through-a-forward-proxy)
  - [Proxy-only egress](#proxy-only-egress)
  - [session](#session)
  - [store](#store)
  - [upstreams](#upstreams)
  - [Upstream error messages](#upstream-error-messages)
  - [Anthropic API](#anthropic-api)
  - [Amazon Bedrock](#amazon-bedrock)
  - [Amazon Bedrock Mantle endpoint](#amazon-bedrock-mantle-endpoint)
  - [Claude Platform on AWS](#claude-platform-on-aws)
  - [Google Cloud Agent Platform](#google-cloud-agent-platform)
  - [Microsoft Foundry](#microsoft-foundry)
  - [Static headers on upstream requests](#static-headers-on-upstream-requests)
  - [Multiple upstreams](#multiple-upstreams)
- [Optional sections](#optional-sections)
  - [admin](#admin)
  - [enforcement](#enforcement)
  - [pricing](#pricing)
  - [Mark prices up](#mark-prices-up)
  - [Send the rates to signed-in clients](#send-the-rates-to-signed-in-clients)
  - [models](#models)
  - [managed](#managed)
  - [Start sessions on a model the policy allows](#start-sessions-on-a-model-the-policy-allows)
  - [Matcher values that stop the gateway at boot](#matcher-values-that-stop-the-gateway-at-boot)
  - [Choose cli or code](#choose-cli-or-code)
  - [What goes in cli](#what-goes-in-cli)
  - [Context window in terminal sessions](#context-window-in-terminal-sessions)
  - [One-hour prompt cache](#one-hour-prompt-cache)
  - [MCP servers in a policy](#mcp-servers-in-a-policy)
  - [Claude Desktop overlay](#claude-desktop-overlay)
  - [Extended context in Claude Desktop](#extended-context-in-claude-desktop)
  - [Precedence with other managed sources](#precedence-with-other-managed-sources)
  - [telemetry](#telemetry)
  - [Send cumulative metrics](#send-cumulative-metrics)
  - [Add your own labels](#add-your-own-labels)
  - [Group changes during an open session](#group-changes-during-an-open-session)
  - [Export directly to your collector](#export-directly-to-your-collector)
  - [When a destination fails](#when-a-destination-fails)
  - [HTTP tuning](#http-tuning)
  - [load_test_mode](#load_test_mode)
- [Complete example](#complete-example)
- [Client-side managed settings](#client-side-managed-settings)
- [Related](#related)

Claude apps gateway

# Claude apps gateway configuration

Copy pageCopy page

Reference for every gateway.yaml option: listener and TLS, OIDC, session, Postgres store, Amazon Bedrock, Claude Platform on AWS, Google Cloud’s Agent Platform, and Microsoft Foundry upstreams, model routing, managed policies, and telemetry.

Copy pageCopy page

A Claude apps gateway deployment is configured by one YAML file, conventionally `gateway.yaml`. The file defines everything the gateway does: where it listens, how developers sign in, where inference goes, and which policies and telemetry apply. This page is the reference for every option in that file. To write your first one, start from the [quickstart](claude-apps-gateway.md#quickstart), which builds a minimal working config and runs it. Once you have a config you’re happy with, the [deployment guide](claude-apps-gateway-deploy.md) covers containerizing and hosting it on Kubernetes, Cloud Run, or your own platform. The gateway reads the file once, at startup, with `claude gateway --config /path/to/gateway.yaml`. Every option is validated against a schema at boot, so a malformed config fails at start with a field-level error rather than at first use. The [complete example](#complete-example) at the end of this page exercises every section.


[​](#file-structure)

File structure

Five sections are [required](#required-sections). Every other section is [optional](#optional-sections), and an omitted section takes its defaults. Unknown keys fail boot, so a typo surfaces as a named error rather than a silently ignored setting. **Required sections:**

- [`listen`](#listen): bind address, public URL, TLS termination
- [`oidc`](#oidc): your identity provider (IdP), including issuer, client, claim mapping, and who may sign in
- [`session`](#session): the bearer tokens the gateway mints, with secret and lifetime
- [`store`](#store): PostgreSQL, for device grants and rate-limit counters
- [`upstreams`](#upstreams): where inference goes, whether Anthropic, Amazon Bedrock, Claude Platform on AWS, Google Cloud’s Agent Platform, or Microsoft Foundry

**Optional sections:**

- [`admin`](#admin): Admin API auth and retention for spend limits
- [`enforcement`](#enforcement): spend-limit fail-open or fail-closed behavior
- [`pricing`](#pricing): contracted rates and a multiplier for the spend meter and for the cost figures developers see
- [`models`](#models) and `auto_include_builtin_models`: admin-curated model list and per-upstream IDs
- [`managed`](#managed): managed settings policies by IdP group
- [`telemetry`](#telemetry): OTLP forwarding to your observability stack
- [`access_control`, `limits`, `timeouts`, `rate_limits`](#http-tuning): IP allow/deny, request size caps, upstream time-to-first-byte, and per-IP sign-in limits
- [`load_test_mode`](#load_test_mode): load testing the gateway without calling a model provider


[​](#secret-expansion)

Secret expansion

Don’t write secrets such as `client_secret`, `jwt_secret`, or `postgres_url` directly in `gateway.yaml`. Reference them with one of the forms below, and the gateway resolves the value at boot from an environment variable or a file:

| Form            | Resolves to                                                                                                                                                                                                                                                 | Use for                                                                |
|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------|
| `${VAR}`        | The environment variable `VAR`. Boot fails if undefined.                                                                                                                                                                                                    | Container environment variables, AWS Secrets Manager via env injection |
| `${file:/path}` | Contents of the file at that absolute path, trimmed. The reference must be the field’s entire value: unlike `${VAR}`, it isn’t expanded inside a longer string, so for a database password set `store.password` rather than embedding it in `postgres_url`. | Kubernetes Secret volume mounts, Vault Agent, SOPS                     |


[​](#required-sections)

Required sections


[​](#listen)

`listen`

The `listen` block controls where the gateway serves: the bind address and port, the externally visible origin, and optional TLS termination.

| Field                  | Required                  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
|------------------------|---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `host`                 | No                        | Bind address. Default `0.0.0.0`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| `port`                 | No                        | Bind port. Default `8080`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `public_url`           | Unless `host` is loopback | The externally visible `https://` origin, used to build the IdP `redirect_uri` and discovery metadata. Required whenever `host` isn’t a loopback address, whether TLS terminates at a proxy such as an ALB, Ingress, or Cloud Run or at the gateway itself through `tls`, because the gateway never derives its own origin from `X-Forwarded-*` headers; they are client-spoofable. Boot fails without it. `trusted_proxies` below governs client-IP resolution only. Also required to enable [telemetry](#telemetry), because the gateway builds the OTLP endpoint it pushes to clients from this URL. |
| `tls.cert` / `tls.key` | No                        | PEM paths if the gateway terminates TLS itself                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `trusted_proxies`      | No                        | CIDRs or IPs of load balancers in front of the gateway. When set, the gateway trusts `X-Forwarded-For` only from these peers and records the real client IP for per-IP rate limiting and audit. Equivalent to nginx `set_real_ip_from`. `X-Forwarded-For` entries written as `ipv4:port` or `[ipv6]:port`, as some load balancers do, are read with the port dropped. An IPv6 address with a port appended and no brackets may be read as a different address or not read at all, so turn off the port option on any proxy that writes that form.                                                       |


[​](#oidc)

`oidc`

The `oidc` block connects the gateway to your identity provider and decides who can sign in. It names the issuer and OAuth client, maps the claims that carry email and groups, and restricts sign-in by email domain or group. OpenID Connect (OIDC) is the SSO protocol the gateway uses with your identity provider; see [Identity provider setup](claude-apps-gateway-deploy.md#identity-provider-setup) for what to register on the IdP side.

| Field                           | Required                                                 | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
|---------------------------------|----------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `issuer`                        | Yes                                                      | OIDC discovery base. Must serve discovery at `/.well-known/openid-configuration`. Use HTTPS in production; the gateway accepts an `http://` issuer. A loopback issuer such as `http://localhost:8081` is rejected by the [SSRF guard](claude-apps-gateway-deploy.md#threat-model-summary) unless `CLAUDE_GATEWAY_ALLOW_LOOPBACK=1` is set in the gateway’s environment.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `client_id`                     | Yes                                                      | From your OAuth client registration                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `client_secret`                 | Unless `token_endpoint_auth_method` is `private_key_jwt` | From your OAuth client registration. Leave it out when you use [certificate client authentication](#certificate-client-authentication).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `allowed_email_domains`         | No                                                       | Reject id_tokens whose `email` claim isn’t in one of these domains, case-insensitive. Defense-in-depth against multi-tenant IdP misconfiguration. Independent of this setting, an id_token whose `email_verified` claim is explicitly `false` is always rejected.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `allowed_groups`                | No                                                       | Restrict sign-in to members of these IdP groups, matched against `groups_claim`. A user in an allowed email domain but in none of these groups is rejected. Requires the IdP to emit the groups claim. Matching is an exact, case-sensitive string comparison against the values in that claim, and the gateway doesn’t expand nested groups: to admit members of a sub-group, list the sub-group here or configure the IdP to emit flattened membership.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `groups_claim`                  | No                                                       | Which id_token claim carries group membership. Default `groups`. Microsoft Entra emits app roles under `roles`. Accepts a flat key or an RFC 6901 JSON Pointer such as `/resource_access/gateway/roles` for nested claims.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `google_groups`                 | No                                                       | Look up the signed-in user’s groups through the Google Workspace Admin SDK Directory API, because Google’s id_token carries no groups claim. Set `service_account_json_path` to a service-account key file with domain-wide delegation on the `https://www.googleapis.com/auth/admin.directory.group.readonly` scope, and `admin_email` to a Workspace administrator the service account impersonates; the Directory API requires a real admin subject. Each user’s group email addresses become their groups claim, so `allowed_groups` and `managed.policies.match.groups` match on group emails.                                                                                                                                                                                                                                                                                                                                                                                                         |
| `email_claim`                   | No                                                       | Which id_token claim carries the user’s email. Default `email`. Some IdPs, such as ADFS and Entra B2C, emit `upn` or `preferred_username` instead. Accepts a flat key, a JSON Pointer, or a list of fallback keys where the first present key is used.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `scopes`                        | No                                                       | Full override of the OIDC scopes the gateway requests. Default `[openid, profile, email, offline_access]`. Set when your IdP rejects scopes it doesn’t recognize, or requires a custom scope to emit groups or email. Must include `openid`. Dropping `offline_access` disables refresh tokens, so developers re-run the browser login every `session.ttl_hours`. See [Identity provider setup](claude-apps-gateway-deploy.md#identity-provider-setup) for per-IdP scope recipes such as Google’s refresh-token flow.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `scope_on_refresh`              | No                                                       | Also send `scope`, with the same list as the sign-in request, when the gateway exchanges a refresh token. Default `false`: the refresh request omits `scope`. Most IdPs return an id_token on every refresh and don’t need this. Set `true` when your IdP returns an id_token on refresh only if asked for `openid` again, which Okta documents for its refresh grant. Without an id_token, every refresh depends on the IdP’s userinfo endpoint accepting the refreshed access token. If you gate sign-in or match policies on groups and your IdP’s refresh-time id_token omits them, also set `userinfo_fallback: true` so the gateway fills them from the userinfo endpoint. An IdP that granted fewer scopes than requested can reject the refresh with `invalid_scope`, including for existing sessions if you add entries to `scopes` while this is on. Unset the key if refreshes start failing at `token_endpoint` after you set it. Requires Claude Code v2.1.260 or later on the gateway server. |
| `extra_auth_params`             | No                                                       | Extra query parameters appended to the IdP authorization request, verbatim. This is the override mechanism for IdP-specific behavior, such as `access_type: offline` for Google refresh tokens, `domain_hint` for some Entra tenants, or `acr_values` for step-up flows. Cannot override the gateway-managed protocol params: `state`, `nonce`, `redirect_uri`, PKCE, `scope`, `response_type`, `response_mode`, and `client_id`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `userinfo_fallback`             | No                                                       | When the id_token omits email or groups, fetch them from `/userinfo`. Needed for Keycloak lightweight access tokens, the Okta org server, and ADFS minimal tokens. The id_token stays authoritative; userinfo only fills gaps. Default `false`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `use_pkce`                      | No                                                       | Send a PKCE (S256) challenge on the authorization request. Default `true`. Set `false` only if your IdP rejects PKCE for this confidential client.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `clock_skew_seconds`            | No                                                       | Tolerate clock drift when validating id_token time claims. Default `0`, which is strict. Raise if you see “token expired / not yet valid” errors right after sign-in due to host/IdP clock skew.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `token_endpoint_auth_method`    | No                                                       | How the gateway authenticates to the IdP’s token endpoint: `client_secret_basic`, `client_secret_post`, or `private_key_jwt` for [certificate client authentication](#certificate-client-authentication). By default the gateway picks one of the two `client_secret` methods from what the IdP advertises.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `client_assertion`              | With `private_key_jwt`                                   | A block with `private_key_pem` and `certificate_pem`: the private key and certificate for [certificate client authentication](#certificate-client-authentication). Requires v2.1.284 or later.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `id_token_signed_response_alg`  | No                                                       | Expected id_token signing algorithm. Default `RS256`. Set for IdPs that sign with ES256, PS256, or EdDSA.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `additional_authorized_parties` | No                                                       | Extra `azp` values to accept beyond `client_id`, for Keycloak broker and token-exchange flows                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `discovery_url`                 | No                                                       | Fetch the discovery document from this URL instead of deriving it from `issuer`, for IdPs behind a proxy that rewrites the issuer host. The path must contain `/.well-known/`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `use_proxy`                     | No                                                       | Send the gateway’s own IdP requests through the forward proxy in `HTTPS_PROXY` or `HTTP_PROXY`, honoring `NO_PROXY`. `false` keeps those requests direct. Requires v2.1.227 or later; see [IdP requests through a forward proxy](#idp-requests-through-a-forward-proxy) below.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `form_action_origins`           | No                                                       | Additional origins for the `/device` page’s `Content-Security-Policy: form-action` directive. The gateway already allows `'self'` and the discovered `authorization_endpoint` origin, but Chrome enforces `form-action` against the entire redirect chain. If your IdP redirects through a second host, such as Azure AD federated to ADFS, hub-spoke Okta, or a corporate SSO interceptor, list every origin the authorization request may redirect through.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `ca_cert_pem`                   | No                                                       | The PEM-encoded CA certificate itself, not a path to a file. It replaces the system trust store for IdP requests only. To load a mounted file, write `${file:/etc/gateway/idp-ca.pem}`. Use for Keycloak or Dex behind corporate PKI.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |


[​](#certificate-client-authentication)

Certificate client authentication

If your identity provider authenticates OAuth clients with a certificate instead of a client secret, as Microsoft Entra does with certificate credentials, set `token_endpoint_auth_method: private_key_jwt`. Requires Claude Code v2.1.284 or later on the gateway server. With this configuration the gateway sends no secret. It authenticates to the IdP’s token endpoint with a short-lived JWT signed with the certificate’s private key when a developer signs in and each time the gateway refreshes their session. The JWT is signed with RS256 and identifies the certificate by `x5t` and `x5t#S256` thumbprint headers rather than a `kid`. Your IdP must be able to find the registered certificate by thumbprint.

1

Create the key and certificate

Create an unencrypted RSA private key of at least 2048 bits, in PKCS#8 or PKCS#1 PEM form, and a certificate for it. The gateway refuses to start with any key that doesn’t meet these conditions. This `openssl` command creates such a key with a self-signed certificate that is valid for one year:

```python
openssl req -x509 -newkey rsa:2048 -nodes -keyout idp-client.key -out idp-client.crt -days 365 -subj "/CN=claude-gateway"
```

It writes `idp-client.key` and `idp-client.crt` to the current directory. Copy or mount both files where the gateway can read them. The example in step 3 uses `/etc/gateway/`.

2

Upload the certificate to the IdP

Upload the certificate, not the private key, to the gateway’s app registration at the IdP.

3

Add the key and certificate to gateway.yaml

Give the gateway the private key and the certificate in a `client_assertion` block. Leave `client_secret` out, because the gateway refuses to start when one is set together with `private_key_jwt`. This `oidc` block authenticates the gateway to a Microsoft Entra tenant with a certificate:

```python
oidc:
  issuer: https://login.microsoftonline.com/<tenant-id>/v2.0
  client_id: <application-id>
  token_endpoint_auth_method: private_key_jwt
  client_assertion:
    private_key_pem: ${file:/etc/gateway/idp-client.key}
    certificate_pem: ${file:/etc/gateway/idp-client.crt}
```

Both values are the PEM contents, not file paths, so load mounted files with `${file:/path}` as the example does. The gateway refuses to start unless `certificate_pem` is a single PEM certificate, without the rest of its chain, whose public key matches `private_key_pem`.

4

Restart the gateway and check the boot log

Restart the gateway and find this line in the boot log:

```python
[gateway] 2026-10-01T23:07:40.512Z info oidc: client authentication private_key_jwt; certificate CN=claude-gateway, SHA-1 thumbprint DE92821854EE8BAA1D98C758FAA04AABE80B9F57, expires Oct  1 23:07:31 2027 GMT
```

Compare the SHA-1 thumbprint with the one the IdP shows for the certificate you uploaded. If the certificate has expired or isn’t valid yet, the gateway still starts but logs a warning that sign-ins and refreshes will fail until you replace it. To confirm that the IdP accepts the certificate, have one developer sign in through the gateway.


[​](#rotate-the-client-certificate)

Rotate the client certificate

The gateway reads the key and certificate once at boot, so a changed file takes effect only after a restart. Rotate in this order so that no token request presents a certificate the IdP doesn’t have:

1.  Upload the new certificate to the IdP alongside the old one.
2.  Replace the key and certificate files that `gateway.yaml` loads, then restart the gateway. If you run several replicas, a [rolling restart](claude-apps-gateway-deploy.md#upgrades) works, because the IdP has both certificates until you remove the old one.
3.  After every replica has restarted, remove the old certificate from the IdP.


[​](#idp-requests-through-a-forward-proxy)

IdP requests through a forward proxy

The inference upstreams honor `HTTPS_PROXY` and `HTTP_PROXY` on every version. The gateway’s own requests to the IdP, discovery, JWKS, token, and userinfo, go direct unless you set `oidc.use_proxy: true`, which requires v2.1.227 or later. When a proxy variable is set, `use_proxy` is unset, and the issuer isn’t covered by `NO_PROXY`, the gateway keeps those requests direct and logs a notice at boot asking you to choose; `use_proxy: false` keeps them direct and silences the notice. With `use_proxy: true`, the pod resolves each IdP endpoint’s hostname itself and asks the proxy to `CONNECT` to the resolved IP address, so the proxy must accept `CONNECT` to the IP address of every host the discovery document names, not only the issuer. Use an `http://` proxy URL. `ca_cert_pem` and the [SSRF guard](claude-apps-gateway-deploy.md#threat-model-summary) apply on the proxied path as well. [Proxy-only egress](#proxy-only-egress) changes both of these: while it’s active, IdP requests follow the proxy unless you set `use_proxy: false`, and the gateway hands the proxy each IdP hostname without resolving it first.


[​](#proxy-only-egress)

Proxy-only egress

Set `CLAUDE_GATEWAY_PROXY_IS_EGRESS_BOUNDARY=1` in the gateway’s environment, next to `HTTPS_PROXY`, when the pod reaches other hosts only through that forward proxy and can’t resolve public DNS names itself, or when the proxy refuses `CONNECT` to an IP address. Requires v2.1.277 or later. It’s an environment variable rather than a `gateway.yaml` key so that nothing in the config file can relax the gateway’s address check.

```python
export HTTPS_PROXY=http://proxy.corp.example.com:3128
export NO_PROXY=
export no_proxy=
export CLAUDE_GATEWAY_PROXY_IS_EGRESS_BOUNDARY=1
```

The gateway logs one `network:` line at boot while proxy-only egress is active. Each row below is one class of outbound request on a gateway with `HTTPS_PROXY` set, by default and while proxy-only egress is active.

| Outbound request                                                                                                             | Default                                                                                                                                                          | Proxy-only egress active                                                                  |
|------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|
| `provider: anthropic` upstreams, Workload Identity Federation token exchange, `telemetry.forward_to` exports                 | Resolved and checked locally, then `CONNECT` to the checked IP address through the proxy. A telemetry collector listed in `NO_PROXY` is reached directly instead | Hostname handed to the proxy                                                              |
| IdP discovery, JWKS, token, and userinfo                                                                                     | Direct unless [`oidc.use_proxy: true`](#idp-requests-through-a-forward-proxy), then `CONNECT` to the checked IP address                                          | Hostname handed to the proxy, unless `oidc.use_proxy: false` keeps an internal IdP direct |
| Amazon Bedrock, Claude Platform on AWS, Google Cloud’s Agent Platform, and Microsoft Foundry upstreams; Google group lookups | Hostname handed to the proxy                                                                                                                                     | Unchanged                                                                                 |

Proxy-only egress stays off unless the gateway’s environment meets all three of these conditions:

- `HTTPS_PROXY` or `HTTP_PROXY` is set.
- `NO_PROXY` and `no_proxy` are empty. If your platform injects either into pods, set both to an empty value on the gateway container. Listing a telemetry collector in `NO_PROXY` keeps proxy-only egress off.
- `CLAUDE_GATEWAY_ALLOW_LOOPBACK` isn’t turned on. A collector or IdP on the pod’s own loopback can’t be combined with proxy-only egress, because a loopback address handed to the proxy would be the proxy host’s own, so give those services an address the proxy can reach instead. For the same reason the gateway refuses `localhost`-style names outright while proxy-only egress is active.

When one of those conditions isn’t met, the gateway logs a warning at boot naming the variable that stopped it and keeps the default behavior. Once proxy-only egress is active, allow every destination in the proxy, including an internal collector and any host configured by IP address. You can still keep an internal IdP direct with [`oidc.use_proxy: false`](#idp-requests-through-a-forward-proxy).

Turn this on only when the proxy’s allowlist is at least as strict as the gateway’s own check. The proxy must refuse cloud metadata endpoints such as `169.254.169.254` and `metadata.google.internal`, link-local addresses, and the proxy host’s own loopback, and it must refuse them by the address a name resolves to, not only by name, because the gateway no longer catches a hostname that resolves to one of them. A proxy that connects anywhere it’s asked removes the gateway’s [SSRF guard](claude-apps-gateway-deploy.md#threat-model-summary) for these requests.


[​](#session)

`session`

The `session` block shapes the bearer tokens the gateway mints after sign-in: the secret that signs them and how long they live.

| Field        | Required | Description                                                                                                                                                                                                                                                                                                                                                                                                           |
|--------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `jwt_secret` | Yes      | At least 32 bytes of entropy, for example from `openssl rand -base64 32`. Signs the gateway’s HS256 bearer tokens. Accepts a single string or an array for rotation: index 0 signs and all entries verify. To rotate, prepend a new secret, wait `ttl_hours`, then drop the old one.                                                                                                                                  |
| `ttl_hours`  | No       | Gateway bearer token lifetime. Default `1`. The CLI silently refreshes before expiry when the IdP issues refresh tokens. A shorter lifetime deprovisions faster; a longer one makes fewer IdP round-trips. If your IdP can’t issue refresh tokens because `offline_access` is unavailable, there is no silent refresh, so raise this to `8` or `12` to avoid sending developers back to the browser login every hour. |


[​](#store)

`store`

The `store` block points the gateway at its PostgreSQL database, which holds device grants and rate-limit counters.

| Field                     | Required | Description                                                                                                                                                                                                                                                                                                                                                                           |
|---------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `postgres_url`            | Yes      | `postgres://` or `postgresql://` URL with one host, not a comma-separated list. The gateway runs its own schema migrations at boot and on upgrade, so the role needs rights to create and alter tables on the target schema. See [Upgrades](claude-apps-gateway-deploy.md#upgrades) and [Postgres](claude-apps-gateway-deploy.md#postgres).                               |
| `username`                | No       | Overrides the user in `postgres_url`                                                                                                                                                                                                                                                                                                                                                  |
| `password`                | No       | Database credential. Set it here rather than in `postgres_url` so the credential stays out of the URL. Accepts any characters and takes precedence over URL credentials.                                                                                                                                                                                                              |
| `max_connections`         | No       | Postgres connection-pool size per replica. Default `5`, which is conservative and friendly to shared databases. With [spend limits](#admin) enabled, the gateway makes a few database operations per inference request, so raise it for a dedicated database under load, within what [Size the connection pool](claude-apps-gateway-deploy.md#size-the-connection-pool) allows. |
| `connect_timeout_seconds` | No       | Seconds the gateway waits when it opens a Postgres connection. A whole number from `1` to `60`, default `5`. Raise it if connection attempts time out when a new gateway instance starts. Requires Claude Code v2.1.274 or later on the gateway server. Earlier versions refuse to start when the key is set.                                                                         |
| `readiness_grace_seconds` | No       | How many seconds `/readyz` keeps reporting ready after Postgres stops answering. A whole number from `0` to `3600`, default `0`. See [Outage behavior](claude-apps-gateway-deploy.md#outage-behavior) for how to pick a value. Requires Claude Code v2.1.282 or later on the gateway server. Earlier versions refuse to start when the key is set.                              |

For local development, point `postgres_url` at a throwaway Postgres container, for example `docker run --rm -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust postgres`.


[​](#upstreams)

`upstreams`

`upstreams` is an ordered list. The gateway forwards inference to the first upstream that resolves the requested model. On `5xx`, `429`, `401`, `403`, `404`, or timeout the gateway fails over to the next upstream; other `4xx` doesn’t, because those errors are attributable to the request rather than the upstream. A `401` or `403` means the upstream rejected the credential the gateway used, or denied it access, for example to the requested model. A `404` means that upstream doesn’t serve the requested model, so a later upstream in the list still can. If you set `forward_user_identity: true` on an upstream, a `429` it returns to a request that carried the developer’s email doesn’t fail over. See [how a per-user limit denial reaches the developer](#per-user-identity-headers-for-a-proxy-you-run). Failover on `404` requires gateway v2.1.198 or later. Earlier releases returned the first `404` to the client even when a later upstream in the list served the model. Multiple upstreams of the same provider must set a distinct `name:`. Amazon Bedrock, Claude Platform on AWS, Google Cloud’s Agent Platform, and Microsoft Foundry clients are built once at startup, and their SDKs refresh credentials internally, so rotating cloud credentials doesn’t require a restart. Static Anthropic API keys and bearers are read at startup; see [Anthropic API](#anthropic-api).


[​](#upstream-error-messages)

Upstream error messages

The gateway returns one upstream’s error response, or its own `502`, depending on how the upstreams answered:

- **An upstream returned a status the gateway doesn’t [fail over](#multiple-upstreams) on**: that upstream’s response. The gateway tries no further upstreams.
- **Every upstream the gateway tried failed in a way it [fails over on](#multiple-upstreams)**: the last `429`. When none returned a `429`, the gateway prefers, in order, the last `401` or `403`, the last `404`, and the last `501`. When none returned any of those, the gateway’s own `502`, `all upstreams failed (N attempted)`, where N counts every entry in [`upstreams`](#upstreams), including entries the gateway skipped because they don’t serve the requested model.

When the gateway returns an upstream’s response, it keeps the upstream’s status code. Whether it keeps the upstream’s message depends on the provider. An Anthropic API upstream’s error body reaches the developer unchanged. The Amazon Bedrock, Claude Platform on AWS, Google Cloud’s Agent Platform, and Microsoft Foundry upstreams can name your account IDs, role ARNs, and project IDs in their error text. The gateway records that full text in the [operational log](claude-apps-gateway-deploy.md#logs). What the developer sees from those upstreams depends on the rejection:

- `400` or `413` in Anthropic’s standard error envelope: the upstream’s own message, such as `prompt is too long`. Claude Platform on AWS, Agent Platform, and Microsoft Foundry return this envelope for model API rejections.
- `400` or `413` in the provider’s own shape: a `capability_rejected:` token. When the gateway can’t classify the rejection, `upstream rejected the request` on a `400` or `request too large for this upstream` on a `413`.
- Any other status: generic per-status copy, such as `upstream rate limit exceeded` on a `429`.

For example, the gateway replaces Amazon Bedrock’s `Input is too long for requested model.` with `capability_rejected: prompt_too_long`. Claude Code [compacts automatically](../02-Claude-Code-CLI/errors.md#prompt-is-too-long) on that token, as it does on `prompt is too long`. Keeping a cloud upstream’s `400` or `413` message, or replacing it with a `capability_rejected:` token, requires gateway v2.1.233 or later.


[​](#anthropic-api)

Anthropic API

The minimal Anthropic upstream is an API key from the [Claude Console](../04-API-Reference/Other/playground.md):

```python
upstreams:
  - provider: anthropic
    auth:
      api_key: ${ANTHROPIC_API_KEY}
    # OR an OAuth bearer (e.g. a Workload-Identity-Federation-exchanged token):
    #   oauth_token: ${file:/var/run/secrets/anthropic-oauth-token}
    # base_url: https://api.anthropic.com   # default; override for a forward proxy
```

The two credential forms differ in the header they send:

- **`api_key`**: sends `x-api-key`. Rotate it in the Claude Console and update the env var.
- **`oauth_token`**: sends `Authorization: Bearer`. Use the bearer form when your org issues short-lived tokens instead of long-lived API keys. The bearer is read once at startup, so refresh by remounting the secret and restarting.

Instead of a static key or bearer, you can use Workload Identity Federation. Create a federation rule by following the [Workload Identity Federation guide](../04-API-Reference/Other/manage-claude-workload-identity-federation.md), then mount your workload’s OIDC JWT as a file, such as a Kubernetes projected service-account token or a CI platform’s id-token. The gateway exchanges the JWT for a short-lived bearer and refreshes it automatically. The token file is re-read on every exchange, so rotated projected tokens are picked up without a restart.

```python
upstreams:
  - provider: anthropic
    auth:
      federation_rule_id: ${ANTHROPIC_FEDERATION_RULE_ID}
      organization_id: ${ANTHROPIC_ORGANIZATION_ID}
      identity_token_file: /var/run/secrets/anthropic/id-token
      # workspace_id: wrkspc_...       # required if the rule covers >1 workspace
      # service_account_id: svac_...   # optional expected-target check
```

##### Per-user identity headers for a proxy you run

You can point a `provider: anthropic` upstream’s `base_url` at a proxy you run instead of at the Anthropic API. To tell that proxy which developer sent each request, set `forward_user_identity: true` on that upstream. The proxy can then attribute spend per developer. Requires a gateway running Claude Code v2.1.233 or later. For example, for a proxy at `upstream-gateway.internal.example.com`:

```python
upstreams:
  - provider: anthropic
    base_url: https://upstream-gateway.internal.example.com
    auth:
      api_key: ${PROXY_KEY}
    forward_user_identity: true        # default false
```

The gateway adds these headers to every request it forwards to that upstream.

| Header                        | Value                                                      |
|-------------------------------|------------------------------------------------------------|
| `x-litellm-end-user-id`       | The developer’s email, when the IdP supplied one.          |
| `x-claude-gateway-user-id`    | The developer’s IdP subject, from the token’s `sub` claim. |
| `x-claude-gateway-user-email` | The developer’s email, when the IdP supplied one.          |

When the IdP token carries no email, the gateway sends only `x-claude-gateway-user-id` and omits the two email headers. If your IdP puts the email in a different claim, set [`oidc.email_claim`](#oidc) to that claim. Your proxy doesn’t receive the [`x-claude-code-*` headers](llm-gateway-protocol.md#request-headers) that Claude Code sends, such as `x-claude-code-session-id`. The gateway doesn’t pass them to any upstream, so group requests by developer with the headers in the table above. When your proxy answers `429` to a request that carried the developer’s email, the gateway returns that response to the developer as-is instead of failing over to the next upstream, so your proxy’s per-user budget or rate limit holds. The proxy’s other responses follow the ordinary [failover rules](#upstreams). If a developer’s IdP token carries no email, the gateway forwards their requests without the email headers, so a `429` to one of those requests counts as upstream capacity and fails over. Before v2.1.267 on the gateway server, every `429` failed over. Set `forward_user_identity` only on an upstream whose `base_url` is a proxy you operate. The gateway sends developer emails to whatever server that `base_url` names. If the `base_url` is the Anthropic API, which is the default, the gateway refuses to start.

If you set `forward_user_identity` on an Amazon Bedrock upstream, the gateway exits at startup with `Unrecognized key(s) in object: 'forward_user_identity'`. If your proxy sits in front of Bedrock, list it as a `provider: anthropic` upstream instead. The gateway sends that upstream the Anthropic Messages format, so your proxy has to [translate requests for Bedrock](llm-gateway-protocol.md#format-mismatch-with-the-upstream). To name each developer to AWS instead, see [Per-developer AWS cost attribution](#per-developer-aws-cost-attribution).


[​](#amazon-bedrock)

Amazon Bedrock

For the client-side Amazon Bedrock deployment that the gateway replaces or fronts, see [Claude Code on Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md). The gateway-side upstream:

```python
upstreams:
  - provider: bedrock
    region: us-east-1
    auth: {}                           # preferred: AWS default credential chain
    # OR explicit credentials:
    # auth:
    #   aws_access_key_id: ${AWS_AKID}
    #   aws_secret_access_key: ${AWS_SK}
    #   aws_session_token: ${AWS_ST}
    # OR a Bedrock API bearer token:
    # auth:
    #   aws_bearer_token: ${AWS_BEARER_TOKEN}
    # Override the bedrock-runtime endpoint for FIPS or VPC-endpoint deployments:
    # base_url: https://bedrock-runtime-fips.us-east-1.amazonaws.com
```

An empty `auth` block uses the AWS SDK’s default credential chain: env vars, `~/.aws/credentials`, ECS task role, EC2 instance metadata, or IRSA on EKS. In production, give the gateway pod an IAM role instead of embedding static keys in a container image. Explicit credentials must be complete: the gateway fails at boot when `aws_access_key_id` and `aws_secret_access_key` aren’t set together, or when `aws_session_token` is set without them. Before v2.1.207, a partial `auth:` block passed validation.

| Setup           | How                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| IAM permissions | Grant the gateway’s principal `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream` on both the inference-profile ARNs and the underlying foundation-model ARNs. For the built-in catalog in US regions: `arn:aws:bedrock:<region>:<account>:inference-profile/us.anthropic.*` and `arn:aws:bedrock:*::foundation-model/anthropic.*`. Also grant `bedrock:CountTokens` on the foundation-model ARNs. The gateway uses it, at no charge, to count the input tokens of a request the client abandoned, so [spend limits](#admin) stay accurate. Without it the gateway falls back to a one-token Bedrock request for that count. |
| Model access    | Amazon Bedrock enables model access by default in commercial regions. The remaining account-level gate is Anthropic’s one-time use case form: if no one in your AWS account has submitted it, open the Amazon Bedrock console, select an Anthropic model from the Model catalog, and complete the form. See [Submit use case details](../02-Claude-Code-CLI/amazon-bedrock.md#1-submit-use-case-details) for the AWS Organizations form and the permissions the submitter needs.                                                                                                                                                                           |
| EKS (IRSA)      | Create an IAM role with the policy above and a trust policy for your cluster’s OIDC provider scoped to the gateway’s service account. Annotate the service account with `eks.amazonaws.com/role-arn: arn:aws:iam::<acct>:role/claude-gateway`. `auth: {}` picks it up.                                                                                                                                                                                                                                                                                                                                                                     |
| ECS / EC2       | Attach the IAM role to the task definition or instance profile. `auth: {}` picks it up.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| Anywhere else   | Pass credentials via the `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN` env vars, or set them explicitly in `auth:` with `${VAR}` expansion                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| Region          | `region:` is the API endpoint region. Cross-region inference profiles route across the geo (US, EU, APAC) regardless of which one you pick. For non-US regions or provisioned-throughput ARNs, add a [`models:`](#models) block with the right per-upstream IDs.                                                                                                                                                                                                                                                                                                                                                                           |

##### Apply an Amazon Bedrock guardrail

To apply an Amazon Bedrock guardrail to every inference request the gateway sends through a Bedrock upstream, add a `guardrail` block to that upstream. Requires Claude Code v2.1.281 or later on the gateway server.

```python
upstreams:
  - provider: bedrock
    region: us-east-1
    auth: {}
    guardrail:
      id: gr-abc123                    # guardrail ID or full ARN
      version: "1"                     # a published version number, or DRAFT
                                       # keep the quotes: a bare 1 fails at boot
```

The gateway doesn’t support guardrail input tags. It adds no guard content tags to prompts, so a guardrail filter that Amazon Bedrock applies only to tagged input doesn’t run on traffic through the gateway. For which filters depend on input tags, see [input tags](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-tagging.html) in the Amazon Bedrock documentation.

Also grant `bedrock:ApplyGuardrail` on the guardrail to the principal that signs this upstream’s requests: the gateway’s AWS principal, or with [`assume_role`](#bedrock-in-another-aws-account) the role named in `role_arn`. Set `guardrail` on every `bedrock` upstream or on none. The gateway refuses to start on a mix, because [failover](#multiple-upstreams) could otherwise send a request to a Bedrock upstream that has no guardrail. The guardrail covers Bedrock upstreams only. If you list another provider in `upstreams`, the gateway sends requests to that provider without the guardrail, or refuses to start if that provider is [`mantle`](#amazon-bedrock-mantle-endpoint). When a `/v1/messages` request whose body carries an `amazon-bedrock-*` field, such as `amazon-bedrock-guardrailConfig`, reaches a Bedrock upstream that has `guardrail` set, the gateway answers 400 instead of forwarding it.

##### Bedrock in another AWS account

Set `assume_role` on a Bedrock upstream and the gateway uses its own AWS identity only to call `sts:AssumeRole` on a role you name, which can be in a different AWS account from the gateway. Every Bedrock request from that upstream is signed with the one-hour credentials STS returns, so no long-lived access key crosses accounts. Requires a gateway running Claude Code v2.1.281 or later. An earlier gateway refuses to start when it finds the key.

```python
upstreams:
  - name: bedrock-isolated
    provider: bedrock
    region: us-east-1
    auth: {}                           # the gateway's own role: it only calls STS
    assume_role:
      role_arn: arn:aws:iam::222222222222:role/claude-gateway-bedrock
      # external_id: ${BEDROCK_ROLE_EXTERNAL_ID}   # when the role's trust policy requires one
```

The `assume_role` block takes three keys:

| Key            | Meaning                                                                                                                                                                                                                                                          |
|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `role_arn`     | The IAM role the gateway assumes, as an `arn:aws:iam::` or `arn:aws-us-gov:iam::` ARN. Give it the [Bedrock permissions](#amazon-bedrock) this upstream needs, `bedrock:CountTokens` included, plus `bedrock:ApplyGuardrail` when the upstream sets `guardrail`. |
| `external_id`  | Optional. Sent as the external ID on every `sts:AssumeRole` call. Set it when the role’s trust policy requires one, and quote it if it’s all digits.                                                                                                             |
| `session_name` | Optional. `email` or `sub` gives each developer their own session: see [Per-developer AWS cost attribution](#per-developer-aws-cost-attribution). Unset, every request uses one session named `claude-apps-gateway`.                                             |

The role’s trust policy names the gateway’s own principal, such as its IRSA or ECS task role. That principal needs `sts:AssumeRole` on the role and no Bedrock permission of its own. Drop the `Condition` if you set no `external_id`.

```python
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "AWS": "arn:aws:iam::111111111111:role/claude-gateway" },
    "Action": "sts:AssumeRole",
    "Condition": { "StringEquals": { "sts:ExternalId": "your-external-id" } }
  }]
}
```

- If STS refuses or is unreachable, the gateway doesn’t send the request with the upstream’s own credentials. It logs the STS error with what to check, then tries the next upstream you listed. [Upstream error messages](#upstream-error-messages) covers what the client receives when no upstream succeeds. A later upstream without `assume_role` would serve the request with its own credentials, so list one only if that is what you want.
- The gateway calls the regional STS endpoint `sts.<region>.amazonaws.com`, which its network must reach. For the FIPS endpoint, set `AWS_USE_FIPS_ENDPOINT=true` in the gateway’s environment rather than `use_fips_endpoint` in an AWS config file.
- `assume_role` applies to `provider: bedrock` only and needs SigV4 source credentials: the gateway refuses to start when it’s set beside `aws_bearer_token`.
- Every developer the gateway admits can use this upstream; [`managed`](#managed) governs which developers may use which models. To keep a model served through the role from also being served from another account, give it a custom id whose `upstream_model` map has only this upstream’s name. For such an id the gateway skips every other upstream, so neither the request nor the token count for an aborted request can fail over to another account. A request for a built-in model name can still [reach this upstream](#multiple-upstreams), and the gateway signs it with the same role. List this upstream last unless its account should also serve those models.

This example gives one model a custom id that only the isolated upstream serves:

```python
models:
  - id: claude-opus-restricted          # a custom id, not a built-in model name
    upstream_model:
      bedrock-isolated: us.anthropic.claude-opus-4-8   # the only upstream that serves it
```

##### Per-developer AWS cost attribution

By default the gateway signs every Bedrock request with one credential, so AWS sees all developers’ requests under a single IAM principal. Add `session_name: email` to [`assume_role`](#bedrock-in-another-aws-account) and the gateway calls `sts:AssumeRole` once per developer per hour, with the session name set to that developer’s email, and signs their requests with the returned credentials, so each developer’s requests reach AWS under their own assumed-role session. The role can be in the gateway’s own account. Requires a gateway running Claude Code v2.1.281 or later. [Cost attribution on AWS](claude-apps-gateway-on-aws.md#cost-attribution) covers the IAM role and where AWS billing shows the sessions.

```python
upstreams:
  - provider: bedrock
    region: us-east-1
    auth: {}                           # the gateway's own role: it only calls STS
    assume_role:
      role_arn: arn:aws:iam::123456789012:role/claude-gateway-bedrock-user
      session_name: email              # or sub
```

`session_name` selects which verified claim becomes the AWS `RoleSessionName`: `email` or `sub`. The gateway writes any character other than ASCII letters, digits, and `_+,.@-` as `=XX` hex per UTF-8 byte, and shortens a result longer than 64 characters to a prefix plus a hash, so each developer’s session name stays valid and unique. A request from a developer whose token lacks the claim isn’t sent through this upstream, and the operator log says to switch to `sub` or set [`oidc.email_claim`](#oidc). An active developer costs one STS call per hour per gateway replica, and concurrent first requests share one call. The gateway also makes one call of its own on this role: the token count for a request the client abandoned, so that [spend limits](claude-apps-gateway-spend-limits.md) stay accurate. That count and its [one-token fallback request](#amazon-bedrock) are signed by the shared `claude-apps-gateway` session, so AWS attributes the fallback to `claude-apps-gateway` rather than to the developer. For strict per-developer attribution, set `assume_role` with `session_name` on every Bedrock upstream you list. An upstream without it signs the requests it serves with its own credentials.


[​](#amazon-bedrock-mantle-endpoint)

Amazon Bedrock Mantle endpoint

The `mantle` provider sends inference to Amazon Bedrock’s [Mantle endpoint](../02-Claude-Code-CLI/amazon-bedrock.md#use-the-mantle-endpoint). It requires Claude Code v2.1.283 or later on the gateway server. Earlier gateway releases reject it at boot, so upgrade every replica before adding it. The example below puts Mantle first, with an Amazon Bedrock upstream behind it to serve every model the `models` field leaves out:

```python
upstreams:
  - provider: mantle
    region: us-east-1
    models: [claude-opus-4-7, claude-haiku-4-5]   # required
    auth: {}                           # AWS default credential chain
  - provider: bedrock
    region: us-east-1
    auth: {}
```

The table below lists the fields specific to a `mantle` upstream.

| Field      | Required | Description                                                                                                                                                                                  |
|------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `region`   | Yes      | AWS region. The gateway derives the endpoint from it as `https://bedrock-mantle.<region>.api.aws/anthropic`.                                                                                 |
| `models`   | Yes      | The models your AWS account has been granted on Mantle, named as clients send them, such as `claude-haiku-4-5`. Only these go to this upstream, and every other model skips to the next one. |
| `auth`     | No       | Takes the same keys as the [Amazon Bedrock](#amazon-bedrock) upstream’s `auth` block, under the same rules.                                                                                  |
| `base_url` | No       | Override the derived endpoint. Keep the `/anthropic` path at the end.                                                                                                                        |

Grant the upstream’s AWS identity Mantle’s own IAM actions for inference and token counting, which [Use the Mantle endpoint](../02-Claude-Code-CLI/amazon-bedrock.md#use-the-mantle-endpoint) lists. For a Mantle model ID that the gateway doesn’t know, add an entry to the top-level [`models:`](#models) block whose `upstream_model` maps this upstream’s name to that ID. Then put that entry’s `id` in this upstream’s `models` field too. A `bedrock` upstream’s `guardrail` and `assume_role` settings don’t extend to the requests Mantle serves:

- **`guardrail`**: the gateway applies no [Bedrock guardrail](#apply-an-amazon-bedrock-guardrail) to requests it sends to Mantle, so it refuses to start when a `mantle` upstream is listed while any `bedrock` upstream sets `guardrail`.
- **`assume_role`**: a `mantle` upstream takes no [`assume_role`](#bedrock-in-another-aws-account). Requests that Mantle serves are sent with the `mantle` upstream’s own `auth` credentials, and aren’t [attributed per developer](#per-developer-aws-cost-attribution).

For what Mantle’s own error responses mean, see [Mantle endpoint errors](../02-Claude-Code-CLI/amazon-bedrock.md#mantle-endpoint-errors).


[​](#claude-platform-on-aws)

Claude Platform on AWS

Claude Platform on AWS serves the first-party Anthropic API on AWS infrastructure at `aws-external-anthropic.<region>.api.aws`. It uses first-party model IDs, honors `anthropic-beta` headers as sent, and serves `count_tokens`, so none of the Bedrock-specific translation applies. The `anthropicAws` provider requires Claude Code v2.1.198 or later; earlier gateway releases reject it at boot. For the client-side deployment of the same platform, see [Claude Code on Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md). The gateway-side upstream:

```python
upstreams:
  - provider: anthropicAws
    region: us-east-1
    workspace_id: wrkspc_...
    auth:
      api_key: ${ANTHROPIC_AWS_API_KEY}   # sent as x-api-key
    # OR SigV4 via the AWS default credential chain:
    # auth: {}
    # OR explicit SigV4 credentials:
    # auth:
    #   aws_access_key_id: ${AWS_ACCESS_KEY_ID}
    #   aws_secret_access_key: ${AWS_SECRET_ACCESS_KEY}
    # Override the derived endpoint:
    # base_url: https://aws-external-anthropic.us-east-1.api.aws
```

The platform runs in a separate AWS account from Amazon Bedrock and signs SigV4 requests for its own service name, `aws-external-anthropic`, so a Bedrock-scoped IAM role doesn’t authorize it. An API key in `auth.api_key` takes precedence when SigV4 credentials are also set. An empty `auth` block uses the AWS SDK’s default credential chain, the same chain the [Amazon Bedrock](#amazon-bedrock) upstream uses.

| Field                                                   | Required | Description                                                                                                                                        |
|---------------------------------------------------------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------|
| `region`                                                | Yes      | AWS region, lowercase letters, digits, and hyphens. The gateway derives the endpoint from it as `https://aws-external-anthropic.<region>.api.aws`. |
| `workspace_id`                                          | Yes      | Sent as a header on every request; the platform requires it                                                                                        |
| `auth.api_key`                                          | No       | API key for the platform, sent as `x-api-key`. Not a bearer token: the two auth modes are an API key or SigV4.                                     |
| `auth.aws_access_key_id` / `auth.aws_secret_access_key` | No       | Explicit SigV4 credentials. Setting one without the other fails at boot. `auth.aws_session_token` is accepted alongside them.                      |
| `base_url`                                              | No       | Override the derived endpoint                                                                                                                      |

Because the platform resolves first-party model IDs, the built-in catalog routes to it with no [`models:`](#models) block. When you curate a `models:` list, key the entry `anthropicAws:` with the first-party ID.


[​](#google-cloud-agent-platform)

Google Cloud Agent Platform

For the equivalent client-side setup, see [Claude Code on Google Cloud](../02-Claude-Code-CLI/google-vertex-ai.md). The gateway-side upstream:

```python
upstreams:
  - provider: vertex
    region: us-east5
    project_id: example-prod
    auth: {}                           # preferred: Application Default Credentials
    # OR a service account key file:
    # auth: { service_account_json: /secrets/sa.json }
    # Override the aiplatform endpoint for Private Service Connect:
    # base_url: https://us-east5-aiplatform.p.googleapis.com
```

An empty `auth` block uses Application Default Credentials: `GOOGLE_APPLICATION_CREDENTIALS`, GCE metadata, or GKE Workload Identity. Service-account JSON key files are supported but discouraged; use Workload Identity or attach a service account to the GCE or Cloud Run instance. Set `region: global` to use the [global endpoint for Google Cloud’s Agent Platform](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations) instead of a regional one. Google then routes each request to an available region, so you don’t track per-region model availability. Setting a specific region pins every request to it.

| Setup                   | How                                                                                                                                                                                                       |
|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| IAM permissions         | Grant the gateway’s service account `roles/aiplatform.user` on the project, or a custom role with `aiplatform.endpoints.predict`. Enable Google Cloud’s Agent Platform API (`aiplatform.googleapis.com`). |
| Model access            | In Model Garden, enable the Claude models for your project. They publish to specific regions; check the model card for supported regions.                                                                 |
| GKE (Workload Identity) | Bind a GCP service account to the gateway’s Kubernetes service account and annotate the KSA with `iam.gke.io/gcp-service-account: claude-gateway@<proj>.iam.gserviceaccount.com`. `auth: {}` picks it up. |
| Cloud Run / GCE         | Set the service’s service account to one with `roles/aiplatform.user`. `auth: {}` picks it up.                                                                                                            |
| Anywhere else           | `auth: { service_account_json: /secrets/sa.json }`, the path to a JSON key file mounted as a secret. The field takes a file path, not the key contents, so no `${file:…}` expansion is involved.          |


[​](#microsoft-foundry)

Microsoft Foundry

For the client-side Microsoft Foundry deployment, see [Claude Code on Microsoft Foundry](../02-Claude-Code-CLI/microsoft-foundry.md). The gateway-side upstream:

```python
upstreams:
  - provider: foundry
    resource: example-foundry              # https://example-foundry.services.ai.azure.com
    auth: { use_azure_ad: true }        # preferred: DefaultAzureCredential / Managed Identity
    # OR an API key:
    # auth:
    #   api_key: ${FOUNDRY_API_KEY}
```

`use_azure_ad: true` resolves through `DefaultAzureCredential`: Managed Identity on AKS, ACI, or App Service; the Azure CLI; or environment credentials. API keys work but are project-wide and don’t rotate automatically. Microsoft Foundry’s endpoint is derived from `resource:`; set the optional `base_url` to override it for sovereign clouds such as Azure Government.

| Setup                   | How                                                                                                                                                                                       |
|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| RBAC                    | Grant the gateway’s identity `Azure AI User` or `Cognitive Services User` on the Microsoft Foundry resource                                                                               |
| Deployments             | Microsoft Foundry uses admin-chosen deployment names, not canonical model IDs. Add a [`models:`](#models) block mapping each canonical ID to your deployment name.                        |
| AKS (workload identity) | Federate a User-Assigned Managed Identity with the cluster’s OIDC issuer and bind it to the gateway’s service account. `use_azure_ad: true` picks it up via `WorkloadIdentityCredential`. |
| ACI / App Service       | Enable system-assigned or user-assigned managed identity on the resource. `use_azure_ad: true` picks it up.                                                                               |
| Anywhere else           | `auth: { api_key: "${FOUNDRY_API_KEY}" }`. Quote `${…}` inside `{ }`.                                                                                                                     |


[​](#static-headers-on-upstream-requests)

Static headers on upstream requests

To add fixed headers to the requests the gateway sends to one upstream, set `headers:` on that upstream. Use it when a proxy you run in front of the provider routes or attributes traffic by a header. `headers:` requires Claude Code v2.1.277 or later on the gateway server. An earlier gateway refuses to start when it finds the key. Upgrade every replica before you add the key, and remove the key before you roll back to an earlier version. The headers go to the server that `base_url` names, or to the provider’s own endpoint when `base_url` is unset. The provider receives them too unless your proxy removes them. This example reaches a `provider: vertex` upstream through a proxy at `upstream-proxy.internal.example.com`. It sets the `x-source` header the proxy reads, and sends a token from the `PROXY_TOKEN` environment variable as `x-proxy-token`:

```python
upstreams:
  - provider: vertex
    region: us-east5
    project_id: example-prod
    base_url: https://upstream-proxy.internal.example.com
    auth: {}
    headers:
      x-source: claude-apps-gateway
      x-proxy-token: ${PROXY_TOKEN}
```

Values are printable ASCII text with no space at either end. Quote a number, `true`, or `false` so YAML reads it as text. To keep a secret out of the config file, use [secret expansion](#secret-expansion) to load the value from an environment variable with `${VAR}` or from a file with `${file:/path}`. A `${VAR}` that resolves to an empty value stops the gateway from starting. `headers:` works on every provider, and each upstream sends only its own. Not every request that the gateway sends to an upstream carries them:

| Request the gateway sends to this upstream                             | Carries `headers:`                   |
|------------------------------------------------------------------------|--------------------------------------|
| `/v1/messages`, streaming or not, and `/v1/messages/count_tokens`      | Yes                                  |
| A request that failed over from another upstream                       | Yes, this upstream’s `headers:` only |
| Amazon Bedrock’s `CountTokens` call for a request the client abandoned | No                                   |
| The Workload Identity Federation token exchange                        | No                                   |

On an Amazon Bedrock or Claude Platform on AWS upstream that signs requests with AWS SigV4, these headers are part of the signature, so your proxy must pass them through unchanged. If you use a name the gateway reserves, it refuses to start, and the startup error names the header. Reserved names include:

- `authorization` and `x-api-key`
- `host`, `content-type`, and `user-agent`
- Any name starting with `anthropic-`, `x-goog-`, `x-amz-`, or `x-amzn-`


[​](#multiple-upstreams)

Multiple upstreams

The same provider can appear more than once with a distinct `name:`. This covers different regions, different accounts via different credential chains, provisioned throughput versus on-demand, and cross-provider fallback. The gateway tries upstreams in order. `5xx`, `429`, `401`, `403`, `404`, timeouts, and missing-endpoint (`501`) fail over; other `4xx` doesn’t. `429` is per-upstream capacity, so provisioned-throughput (PT) exhaustion fails over to on-demand. If you set [`forward_user_identity: true`](#per-user-identity-headers-for-a-proxy-you-run) on an upstream, a `429` to a request that carried the developer’s email is a per-user denial instead and doesn’t fail over. Every request starts at the first upstream. A request reaches a later upstream only when every upstream ahead of it has failed or doesn’t serve the requested model. The gateway keeps no record of failed upstreams, so while an upstream is down, every request that reaches it still tries it and waits for it to fail before moving on. For an Anthropic API upstream, [`timeouts.upstream_ttfb_ms`](#http-tuning) bounds the wait on a down upstream. That setting doesn’t apply to the other providers, where the gateway waits up to one hour for an upstream to start responding. `404` is per-upstream model availability, so an upstream that hasn’t enabled a model doesn’t block a later upstream that serves it. An upstream that can’t resolve the requested model is skipped without a network round-trip. This example routes a provisioned-throughput Amazon Bedrock allotment first, overflows to on-demand and a second account, and falls back to the Anthropic API last:

```python
upstreams:
  # Primary: provisioned throughput in your home region.
  - name: bedrock-pt
    provider: bedrock
    region: us-east-1
    auth: {}
  # Overflow: on-demand cross-region.
  - name: bedrock-od
    provider: bedrock
    region: us-west-2
    auth: {}
  # Different account: a separate Bedrock allotment via static keys.
  - name: bedrock-acct2
    provider: bedrock
    region: us-east-1
    auth:
      aws_access_key_id: ${ACCT2_AKID}
      aws_secret_access_key: ${ACCT2_SK}
  # Last resort: direct Anthropic API.
  - name: anthropic-fallback
    provider: anthropic
    auth:
      api_key: ${ANTHROPIC_API_KEY}

# Per-upstream model IDs are keyed on the upstream's `name:`.
models:
  - id: claude-opus-4-8
    label: Claude Opus 4.8
    upstream_model:
      bedrock-pt: arn:aws:bedrock:us-east-1:111111111111:provisioned-model/abcdef
      bedrock-od: us.anthropic.claude-opus-4-8
      bedrock-acct2: us.anthropic.claude-opus-4-8
      anthropic-fallback: claude-opus-4-8
```

| Lever                  | How                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
|------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Different regions      | One Amazon Bedrock upstream per region, each with its own `region:`. With [`auto_include_builtin_models: true`](#models) the cross-region inference profiles route automatically; for region-pinned deployments use a `models:` block.                                                                                                                                                                                                                                                                                                                                                            |
| Different accounts     | One Amazon Bedrock upstream per account. The default chain (`auth: {}`) uses the pod’s identity; for a second account, add [`assume_role`](#bedrock-in-another-aws-account) to reach it with short-lived credentials, or set explicit credentials or a bearer token in `auth:`.                                                                                                                                                                                                                                                                                                                   |
| Provisioned throughput | Map the model to the provisioned-throughput ARN in `models:` for that upstream’s name. Other upstreams keep the on-demand ID, so PT capacity is exhausted before failing over.                                                                                                                                                                                                                                                                                                                                                                                                                    |
| VPC / FIPS endpoints   | Set `base_url:` on the upstream to your VPC endpoint or FIPS endpoint URL                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| Model-scoped routing   | Only a custom model `id`, one that isn’t a built-in Claude model, skips the upstreams absent from its `upstream_model:` map. A `mantle` upstream is tried only for the models listed in its [`models` field](#amazon-bedrock-mantle-endpoint). On every other upstream the gateway tries built-in models in order and uses the provider’s default ID where the map has no entry, so for built-in models the map changes which ID an upstream receives rather than whether it is tried; an upstream that rejects the ID follows the same [failover rules](#upstreams) as any other upstream error. |

Failing over between cloud providers, or to the direct Anthropic API, changes which agreement, geography, and other terms govern the request. The CLI applies the same feature gating to gateways regardless of which upstream serves a given request, so by default failover doesn’t send a body field an upstream would reject. If you set the [one-hour cache TTL](#one-hour-prompt-cache) in a policy, it goes to every upstream in the list, so set it only if all of them accept it.


[​](#optional-sections)

Optional sections


[​](#admin)

`admin`

Optional. Enables `/v1/organizations/spend_limits`, which mirrors Anthropic’s public Admin API, and per-developer spend enforcement on `/v1/messages`. See [Spend limits](claude-apps-gateway-spend-limits.md) for how caps are set and enforced; this section covers the `gateway.yaml` keys that turn the feature on and tune it.

```python
admin:
  # Named static API keys for the admin endpoints, sent as x-api-key.
  # The id appears in the audit log as admin-key:<id> so each key is
  # attributable. Array for rotation: add the new key, roll clients,
  # remove the old.
  write_keys:
    - { id: terraform, key: "${GATEWAY_ADMIN_WRITE_KEY_TF}" }
    - { id: ci,        key: "${GATEWAY_ADMIN_WRITE_KEY_CI}" }
  read_keys:
    - { id: reporting, key: "${GATEWAY_ADMIN_READ_KEY}" }
  # IdP groups granted full admin via the normal gateway JWT (no API key).
  admin_groups: [platform-finops]
  blocked_message: request an increase at https://go.example.com/claude-limits
```

| Field                     | Required | Description                                                                                                                                                                                                                                                                                                                                                  |
|---------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `write_keys`              | No       | Array of `{id, key}`. An `x-api-key` matching one of these can list, set, and delete spend limits. Key values must be at least 32 characters; `id`s must be unique across `read_keys` and `write_keys`.                                                                                                                                                      |
| `read_keys`               | No       | Array of `{id, key}`. Read-only: every `GET` endpoint, including listing caps, fetching one by ID, and reading [`/effective`](claude-apps-gateway-spend-limits.md#%2Feffective) and [`/audit`](claude-apps-gateway-spend-limits.md#%2Faudit).                                                                                                    |
| `admin_groups`            | No       | IdP group names. A gateway JWT whose `groups` claim includes one of these has full admin access, read and write, and audits as `oidc:<sub>`. Use this for human admins; use API keys for machines. An empty entry in this list stops the gateway at boot. See [Matcher values that stop the gateway at boot](#matcher-values-that-stop-the-gateway-at-boot). |
| `blocked_message`         | No       | Appended verbatim to the `429 billing_error` a blocked developer sees. Write the whole instruction, such as a URL or a Slack channel. When unset, the gateway sends only the default message. See [How enforcement works](claude-apps-gateway-spend-limits.md#how-enforcement-works).                                                                  |
| `audit_retention_days`    | No       | Default `365`. Older `admin_audit` rows are swept.                                                                                                                                                                                                                                                                                                           |
| `spend_retention_months`  | No       | Default `13`. `spend` counter rows older than this are swept. The default keeps a full year plus the current partial month for year-over-year reporting.                                                                                                                                                                                                     |
| `identity_retention_days` | No       | Default `90`. Last-seen TTL for `principal_emails` rows, which hold each developer’s email, display name, and groups (PII). Deliberately shorter than spend retention so a deprovisioned identity ages out while its anonymous spend counters remain.                                                                                                        |
| `group_limit_mode`        | No       | `min` (default) or `max`. When a developer is in several groups with caps, `min` enforces the most restrictive and `max` the least. Used by both enforcement and `/effective`.                                                                                                                                                                               |


[​](#enforcement)

`enforcement`

The `enforcement` block controls how spend-limit checks behave when the store is unavailable.

| Field                  | Required | Description                                                                                                                                                                                                                                                                                                                                                                    |
|------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `fail_closed_on_error` | No       | Default `false`. Spend enforcement fails open on a Postgres outage, so inference stays up. Set `true` to fail closed: over-cap developers are blocked, but so is everyone else if the store is unreachable. Requires an [`admin:`](#admin) block: spend enforcement only runs when `admin` is configured, and the gateway refuses to start if you set this `true` without one. |


[​](#pricing)

`pricing`

The `pricing` block tells the spend meter what to charge instead of USD list price, so caps and [`/effective`](claude-apps-gateway-spend-limits.md#%2Feffective) reflect your contracted rates. Amounts stay in USD and remain an estimate, not an invoice. Two prerequisites:

- Claude Code v2.1.227 or later on the gateway server. Earlier versions reject the unknown key at boot.
- An [`admin:`](#admin) block or, in v2.1.268 or later, a [`managed:`](#managed) block with at least one policy. The gateway refuses to start with `pricing` set and neither block, because nothing would read it.

```python
pricing:
  multiplier: 0.85
  overrides:
    - upstream: bedrock-eu
      model: claude-sonnet-4-6
      input: 3.30
      output: 16.50
      cache_read: 0.33
      cache_write: 4.125
```

| Field        | Required | Description                                                                                                                                                                                                                     |
|--------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `multiplier` | No       | Default `1`. The meter multiplies every metered amount by this, whether list-priced or overridden, so `0.85` bills 85% of the price. Must be greater than 0 and at most 10, and a value above 1 is a [markup](#mark-prices-up). |
| `overrides`  | No       | Rows of `{upstream, model, input, output, cache_read, cache_write}` in USD per million tokens. All four rates are required. Each must be greater than 0 and at most 10000.                                                      |

How the meter matches an override row:

- A row replaces list price for requests that `upstream`, an [`upstreams[].name`](#upstreams), serves for `model`. That includes the higher [fast mode](../02-Claude-Code-CLI/fast-mode.md#understand-the-cost-tradeoff) rate, so fast and standard requests meter at the same four rates.
- A built-in ID such as `claude-sonnet-4-6`, matched like [`models[].id`](#models), covers every dated form, regional Amazon Bedrock form, or Google Cloud’s Agent Platform form the meter prices as that model. Any other string, such as an alias or an inference-profile ARN, matches the ID the client sent or the string sent upstream, case-insensitively.
- Where rows overlap, the meter picks the most specific row rather than the first row: a row whose `model` is the exact model string sent upstream, then a row matching the exact ID the client sent, then a row naming the built-in model.
- An unknown upstream name fails boot, and so do two rows for one upstream that name the same model, including two spellings of one built-in model. The gateway warns at boot about a row no requestable model can use.
- Web-search requests stay at the \$0.01 list price; the multiplier still applies to them.

For per-region rates, give each region its own named upstream and one row per upstream.


[​](#mark-prices-up)

Mark prices up

With v2.1.271 or later on the gateway server, you can set `multiplier` above 1, up to 10, to meter more than the provider charges, for example an internal chargeback rate. This example meters every request at 120% of the price:

```python
pricing:
  multiplier: 1.2
```

With an [`admin:`](#admin) block, the markup also applies to spend limits. The meter counts 120% of the price, so developers reach their caps sooner. The gateway logs a warning at boot that says so. The multiplier doesn’t change what the upstream provider charges for the requests. If the gateway also [sends the rates to signed-in clients](#send-the-rates-to-signed-in-clients), developers need Claude Code v2.1.271 or later to see the markup. Earlier clients ignore a `multiplier` above 1 and show costs without it. A gateway server earlier than v2.1.271 refuses to start if you set a `multiplier` above 1.


[​](#send-the-rates-to-signed-in-clients)

Send the rates to signed-in clients

With v2.1.268 or later on the gateway server, the gateway also puts the rates from `pricing` into the [`managed`](#managed) policies it serves, as the [`modelPricing`](../02-Claude-Code-CLI/settings-reference.md#modelpricing) managed setting. Developers matched by a policy then see the `pricing` rates for the first upstream that serves each model ID in `/usage`, the status line, and OpenTelemetry. A developer who matches no policy receives no managed settings, so their figures stay at list price. Clients apply the setting in Claude Code v2.1.242 or later.

- What the gateway adds: unless a policy’s `cli` block already sets `modelPricing`, the gateway adds the `multiplier` and, for every model ID a client can request, the override row of the first upstream that serves that ID. A rate that only a failover upstream charges stays on the gateway.
- Opt one policy out: set `modelPricing` to `{}` in that policy’s `cli` block, and its developers stay at list price.
- Keep a policy’s own rates: a policy whose `cli` block sets `modelPricing` with its own `multiplier` or `overrides` keeps that `modelPricing` whole, and the gateway adds no rates of its own to it.


[​](#models)

`models`

The `models` block is an optional admin-curated model list, served at `/v1/models` and used to translate model IDs per upstream. It is required for non-US Amazon Bedrock regions, Amazon Bedrock provisioned-throughput ARNs, and Microsoft Foundry deployment names.

```python
auto_include_builtin_models: true   # false: expose only the list below
models:
  - id: claude-opus-4-8
    label: Claude Opus 4.8
    # description: optional text shown in clients that surface it
    upstream_model:
      anthropic: claude-opus-4-8
      bedrock: us.anthropic.claude-opus-4-8   # or an inference-profile ARN
      foundry: your-opus-deployment-name
```

Each key under `upstream_model` must match the `name` of a configured upstream, which defaults to the provider name. A key that matches no upstream fails boot, so omit the lines for providers you don’t use.


[​](#managed)

`managed`

The `managed` block defines role-based access policies keyed on IdP groups or email domain. Policies are evaluated in order; the first match is selected, then merged onto the `match: {}` catch-all base. They are served per-user at `GET /managed/settings` with ETag/304 caching.

```python
managed:
  policies:
    # Specific groups first.
    - match: { groups: [eng-contractors] }
      cli:
        availableModels: [claude-sonnet-4-6]
        permissions: { deny: ["WebFetch", "WebSearch"] }
    # Default catch-all last: matches everyone who authenticated.
    - match: {}
      cli:
        availableModels: [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5]
        # Make the Default option in /model resolve inside each policy's
        # list. The eng-contractors policy inherits enforceAvailableModels.
        enforceAvailableModels: true
```

A `match: {}` catch-all, conventionally listed last, is treated as a base layer. Every other policy inherits any key it doesn’t set from the catch-all, so per-role entries only need to list what differs from the org default. The merge rules depend on the key type:

- **Allow-lists**: `availableModels` and `permissions.allow`. A specific policy’s list fully replaces the base’s.
- **Deny-lists and hook arrays**: `permissions.deny`, `permissions.ask`, `disabledMcpjsonServers`, `deniedMcpServers`, `blockedMarketplaces`, `deniedModels`, and every `hooks` event-type array. These take the union of base and policy, so an org-wide deny or audit hook can’t be accidentally dropped by a per-role override.
- **Record-typed keys**: `env`, `modelOverrides`, and `skillOverrides`. These shallow-merge, so a per-role `env` block overrides keys it sets and inherits the rest from the base.

`availableModels` is also enforced server-side at `/v1/messages`, so a denied model returns `400` regardless of what the client sends. An empty list denies every model. The check also covers the model a session starts on before the developer picks one, so [start sessions on a model the policy allows](#start-sessions-on-a-model-the-policy-allows). The gateway validates the `model` value itself before it relays a request, so a malformed value never reaches an upstream. It rejects the request with a `400` in two cases:

- When the value is missing or empty, the gateway rejects the request with the message `model is required`. That check requires a gateway running Claude Code v2.1.228 or later.
- When the value is present but isn’t a string, the gateway rejects the request with the message `model must be a string`. Requires a gateway running Claude Code v2.1.221 or later.

| Matcher                                             | Behavior                                                                                                                         |
|-----------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------|
| `match: {}`                                         | Matches every authenticated user. Start with one of these and add group-scoped policies above it later.                          |
| `match: { groups: [a, b] }`                         | Matches if the JWT’s `groups` claim contains any of the listed groups. Case-sensitive: groups must match the IdP’s exact casing. |
| `match: { email_domain: example.com }`              | Matches the part after the last `@` in the JWT’s `email` claim, case-insensitive. Accepts one domain per policy.                 |
| `match: { groups: [a], email_domain: example.com }` | Both conditions must match                                                                                                       |

An authenticated user who matches no policy gets the gateway’s defaults, which means every model in the catalog and no managed settings. Add a `match: {}` catch-all last if you want a guaranteed default policy.

The gateway keeps no user directory of its own. It authorizes each request from the user’s IdP token, reading group membership from the token’s `groups` claim and evaluating policies against it. There is no roster to enumerate and no accounts to pre-create, and therefore no SCIM endpoint, because there is nothing for SCIM to sync into.Run user and group lifecycle management at the source of truth, which is your IdP’s native SCIM provisioning or a dedicated identity-governance platform. Membership and deprovisioning governed there flow into the gateway automatically through the token. If you want SCIM provisioning of Claude accounts themselves, that is a [Claude for Enterprise](admin-setup.md) capability.Two propagation clocks apply:

- **Policy contents**: editing a policy and redeploying reaches connected Claude Code clients on their next managed-settings poll, within an hour, apart from the [changes that apply only at the next launch](server-managed-settings.md#fetch-and-caching-behavior)
- **Group membership**: changing a user’s group membership changes which policy matches them. This takes effect on the next session re-mint, meaning the next silent refresh, bounded by `session.ttl_hours`.

Claude Desktop follows [its own schedule](#when-a-policy-change-reaches-claude-desktop).


[​](#start-sessions-on-a-model-the-policy-allows)

Start sessions on a model the policy allows

If `availableModels` leaves out Claude Code’s default model, sessions get `400` responses until the developer picks a listed model, for example with `/model`. In gateway sessions the default is the Opus model the `opus` alias resolves to, and `availableModels` on its own doesn’t change it. To fix this, set [`enforceAvailableModels: true`](../02-Claude-Code-CLI/model-config.md#enforce-the-allowlist-for-the-default-model) in the same `cli` block, then check which kind of entry the list has:

- **An alias such as `sonnet`, or a built-in ID such as `claude-sonnet-4-6`**: sessions start on one of those models, and the Default option in `/model` resolves to it
- **No alias or built-in ID in the list**: sessions can keep starting on the built-in default, so also set [`model`](../02-Claude-Code-CLI/model-config.md#control-the-model-users-run-on) to one of the listed IDs in that policy’s `cli` block

This policy lists one custom ID that [`models`](#models) defines, and starts sessions on that ID:

```python
managed:
  policies:
    - match: { groups: [restricted-projects] }
      cli:
        availableModels: [claude-opus-restricted]
        enforceAvailableModels: true
        model: claude-opus-restricted
```


[​](#matcher-values-that-stop-the-gateway-at-boot)

Matcher values that stop the gateway at boot

At boot, the gateway checks the `match` block of every policy and the [`admin_groups`](#admin) list. Any of these values stops the gateway with an error that names the field:

- An empty `groups` list
- An empty entry in `groups` or in `admin_groups`
- An empty `email_domain`
- An `email_domain` that contains `@`, whitespace, or a comma. The gateway trims the value and strips one leading `@` before this check. Write one bare domain, such as `example.com`.

Before v2.1.232, the gateway started with these values. Each value had this effect:

- An empty `email_domain`: the gateway skipped the domain check, so a policy with an empty `email_domain` and no `groups` list matched every authenticated user
- An empty `groups` list: the policy matched no one
- An `email_domain` containing `@`, whitespace, or a comma: the policy matched no one
- An empty entry in `groups` or in `admin_groups`: the entry matched a user only when that user’s IdP `groups` claim also contained an empty entry. In `admin_groups`, that match granted admin access. If your `admin_groups` list never contained an empty entry, no one gained admin access this way.


[​](#choose-cli-or-code)

Choose `cli` or `code`

When a policy uses the `code` key, each of these stops the gateway from starting:

- **Gateway version**: `code` requires Claude Code v2.1.296 or later on the gateway server. An earlier gateway refuses to start when it finds the key. Upgrade every replica before you add the key, and change `code` back to `cli` before you roll back to an earlier version.
- **Mixed keys**: a file that has both `code` and `cli`, or its earlier spelling `settings`, stops the gateway at boot. Put every block under one key, in one edit.

A policy’s Claude Code settings, such as a rule that denies reading `.env` files, go in a block under the `cli` or `code` key. `code` is the recommended key, and `cli` is the legacy key. Both keys take the same contents. The key decides where the settings are enforced:

- **`cli`**: the terminal, the VS Code and JetBrains extensions, and the Agent SDK. Under `cli`, Claude Desktop’s Code tab gets the [derived settings](#claude-desktop-overlay), so a scoped rule such as `Read(./.env)` doesn’t stop a user there.
- **`code`**: the same places, and Claude Desktop’s Code tab can be covered too.

A file that uses `cli` works as it did, and a gateway that finds `cli` in a policy with a [`desktop`](#claude-desktop-overlay) key warns at boot and starts anyway. Switch to `code` so that the settings can also cover the Code tab. Before you switch, read [Apply `code` settings in the Code tab](#apply-code-settings-in-the-code-tab). The policy needs a `desktop` key and users’ machines need setup before the settings apply there, and web search turns off in Claude Desktop. This policy puts the deny rule under `code`, with an empty `desktop` key:

```python
managed:
  policies:
    - match: {}
      code:
        permissions: { deny: ["Read(./.env)"] }
      desktop: {}
```

When you switch, delete any `serve_to_desktop` line in the same edit that renames the blocks. Where an instruction names the `cli` block, use your `code` block.


[​](#what-goes-in-cli)

What goes in `cli`

Each `code` or `cli` value is a complete Claude Code `managed-settings.json` document, the same schema you would deploy via MDM or `/etc/claude-code/managed-settings.json`, expressed here as YAML. The CLI applies the delivered document at the managed tier, above user and project settings, in place of server-managed settings. It therefore ignores the settings [restricted to OS-level policy sources](server-managed-settings.md#current-limitations), such as `policyHelper` and `wslInheritsWindowsSettings`. The gateway validates each document against the CLI’s settings schema at boot, so an unrecognized top-level key fails boot with an error naming every offending key. Deliberately open parts of the schema still accept arbitrary values, because newer clients may recognize entries the gateway’s schema doesn’t. These open keys include `env`, `pluginConfigs`, and keys nested under `permissions`. Because validation uses the schema bundled with the gateway’s installed version, putting a top-level settings key introduced by a newer Claude Code release into managed config requires upgrading the gateway first. Smoke-test a new policy on one client before rolling it out. The full key reference is in [Claude Code settings](../02-Claude-Code-CLI/settings-reference.md#all-settings). The keys most operators reach for first:

```python
managed:
  policies:
    - match: {}
      cli:
        # Model access (also enforced server-side at /v1/messages)
        availableModels: [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5]
        enforceAvailableModels: true              # Default resolves inside the list

        # Permission policy
        permissions:
          deny:
            - "WebFetch"
            - "Read(./.env)"
            - "Read(./secrets/**)"
          disableBypassPermissionsMode: disable   # blocks --dangerously-skip-permissions
        allowManagedPermissionRulesOnly: true     # ignore user/project permission rules

        # Environment pushed into the CLI process. DISABLE_UPDATES blocks
        # background and manual updates; DISABLE_AUTOUPDATER stops only
        # background updates.
        env:
          DISABLE_UPDATES: "1"                    # pin versions via your own distribution

        # Org-wide hooks. Hook commands run on developer machines, not the
        # gateway, so the path must exist on every client OS in the policy.
        hooks:
          PostToolUse:
            - matcher: "Edit|Write"
              hooks:
                - { type: command, command: /usr/local/bin/audit-edit.sh }
```

| Key                                                                                                                                    | Enforced by   | Effect                                                                                                                                                                                                                                                                                                                                                                                                                     |
|----------------------------------------------------------------------------------------------------------------------------------------|---------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `availableModels`                                                                                                                      | Gateway + CLI | Model allowlist. Also checked at `/v1/messages`, so a patched client can’t bypass it.                                                                                                                                                                                                                                                                                                                                      |
| [`deniedModels`](../02-Claude-Code-CLI/settings-reference.md#deniedmodels), [`availableModelsMatch`](../02-Claude-Code-CLI/settings-reference.md#availablemodelsmatch) | Gateway + CLI | [Block specific models or versions](../02-Claude-Code-CLI/model-config.md#block-specific-models-or-versions) that `availableModels` would otherwise permit. A blocked model is rejected with a `400` at `/v1/messages` and is left out of `/v1/models`. The gateway’s own check requires Claude Code v2.1.299 or later on the gateway server. The CLI’s check requires v2.1.283 or later on clients, and earlier clients ignore both keys. |
| `permissions.allow` / `.deny`                                                                                                          | CLI           | Tool and command rules. See [Permissions](../02-Claude-Code-CLI/permissions.md).                                                                                                                                                                                                                                                                                                                                                           |
| `permissions.disableBypassPermissionsMode`                                                                                             | CLI           | Set to `disable` to block [`bypassPermissions`](../02-Claude-Code-CLI/permission-modes.md#skip-all-checks-with-bypasspermissions-mode), the mode that skips permission prompts, and the `--dangerously-skip-permissions` flag                                                                                                                                                                                                              |
| `allowManagedPermissionRulesOnly`                                                                                                      | CLI           | When `true`, managed settings become the only settings source of permission rules. The [`allowManagedPermissionRulesOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedpermissionrulesonly) entry lists every source Claude Code then ignores.                                                                                                                                                                                 |
| `env`                                                                                                                                  | CLI           | Environment variables merged into the CLI process. Use for telemetry, auto-update, and model-name overrides.                                                                                                                                                                                                                                                                                                               |
| `hooks`                                                                                                                                | CLI           | Org-wide [hooks](../07-Hooks/hooks.md)                                                                                                                                                                                                                                                                                                                                                                                           |
| `managedMcpServers`                                                                                                                    | CLI           | Remote MCP servers [provided to every matching developer](../06-MCP-Tools/General/managed-mcp.md#provide-servers-through-managed-settings) alongside the servers they add themselves, `http` and `sse` only. See [MCP servers in a policy](#mcp-servers-in-a-policy). Requires Claude Code v2.1.259 or later on the gateway server and on clients. Earlier clients ignore the key.                                                           |

Because these settings arrive over the network, the CLI shows each developer a security approval dialog before applying the settings listed below:

- `hooks`
- `env` variables that require the developer’s approval, such as proxy and base-URL variables
- shell-execution settings such as `apiKeyHelper` and `statusLine`
- the sandbox binary settings `sandbox.bwrapPath`, `sandbox.socatPath`, and `sandbox.ripgrep`
- Sandbox settings that intercept traffic, inject credentials, or weaken isolation, such as `sandbox.network.tlsTerminate` and the proxy port settings. [Security approval dialogs](server-managed-settings.md#security-approval-dialogs) lists them all.

[Approval memory](server-managed-settings.md#approval-memory) covers how long an approval lasts and when the dialog appears again. Claude Code applies some delivered `env` variables without showing the developer the approval dialog, such as model selection settings and numeric limits. Other delivered variables can require the developer’s approval before they take effect; a non-empty proxy, base-URL, or `OTEL_EXPORTER_OTLP_ENDPOINT` value always does. When a delivered variable needs approval, the dialog names it. [Environment variables and the approval dialog](server-managed-settings.md#environment-variables-and-the-approval-dialog) has the details, including four privacy toggles whose delivered value decides whether they need approval. Before v2.1.218, Claude Code applied fewer variables without asking the developer, so more delivered variables triggered the dialog. The gateway’s [telemetry](#telemetry) configuration pushes `OTEL_EXPORTER_OTLP_ENDPOINT`, so setting `telemetry.forward_to` triggers the dialog on each interactive client. The dialog protects the developer’s machine from a compromised or hostile gateway, not the organization from the developer. A [non-interactive run](server-managed-settings.md#security-approval-dialogs), such as `claude -p`, an Agent SDK session, or a session in Claude Desktop’s Code tab, can’t show the dialog. It applies the pushed settings for that run only and doesn’t record them as approved, so the developer’s next interactive session still shows the dialog. If a developer declines, Claude Code exits that session rather than applying the policy. When you push a new hook, or any env var that triggers the dialog, to a broad policy, every matching developer therefore sees the dialog in their interactive sessions. A running interactive session shows it on the next hourly poll, and otherwise it appears at the developer’s next interactive startup. The `cli` key was named `settings` in earlier releases, and the gateway still accepts that spelling as an alias. For new deployments, use [`code`](#choose-cli-or-code).


[​](#context-window-in-terminal-sessions)

Context window in terminal sessions

Terminal sessions signed in through `/login` use the 1M context window for Opus 4.7 and later, Sonnet 5 and later, and the Fable models. The model ID needs no `[1m]` suffix, and sessions compact at about 967K tokens. Before Claude Code v2.1.287 on the developer’s machine, Claude Code treated the Opus and Fable models as having a 200K window unless the model ID ended in `[1m]`. To have terminal sessions compact at the 200K boundary instead, set the [auto-compact window](../02-Claude-Code-CLI/model-config.md#set-the-auto-compact-window) in the policy’s `env`:

```python
managed:
  policies:
    - match: {}
      cli:
        env:
          CLAUDE_CODE_AUTO_COMPACT_WINDOW: "200000"
```

Claude Code applies this variable without showing the developer the approval dialog. The variable applies to every model, including model IDs that end in `[1m]`. To turn off 1M context instead, set [`CLAUDE_CODE_DISABLE_1M_CONTEXT: "1"`](../02-Claude-Code-CLI/model-config.md#extended-context) in the same `env` block. Claude Code then treats every model as having a 200K window. In interactive sessions, each developer approves this variable in the [approval dialog](#what-goes-in-cli) before it takes effect.


[​](#one-hour-prompt-cache)

One-hour prompt cache

To request the one-hour [prompt cache TTL](../02-Claude-Code-CLI/prompt-caching.md#cache-lifetime) instead of the default five minutes, add these keys to a policy’s existing [`code`, `cli`, or `settings` block](#choose-cli-or-code) and restart the gateway. The keys need Claude Code v2.1.242 or later on the gateway server and on clients. `promptCacheTtl` covers the main conversation. `subagentPromptCacheTtl` is optional and covers subagents, workflows, and background requests. This example adds both to a `match: {}` policy keyed `cli`:

```python
managed:
  policies:
    - match: {}
      cli:
        promptCacheTtl: 1h
        subagentPromptCacheTtl: 1h
```

Set the keys only if your upstreams offer the one-hour TTL for every model the gateway serves. If an upstream rejects the TTL, the request fails, and neither the gateway nor Claude Code retries it with the five-minute TTL. For Amazon Bedrock, check [supported models, regions, and limits](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html#prompt-caching-models).

To confirm `promptCacheTtl`, sign in to the gateway as a user the policy matches and [check which TTL your cache writes used](../02-Claude-Code-CLI/prompt-caching.md#choose-the-ttl-yourself). Pass `--model` to repeat the check for each model the gateway serves. One-hour cache writes [cost more](../02-Claude-Code-CLI/prompt-caching.md#cache-lifetime) than five-minute writes. For [spend limits](claude-apps-gateway-spend-limits.md#how-requests-are-priced) to count that higher rate, you need v2.1.286 or later on the gateway server.


[​](#mcp-servers-in-a-policy)

MCP servers in a policy

To provide MCP servers to the Claude Code clients a policy matches, set [`managedMcpServers`](../06-MCP-Tools/General/managed-mcp.md#provide-servers-through-managed-settings) in that policy’s `cli` block. You need Claude Code v2.1.259 or later on the gateway server and on clients. The gateway checks each entry at boot with [the same rules Claude Code applies on the client](../06-MCP-Tools/General/managed-mcp.md#what-an-entry-can-contain), and if an entry fails a check, the gateway refuses to start and names the entry. If you write a `${VAR}` reference in `gateway.yaml`, the gateway resolves it from its environment at boot through [secret expansion](#secret-expansion) before it runs the entry checks, so every matching client receives the literal value and can read it. The [header guidance for provided servers](../06-MCP-Tools/General/managed-mcp.md#provide-servers-through-managed-settings) applies to the expanded value. The gateway rejects the `.mcp.json` spelling `mcpServers` in a `cli` block, and its boot error names `managedMcpServers` as the key to use. Before v2.1.259, the gateway rejected any MCP server definition in a `cli` block.


[​](#claude-desktop-overlay)

Claude Desktop overlay

If your organization also deploys [Claude Desktop](../16-Mobile-Desktop/desktop.md), the same gateway serves both clients. Point `bootstrapUrl`, in Claude Desktop’s [managed configuration](https://claude.com/docs/third-party/claude-desktop/configuration), at `<listen.public_url>/user/bootstrap`. Claude Desktop derives the OAuth issuer from that URL, runs the same device-code sign-in against this gateway, and fetches its configuration from the response.

Requires Claude Code v2.1.203 or later on the gateway server, and an explicit opt-in: `/user/bootstrap` returns 404 unless the policy matching the user carries a `desktop` key. An empty `desktop: {}` opts a policy in, and a `desktop` key on the `match: {}` base layer opts in every policy that inherits it. The audit log records each request as `desktop_bootstrap.serve` or `desktop_bootstrap.denied`.

If you don’t deploy Claude Desktop, leave `desktop` out of your policies entirely; the gateway then returns 404 from `/user/bootstrap` for every user.

##### Settings the gateway derives for Claude Desktop

The gateway derives much of the bootstrap response from the matched policy’s `cli` or `code` block and from top-level gateway config:

- The model list, from `availableModels`. The list leaves out models that the policy blocks with [`deniedModels` or `availableModelsMatch: exact`](#what-goes-in-cli). This filter requires Claude Code v2.1.299 or later on the gateway server. [Extended context in Claude Desktop](#extended-context-in-claude-desktop) covers each model’s 1M context option
- Disabled tools, from bare tool-name `permissions.deny` entries. If you set `disabledBuiltinTools` in the policy’s `desktop` block, the gateway serves the union of your value and the derived list, so you can disable more tools this way but can’t re-enable one you disabled through `permissions.deny`
- The egress allowlist, from `sandbox.network.allowedDomains`. If you set `coworkEgressAllowedHosts` in the policy’s `desktop` block, the gateway uses that value instead of the derived list. If the allowlist the gateway serves includes `*` and the gateway doesn’t also serve Claude Desktop’s [`allowedWorkspaceFolders`](https://claude.com/docs/third-party/claude-desktop/code#applied-as-managed-policy), Claude Desktop doesn’t turn on the [Bash sandbox](../02-Claude-Code-CLI/sandboxing.md) in the Code tab
- An OTLP endpoint that points at the gateway itself, and the signed-in user’s identity attributes. The gateway relays the exports it receives at that endpoint to your `forward_to` destinations. It includes the endpoint and the attributes when you set both [`telemetry.forward_to`](#telemetry) and `listen.public_url`. Claude Desktop exports every signal with one encoding: `http/protobuf`, or `http/json` when you set `OTEL_EXPORTER_OTLP_PROTOCOL` or one of its per-signal variants to `http/json` in the policy’s `env`. Before Claude Code v2.1.261 on the gateway server, the response set `http/json` regardless, so a collector that accepts only protobuf rejected Claude Desktop’s exports

To set `disabledBuiltinTools`, `coworkEgressAllowedHosts`, or Claude Desktop’s own `managedMcpServers` setting in a policy’s `desktop` block, you need Claude Code v2.1.232 or later on the gateway server. Claude Desktop’s `managedMcpServers` takes an array value rather than an object. The gateway omits keys with no Claude Desktop equivalent, such as `hooks` and scoped permission rules like `Bash(npm *)`, from the bootstrap response.

##### Apply `code` settings in the Code tab

The Claude Code settings a policy holds under its [`code`](#choose-cli-or-code) key apply in Claude Desktop’s Code tab when all of these hold:

- The policy also has a `desktop` key, and an empty `desktop: {}` counts
- Claude Desktop is version 2.9939.2 or later
- The Claude Code managed settings on the machine [name this gateway](#client-side-managed-settings)
- That machine reaches the gateway over HTTPS on a private network

The last two conditions apply to whichever machine runs Claude Code for the session. For a local session, that is the user’s own computer. For a Code tab session over SSH, it is the remote host. Deploy the [client-side managed settings](#client-side-managed-settings) to those machines before you rename `cli` to `code`. When the first two conditions hold and either of the last two fails, a user the policy matches gets one of these results:

- **Over HTTPS, where Claude Desktop bundles Claude Code v2.1.296 or later**: the Code tab doesn’t start, and the reply to the user’s prompt says why
- **Over HTTPS, where Claude Desktop bundles an earlier version**: the Code tab starts without your `code` settings, and nothing in the session says so
- **Over plain HTTP**: the Code tab starts without your `code` settings, whichever version Claude Desktop bundles

When a proxy in front of the gateway answers `/managed/settings` with its own 404, the Code tab also starts without your `code` settings, including on a machine that meets all four conditions. In a policy that has a `desktop` key, a `code` block that holds settings turns off the WebSearch tool in Cowork, Chat, and the Code tab. For MCP servers, see [Where `managedMcpServers` applies](../06-MCP-Tools/General/managed-mcp.md#where-managedmcpservers-applies).

##### Set Claude Desktop settings directly

Add the optional `desktop` block alongside `cli` to set Claude Desktop settings directly. Write settings from Claude Desktop’s [managed configuration reference](https://claude.com/docs/third-party/claude-desktop/configuration) as flat key names. Leave out keys Claude Desktop reads only from MDM or local files, such as `bootstrapUrl`; the gateway rejects them at boot. This example sets three Claude Desktop keys for the `eng-contractors` group alongside its `cli` settings:

```python
managed:
  policies:
    - match: { groups: [eng-contractors] }
      cli:
        availableModels: [claude-sonnet-4-6]
        enforceAvailableModels: true
      desktop:
        isLocalDevMcpEnabled: false
        disableAutoUpdates: true
        banner: { text: "Contractor build: internal use only" }
```

Every key is optional; Claude Desktop applies its own default for any key you omit.

##### What the gateway rejects at boot

The gateway validates each `desktop` block at boot against the configuration schema Claude Desktop itself uses, so a mistake surfaces at gateway start as an error naming the key rather than reaching every connected desktop. The gateway fails at boot when a block contains:

- An unknown key
- A recognized key whose value Claude Desktop would reject or silently drop, such as an empty value or a misspelled sub-key inside a nested entry. Before v2.1.260, the gateway silently dropped a misspelled field inside a nested object of a `managedMcpServers` or `orgPluginSettings` entry instead of failing at boot.
- A key the gateway computes itself: the inference connection, the model list, and the OTLP relay. Configure those through [`upstreams`](#upstreams), [`models`](#models), and the [`telemetry`](#telemetry) section’s `forward_to`.
- A legacy alias of a current key. In the boot error, the gateway names the canonical key to write.

If you use a deprecated value or entry shape, such as a `managedMcpServers` entry without `transport`, the gateway starts and logs a warning that names the replacement. Before v2.1.232, the gateway accepted a fixed list of 11 feature-gate keys, such as `chatTabEnabled` and `disableAutoUpdates`, and rejected every other key at boot. Before v2.1.227, the gateway also rejected `chatTabEnabled` and `chatAdvancedFileAnalysisEnabled` at boot.

##### Keys that need a later gateway or Claude Desktop version

The gateway validates a `desktop` block against the schema bundled with its installed version, as it does the `cli` block. To deliver a setting introduced by a newer Claude Desktop release, upgrade the gateway first. For example, `userPluginMarketplacesEnabled` and `userPluginUploadsEnabled` need Claude Code v2.1.260 or later on the gateway server and Claude Desktop 1.37937.0 or later on members’ machines. `blockReadsOutsideWorkingDirectories`, `disableBypassPermissionsMode`, `configRecheckIntervalMinutes`, and `sshClientPath` need Claude Code v2.1.281 or later on the gateway server. So do the `required` value of `microsoftAuthBroker` and the `continuousAccessEvaluation` field of a Microsoft 365 `managedMcpServers` entry. Claude Desktop releases that predate the `required` value read it as `disabled`, so set `required` only after every member’s Claude Desktop supports it. Claude Desktop’s [managed configuration reference](https://claude.com/docs/third-party/claude-desktop/configuration) lists the release that first reads each key. If you set `orgPluginSettings` in a policy’s `desktop` block, the gateway serves it in the array form that Claude Desktop 1.15200.0 and later reads. Older desktops ignore the array and enforce no plugin tool policy, so update members to 1.15200.0 or later before you rely on it.

##### How a role policy inherits the base `desktop` block

The gateway fills in keys a policy’s `desktop` block doesn’t set from the `match: {}` catch-all’s `desktop` block, the same way it fills in a policy’s `cli` block from the base. If you set `disabledBuiltinTools` or `builtinToolPolicy` in both the base and a role policy, the gateway keeps the base’s restriction:

- `disabledBuiltinTools`: the gateway uses the union of the base’s list and the policy’s list
- `builtinToolPolicy`: if you set a tool to a value other than `allow` in the base, the gateway keeps that value even if you set `allow` for the same tool in a role policy

For every other key, if you set it in the role policy, the gateway uses the role policy’s value. The gateway replaces an array or a nested object such as `banner` whole, so if you set `banner.text` in a role policy, the gateway drops the base’s `banner.backgroundColor`.

##### When a policy change reaches Claude Desktop

After you redeploy the gateway with a changed policy, Claude Desktop applies most settings only when it next starts:

- **Closed**: Claude Desktop fetches the bootstrap response when it starts, so the change applies from the next start
- **Open**: Claude Desktop checks for a changed response every 10 minutes by default and applies a few settings without a restart. For the rest, such as [`skillCreationEnabled`](https://claude.com/docs/third-party/claude-desktop/configuration#skillcreationenabled), the user sees a **Relaunch Claude Desktop** card in the sidebar and keeps the previous configuration until they restart the app. After 24 hours by default, Claude Desktop shows a restart dialog and restarts on its own after 2 minutes of inactivity

To shorten the 24 hours, set [`relaunchEnforcementHours`](https://claude.com/docs/third-party/claude-desktop/configuration#relaunchenforcementhours) in the policy’s `desktop` block. You need Claude Code v2.1.260 or later on the gateway server and Claude Desktop 1.40609.0 or later on members’ machines. With `0`, the dialog appears as soon as Claude Desktop finds the change.


[​](#extended-context-in-claude-desktop)

Extended context in Claude Desktop

If you serve [Claude Desktop](#claude-desktop-overlay) from the gateway, its model picker offers a 1M context option for each listed model that can run with a 1M context window. These include Claude Opus 4.6 and later, Claude Sonnet 4.6 and later, and the Fable models. The option is the model’s `[1m]` variant, which [Extended context](../02-Claude-Code-CLI/model-config.md#extended-context) describes. You need Claude Code v2.1.284 or later on the gateway server. A [`models`](#models) entry gets no 1M option when:

- An upstream that can serve the entry maps it to a model without 1M support, including an upstream the gateway reaches only on failover
- Neither its `id` nor any of its `upstream_model` values names a Claude model, such as a custom alias routed to an application inference profile ARN

To change what the picker offers, use one of these:

- **Start users on the 1M option**: set `modelPrefer1mContext: true` in the policy’s `desktop` block. Users who haven’t yet chosen a model start on the 1M option when the first listed model has one. Users who already chose a model keep their choice.
- **Offer the option by hand**: do this if your gateway server runs a version older than v2.1.284, or an entry names no Claude model. List the model twice in `models`, once with its plain ID and once with `[1m]` appended, both with the same `upstream_model` map. Claude Desktop shows the pair as one model with a 1M option. The gateway serves a `[1m]` entry without checking it, so add one only for a model your upstreams serve at 1M.

This example offers the option by hand for a custom alias routed to an application inference profile, and starts new users on it:

```python
models:
  - id: corp-sonnet
    upstream_model:
      bedrock: arn:aws:bedrock:us-east-2:123456789012:application-inference-profile/sonnet-5-prod
  - id: corp-sonnet[1m]
    upstream_model:
      bedrock: arn:aws:bedrock:us-east-2:123456789012:application-inference-profile/sonnet-5-prod

managed:
  policies:
    - match: {}
      desktop:
        modelPrefer1mContext: true
```

##### Remove the 1M option

To remove the option from the picker, set `CLAUDE_CODE_DISABLE_1M_CONTEXT: "1"` in the `env` block under the policy’s `cli` key. If you also listed an entry whose `id` ends in `[1m]`, the gateway still serves it, so delete that entry too. The variable also reaches the terminal sessions of developers the policy matches. For what it changes there, see [Extended context](../02-Claude-Code-CLI/model-config.md#extended-context).


[​](#precedence-with-other-managed-sources)

Precedence with other managed sources

If a device also has an MDM-delivered policy or a local `managed-settings.json`, gateway-delivered settings rank first. [Precedence within the managed tier](managed-settings.md#precedence-within-the-managed-tier) on the managed settings page says when the local sources apply, and has the [keys Claude Code reads from every admin source](managed-settings.md#keys-read-from-every-admin-source) regardless of which source it selected, such as the sandbox lock keys, `forceRemoteSettingsRefresh`, and the per-variable `env` merge. A [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) configured in an MDM profile or the managed settings file runs only when the gateway delivers no settings; the entry says what its output replaces. Embedding hosts such as [Claude Desktop](../16-Mobile-Desktop/desktop.md) can supply policy through the SDK `managedSettings` option. [Parent settings from embedding hosts](managed-settings.md#parent-settings-from-embedding-hosts) says when Claude Code applies it, and [Restrict parent settings](claude-apps-gateway.md#restrict-parent-settings) lists which allow-direction settings still apply without the `allowManaged*Only` locks. Gateway policies apply to every Claude Code invocation on the machine, including non-interactive `claude -p` runs and sessions spawned by the Agent SDK. If the gateway is unreachable at startup, signed-in sessions exit with an error rather than running without their policy.


[​](#telemetry)

`telemetry`

The CLI sends metrics, logs, and, when enabled, traces to the gateway, which relays them verbatim to each configured destination. The exports use OpenTelemetry Protocol (OTLP) over HTTP. To skip the relay and have sessions export straight to your collector, [name the collector in a policy](#export-directly-to-your-collector). See [Monitoring usage](monitoring-usage.md) for the metrics and events the CLI emits. In sessions signed in through `/login`, the CLI stamps each export with the authenticated user’s identity, read from the gateway-issued JWT: the `user.id`, `user.email`, and `user.groups` attributes. Per-developer cost and usage attribution therefore works with no developer-side configuration. Events that Claude Code logs before the developer signs in [don’t carry this identity](monitoring-usage.md#standard-attributes). To see which attribute follows a change in a developer’s groups, see [Group changes during an open session](#group-changes-during-an-open-session). [Claude Desktop](#claude-desktop-overlay) and Cowork sessions signed in through the gateway stamp their telemetry with `user.email` and `user.groups` alongside `enduser.id`, so you can cover terminal, Desktop, and Cowork usage with one query on `user.email` or `user.groups`. `user.groups` is the comma-separated IdP group list. Desktop and Cowork telemetry also carries `enduser.sub`, the `sub` claim your identity provider issues for the user, which stays the same when a user’s email changes. Terminal sessions stamp the same value under `user.id`, so a query that matches `enduser.sub` against terminal `user.id` covers one user’s terminal, Desktop, and Cowork usage together. On Desktop and Cowork exports, `user.id` is an anonymous identifier, not the subject. Like all OpenTelemetry data from Claude Code, these attributes go only to destinations your organization configures, never to Anthropic. If a user’s group list is longer than 255 characters once percent-encoded, or a group name contains a comma or equals sign, the gateway leaves `user.groups` off that user’s Desktop and Cowork telemetry rather than truncating it. That user’s terminal sessions still carry the full list. The gateway leaves `enduser.sub` off when the subject is longer than 255 characters once percent-encoded, or contains a space, a character outside printable ASCII, or one of `,` `;` `=` `\` `"` `%`. That user’s Desktop and Cowork telemetry keeps its other attributes. You need Claude Code v2.1.265 or later on the gateway server for `user.email` and `user.groups` on Desktop and Cowork telemetry, and Claude Desktop 1.24012 or later on each developer’s machine for `user.groups`. You need Claude Code v2.1.274 or later on the gateway server for `enduser.sub`.

```python
telemetry:
  forward_to:
    - url: https://otel-collector.internal.example.com
      headers:
        Authorization: ${OTLP_TOKEN}
      # Per-signal opt-in. Default: metrics only.
      metrics: true
      logs: false
      traces: false
    - url: https://api.datadoghq.com/api/v2/otlp
      headers:
        DD-API-KEY: ${DD_API_KEY}
```

Each destination opts into `metrics`, `logs`, and `traces` independently, and the default is metrics only. The signals differ in sensitivity:

- **Metrics**: aggregate counters such as token counts, request counts, and latency
- **Logs and traces**: can carry full Bash commands, tool inputs, and file paths, covering anything Claude Code does on a developer’s machine

Enable logs and traces only on destinations with the access controls and retention policy that data warrants.

Each `forward_to` URL must use `https://`, with one exception for a collector on the gateway’s own loopback interface:

- `http://localhost:<port>` passes config validation, but the [SSRF guard](claude-apps-gateway-deploy.md#threat-model-summary) blocks every export with `ECONNREFUSED_SSRF` unless you set `CLAUDE_GATEWAY_ALLOW_LOOPBACK=1` in the gateway’s environment
- `http://127.0.0.1:<port>` or `http://[::1]:<port>` fails boot unless that variable is set

For an in-cluster collector, expose it over HTTPS at its own internal address, or run it as a sidecar with the variable set. When `HTTPS_PROXY` is set, the gateway sends exports through that proxy. To reach an internal collector directly, add it to `NO_PROXY` by hostname or by a domain with a leading dot such as `.internal.example.com`, which requires Claude Code v2.1.277 or later on the gateway server. Make sure the gateway can reach the collector without the proxy. An entry without a leading dot matches only that exact name, not names under it. CIDR ranges don’t match. With [proxy-only egress](#proxy-only-egress) turned on, allow the collector in the proxy instead, since any `NO_PROXY` entry keeps proxy-only egress off. Telemetry is off in the CLI by default. When you set both `telemetry.forward_to` and `listen.public_url`, the gateway turns it on for connected clients by pushing six environment variables through `/managed/settings`:

- `CLAUDE_CODE_ENABLE_TELEMETRY=1`
- `OTEL_METRICS_EXPORTER`, `OTEL_LOGS_EXPORTER`, and `OTEL_TRACES_EXPORTER`, each set to `otlp` if at least one `forward_to` destination enables that signal and to `none` otherwise
- `OTEL_EXPORTER_OTLP_ENDPOINT=<public_url>`
- `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`

When you [add your own labels](#add-your-own-labels), the gateway also pushes `OTEL_RESOURCE_ATTRIBUTES`. Before Claude Code v2.1.265 on the gateway server, the gateway pushed all three exporter selectors as `otlp`, including for signals no destination opted into. The pushed endpoint is built from the public URL, so metrics and logs need no OTEL configuration from developers or policies. Developers signed in through `/login` can’t redirect exports with their own OTEL configuration:

- **Locally set variables**: Claude Code applies the pushed variables at the managed tier, so each one overrides the value a developer sets for it locally.
- **Locally configured endpoints**: with OTLP/HTTP export enabled, the CLI ignores any locally configured endpoint, whether or not the gateway pushed the telemetry variables. Its exports go to the gateway unless a policy [names your collector as the endpoint](#export-directly-to-your-collector).

Without a `forward_to` destination for a signal, the gateway accepts and discards it. If developers already export Claude Code telemetry to one of your collectors, add it as a `forward_to` destination, with logs or traces enabled if they export those, so it keeps receiving their data after they sign in. To skip the relay instead, [name the collector in a policy](#export-directly-to-your-collector). [Traces](monitoring-usage.md#traces-beta) also require `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1` on each client. Set it in a managed policy’s `env` block, since the gateway doesn’t push it. Developers approve it in the same [security approval dialog](#managed) that the pushed endpoint already triggers. Set it to `1` only in the policies whose groups you want traced. A policy that doesn’t set it inherits the value from your `match: {}` catch-all policy if that policy sets one, per the [merge rules](#managed). To keep a group’s clients from sending traces even when a developer sets the variable locally, set it to `0` in that group’s policy. Both protobuf and JSON OTLP encodings are relayed, and any OpenTelemetry-compatible backend works as a destination.


[​](#send-cumulative-metrics)

Send cumulative metrics

If your collector accepts only cumulative metrics, set [`OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE`](monitoring-usage.md#common-configuration-variables) to `cumulative`. Claude Code sends delta metrics by default, and the gateway relays them without converting them. Set the variable where each kind of session reads it:

- **Terminal sessions**: in the `env` block under a policy’s [`cli` or `code` key](#choose-cli-or-code)
- **Cowork sessions that [run on the user’s machine](managed-settings.md#where-and-when-a-policy-applies)**: in the `env` block of the [client-side managed settings](#client-side-managed-settings) on that machine
- **Code tab sessions under a `code` policy**: in the policy’s `env` block, once you meet the [conditions for `code` settings to apply](#apply-code-settings-in-the-code-tab)
- **Code tab sessions under a `cli` policy**: in the `env` block of the client-side managed settings on the machine that runs the session. Over SSH, that’s the remote host.

If you add the variable to a machine’s managed settings, also set [`parentSettingsBehavior: "merge"`](claude-apps-gateway.md#set-the-opt-in) in the highest-priority source on that machine. Without it, Cowork sessions and Code tab sessions under a `cli` policy run without the egress allowlist and other policy that Claude Desktop passes them as parent settings.


[​](#add-your-own-labels)

Add your own labels

To put fixed labels such as `service.namespace` or `deployment.environment.name` on the telemetry of sessions signed in through the gateway, set `telemetry.resource_attributes`. Each label is an OpenTelemetry resource attribute, and every destination receives the same labels. Sessions get the labels only when you also set `telemetry.forward_to` and `listen.public_url`. This example adds two labels:

```python
telemetry:
  forward_to:
    - url: https://otel-collector.internal.example.com
  resource_attributes:
    service.namespace: claude
    deployment.environment.name: prod
```

The gateway refuses to start when a label breaks one of these rules, and the startup error names the label:

- Names use only letters, digits, `.`, `_`, and `-`
- Names aren’t reserved. Compared in any letter case, the reserved names are everything that starts with `user.`, `enduser.`, or `identity.`, plus `service.name`, `service.version`, `claude.deployment_mode`, `host.arch`, `os.type`, `os.version`, and `wsl.version`
- Values are non-empty printable ASCII with no space and none of `, ; = \ " %`
- Values are at most 255 characters as the gateway counts them after percent-encoding, so `/`, `:`, and `@` each count as three
- Values are text, so quote a number, `true`, or `false`

You need Claude Code v2.1.281 or later on the gateway server to set `telemetry.resource_attributes`. An earlier gateway refuses to start when it finds the key. Upgrade every replica before you add the key, and remove the key before you roll back to an earlier version. Terminal sessions signed in through `/login` receive the labels as `OTEL_RESOURCE_ATTRIBUTES`, pushed with the other [telemetry variables](#telemetry). If you set `OTEL_RESOURCE_ATTRIBUTES` in a policy’s `env` block, terminal sessions that policy matches get that value instead of the labels. Claude Desktop receives the labels from the gateway alongside `user.email` and the other identity attributes. Claude Code also copies each label onto every metric data point, so you can filter metrics by it in a backend that doesn’t index resource attributes. To turn that copy off, see [Metrics cardinality control](monitoring-usage.md#metrics-cardinality-control).


[​](#group-changes-during-an-open-session)

Group changes during an open session

Terminal sessions put `user.groups` on the OTLP resource and again on each metric data point and event. If a developer’s groups change while a session is open, data points and events for usage after the next [silent refresh](#session) carry the new groups. The resource keeps the old groups until the developer restarts Claude Code, so group by the attribute on the data point or event. If you turn on `resource_to_telemetry_conversion` in the OpenTelemetry Collector’s Prometheus remote write exporter, the exporter replaces each data point’s `user.groups` with the resource’s, so every data point shows the old groups. To keep the data point’s value, delete `user.groups` from the resource ahead of that exporter. This OpenTelemetry Collector `resource` processor deletes the attribute in the pipelines that list it:

```python
processors:
  resource/drop-user-groups:
    attributes:
      - key: user.groups
        action: delete
```

After you add `resource/drop-user-groups` to the metrics pipeline’s `processors`, each series carries the `user_groups` label from its own data point.


[​](#export-directly-to-your-collector)

Export directly to your collector

To have sessions signed in through `/login` send telemetry straight to your collector instead of through the relay, set `OTEL_EXPORTER_OTLP_ENDPOINT` to the collector’s `https://` base URL in the `env` block of a [managed policy](#managed). Claude Code appends `/v1/metrics`, `/v1/logs`, or `/v1/traces` to the URL you set, such as `https://otel-collector.example.com:4318`, and exports each signal there over OTLP/HTTP. Requires Claude Code v2.1.265 or later on each developer’s machine. Earlier clients export through the relay. To authenticate to the collector, set `OTEL_EXPORTER_OTLP_HEADERS` in the same `env` block. Sessions never send the developer’s gateway session token to a collector named this way. When you add or change this endpoint in a policy, Claude Code asks each developer to approve it in the [security approval dialog](#managed) before applying it in an interactive session. Claude Code checks the endpoint before it exports a signal directly, and keeps that signal on the relay when a check fails. The checks include:

- The endpoint comes from the gateway itself. If you set the same variable in an MDM profile or a local `managed-settings.json`, exports stay on the relay.
- The URL uses `https://`, or `http://` to a loopback address
- The URL resolves to a path ending in `/v1/<signal>`, with no query or fragment. Claude Code builds that path itself from the generic variable. It uses a per-signal variable such as `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT` as written, so include the full path there.
- The URL isn’t the gateway’s own host. An endpoint addressed to the gateway keeps the relay path and its session token.
- Neither you nor the developer has configured [`otelHeadersHelper`](../02-Claude-Code-CLI/settings-reference.md#otelheadershelper) in any settings source. With a helper configured, every signal stays on the relay.

The endpoint you name changes only where exports go. You still choose which signals export at all with the `OTEL_*_EXPORTER` selectors. The endpoint alone doesn’t turn export on, so also set the variables that do, unless the gateway already pushes them:

- If the gateway already [pushes the telemetry variables](#telemetry), they cover enablement, selectors, and protocol, and your explicit endpoint overrides the pushed `<public_url>` value. Set an `OTEL_*_EXPORTER` selector to `otlp` yourself only for a signal that no `forward_to` destination enables.
- If it doesn’t, also set `CLAUDE_CODE_ENABLE_TELEMETRY=1`, the `OTEL_*_EXPORTER` selectors, and `OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf`.

When the developer signs out, or signs in to a different gateway, exports to the collector stop and Claude Code drops each remaining batch rather than sending it.


[​](#when-a-destination-fails)

When a destination fails

The gateway doesn’t buffer, retry, or store telemetry, so it drops an export that doesn’t reach a destination rather than delivering it late. Each destination succeeds or fails on its own, and the exporting client receives a success response either way, so a failed delivery appears only in the gateway’s log. After five consecutive failed deliveries to a destination, the gateway pauses forwarding to it in 30-second stretches, logging each pause, until a delivery succeeds. Any error response, timeout, or connection error counts as a failed delivery, except `400`, `413`, `415`, `422`, and `431`, which mean the collector refused that export’s payload as malformed or too large. A refused payload neither advances nor resets the failure count: the gateway keeps forwarding to the destination and logs a warning naming it and the status, on the destination’s first refusal and every hundredth after.


[​](#http-tuning)

HTTP tuning

Four optional top-level blocks, `access_control`, `limits`, `timeouts`, and `rate_limits`, tune the HTTP surface. The defaults suit most deployments.

| Block            | Key                                            | Default  | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|------------------|------------------------------------------------|----------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `access_control` | `allow_cidrs` / `deny_cidrs`                   | empty    | Inbound IP allow/deny by client address, after `trusted_proxies` resolution. `deny_cidrs` is checked first; a client it matches is rejected even if `allow_cidrs` also matches. If `allow_cidrs` is non-empty the gateway is default-deny. `/healthz` and `/readyz` are exempt from `allow_cidrs`. When a trusted proxy sends an `X-Forwarded-For` entry that isn’t an IP address, the real client is unknown and the gateway logs a warning once naming what to check. Where either list applies to the request, it refuses it with `403` and audit reason `xff_unparseable`. Where neither does, it serves the request and uses the proxy’s own address as the client IP for per-IP rate limits and audit. |
| `limits`         | `max_request_bytes`                            | 32 MiB   | Max inbound request body; oversize requests get `413` before the body is buffered. Raise for large file or image requests.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `limits`         | `max_request_header_bytes`                     | unset    | Lowers the gateway’s 256 KiB limit on a request’s total headers. A request over the limit returns `431`, and a value above 256 KiB has no effect. If developers get `431` after signing in, see [Request headers too large after sign-in](claude-apps-gateway-deploy.md#request-headers-too-large-after-sign-in).                                                                                                                                                                                                                                                                                                                                                                                      |
| `limits`         | `max_url_length`                               | unset    | When set, an over-long URL returns `414`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `timeouts`       | `upstream_ttfb_ms`                             | 120000   | Max wait for the upstream’s response headers (time to first byte). The response body then streams with no wall-clock cap. Applies to the direct Anthropic upstream path; on every other provider the gateway waits up to one hour for the response to start.                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `rate_limits`    | `device_authorization.max` / `.window_seconds` | 30 / 600 | Per-IP rate limit on the unauthenticated device-authorization endpoint. Raise for a large org behind a shared egress IP or NAT. [Large rollouts](claude-apps-gateway-deploy.md#large-rollouts) shows how to size it. These limits apply only to the device-grant sign-in flow, not to `/v1/messages` inference. See [User-code brute-force resistance](claude-apps-gateway-deploy.md#user-code-brute-force-resistance).                                                                                                                                                                                                                                                                          |
| `rate_limits`    | `device_verify.max` / `.window_seconds`        | 10 / 600 | Per-IP rate limit on `user_code` submissions at `/device`. It is what stops someone from guessing another developer’s code. [Large rollouts](claude-apps-gateway-deploy.md#large-rollouts) shows how far to raise it.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |

If you leave both `access_control` lists empty, which is the default, the gateway serves any client address, so only your network restricts who can reach it. That matters because a gateway can push [managed settings](#managed) that run commands on developer machines. While `allow_cidrs` is empty, the gateway warns in two places, without changing how it answers any request:

- **At boot**: a warning in the operational log recommends allowing only the private ranges `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `100.64.0.0/10`, `127.0.0.0/8`, `::1/128`, and `fc00::/7`, plus any other internal ranges your developers connect from. If you bind the gateway to a loopback address and set neither `trusted_proxies` nor `public_url`, as in local development, the warning doesn’t appear.
- **At runtime**: the first time a request arrives from an address outside those private ranges, the gateway logs a warning and emits an [`access.public_client` audit event](claude-apps-gateway-deploy.md#logs) carrying the client IP. Both fire once per process. Link-local addresses, `169.254.0.0/16` and `fe80::/10`, don’t count as public. The gateway answers `/healthz` and `/readyz` before this check runs, so health probes from public ranges don’t trigger it.

Both signals use the client address as the gateway resolves it. If a load balancer, port-forward, or tunnel relays traffic and isn’t listed in `listen.trusted_proxies`, the gateway sees the relay’s address, which is usually private, so neither the runtime warning nor a private allow list catches traffic relayed through it. Behind such a front end, set [`listen.trusted_proxies`](#listen) first so the gateway sees real client addresses, and keep the gateway and everything in front of it unreachable from the public internet regardless.


[​](#load_test_mode)

`load_test_mode`

The `load_test_mode` block lets you load test a gateway without calling a model provider. While it’s on, the gateway builds and signs each provider request as usual, discards it instead of sending it, and streams a canned reply back through its normal response path. The reply is filler text that begins with a sentence saying it is canned. Requires Claude Code v2.1.282 or later on the gateway server. An earlier gateway refuses to start when it finds the key. Upgrade every replica before you add the block, and remove the block before you roll back. The example below turns the mode on with the defaults, a reply of roughly 750 tokens of text streamed over about 10 seconds:

```python
load_test_mode:
  enabled: true
  reply_tokens: 750     # roughly how many tokens of text each canned reply carries
  reply_seconds: 9.5    # how long a streamed reply takes
```

| Field           | Required | Description                                                                                                                                                     |
|-----------------|----------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `enabled`       | Yes      | `true` turns the mode on. `false` keeps your numbers in the file with the mode off. The gateway refuses to start if the block is present without it.            |
| `reply_tokens`  | No       | Default `750`. Roughly how many tokens of text each canned reply carries, a whole number from 1 to 100000.                                                      |
| `reply_seconds` | No       | Default `9.5`. How long a streamed reply takes, from 0 to 600. `0` sends the whole reply at once. A reply to a non-streaming request always comes back at once. |

A load test in this mode covers the gateway, your Postgres, and everything in front of the gateway. It doesn’t cover the provider’s limits, speed, or network path. No model request is sent to the provider, so a replica’s CPU per request is an estimate and reads lower than production, which also encrypts its traffic to the provider. Confirm a replica count with a small pilot against the real provider. Before v2.1.283, the estimate reads much lower. While the mode is on, a request can carry an `x-load-test-user` header holding a whole number of up to seven digits. The gateway counts each number as a separate developer, with the email and groups of the developer whose token came with the request. Give the load-test deployment its own empty database, because the gateway refuses to start with the mode on against a database in which any developer has already spent anything.

Never turn this on for a gateway that developers use. Every request gets the canned reply and no model is called. The gateway logs a `load_test_mode is on` warning at boot and marks each `inference` [audit event](claude-apps-gateway-deploy.md#logs) with `load_test: true` while the mode is on.


[​](#complete-example)

Complete example

This full reference config exercises every core section; the [HTTP tuning blocks](#http-tuning) keep their defaults. Copy it, delete what you don’t need, and fill in your values. The config in the [Quickstart](claude-apps-gateway.md#quickstart) is a minimal version of this.

gateway.yaml

```python
# Run with:
#   claude gateway --config gateway.yaml
# Operational log verbosity is controlled by the CLAUDE_GATEWAY_LOG_LEVEL
# environment variable (debug | info | warn | error; default info). debug
# also logs the claim names in each id_token, for groups_claim diagnosis.
# It does not affect audit events, which are always emitted.

listen:
  host: 0.0.0.0
  port: 8080
  public_url: https://claude-gateway.internal.example.com
  # Omit the tls block when running behind a TLS-terminating ingress.
  # tls:
  #   cert: /certs/gateway.crt
  #   key: /certs/gateway.key
  # trusted_proxies:
  #   - 10.0.0.0/8

oidc:
  issuer: https://example.okta.com
  client_id: 0oa1example2
  client_secret: ${OIDC_CLIENT_SECRET}
  allowed_email_domains:
    - example.com
  # Required when the issuer is the Okta org server, whose id_tokens
  # can omit email and groups; the gateway fills them from /userinfo.
  userinfo_fallback: true
  # allowed_groups: [claude-code-users]
  # Okta emits groups only when the `groups` scope is requested and the
  # app's groups claim filter allows them. The contractors policy below
  # matches on groups, so the scope is requested here.
  scopes: [openid, profile, email, offline_access, groups]
  # extra_auth_params: { access_type: offline, prompt: consent }  # Google
  # groups_claim: groups          # Entra app roles: use `roles`
  # email_claim: email

session:
  jwt_secret: ${GATEWAY_JWT_SECRET}   # openssl rand -base64 32
  # ttl_hours: 1

store:
  postgres_url: ${GATEWAY_POSTGRES_URL}
  # max_connections: 5
  # connect_timeout_seconds: 5
  # readiness_grace_seconds: 300   # keep passing the readiness check through a database failover

# Enables /v1/organizations/spend_limits (mirrors the Anthropic Admin API)
# and per-developer spend enforcement on /v1/messages. Omit to disable.
# Caps themselves are set via the admin API, not here.
# admin:
#   write_keys:
#     - { id: terraform, key: "${GATEWAY_ADMIN_WRITE_KEY_TF}" }
#   read_keys:
#     - { id: reporting, key: "${GATEWAY_ADMIN_READ_KEY}" }
#   admin_groups: [platform-finops]
#   blocked_message: request an increase at https://go.example.com/claude-limits
#   # audit_retention_days: 365
#   # spend_retention_months: 13
#   # identity_retention_days: 90
#   # group_limit_mode: min

# enforcement:
#   fail_closed_on_error: false

# Load test this deployment without calling a model provider. Never on a
# gateway that developers use: every request gets a canned reply.
# load_test_mode:
#   enabled: true
#   # reply_tokens: 750
#   # reply_seconds: 9.5

# Meter at contracted rates instead of USD list price. Requires admin: or a
# managed: policy. With managed:, the same rates also go to signed-in clients.
# Rates below are placeholders, not real contract prices.
# pricing:
#   multiplier: 0.85
#   overrides:
#     - { upstream: anthropic, model: claude-sonnet-4-6, input: 3.30, output: 16.50, cache_read: 0.33, cache_write: 4.125 }

upstreams:
  - provider: anthropic
    auth:
      api_key: ${ANTHROPIC_API_KEY}

  # - provider: bedrock
  #   region: us-east-1
  #   auth: {}

  # - provider: mantle
  #   region: us-east-1
  #   models: [claude-opus-4-8, claude-opus-4-7, claude-haiku-4-5]
  #   auth: {}

  # - provider: anthropicAws
  #   region: us-east-1
  #   workspace_id: wrkspc_...
  #   auth:
  #     api_key: ${ANTHROPIC_AWS_API_KEY}

  # - provider: vertex
  #   region: us-east5
  #   project_id: example-prod
  #   auth: {}

  # - provider: foundry
  #   resource: example-foundry
  #   auth: { use_azure_ad: true }

auto_include_builtin_models: true
models:
  - id: claude-opus-4-8
    label: Claude Opus 4.8
    upstream_model:
      anthropic: claude-opus-4-8
      # bedrock: us.anthropic.claude-opus-4-8
      # mantle: anthropic.claude-opus-4-8
      # anthropicAws: claude-opus-4-8
      # vertex: claude-opus-4-8
      # foundry: <your-opus-deployment-name>
  - id: claude-sonnet-4-6
    label: Claude Sonnet 4.6
    upstream_model:
      anthropic: claude-sonnet-4-6
  - id: claude-haiku-4-5
    label: Claude Haiku 4.5
    upstream_model:
      anthropic: claude-haiku-4-5

managed:
  policies:
    - match: { groups: [contractors] }
      cli:
        availableModels: [claude-haiku-4-5]
        # allow auto-approves these tools; it does not block the rest.
        # Add deny rules to restrict tools.
        permissions: { allow: [Read, Grep] }
    - match: {}
      cli:
        availableModels: [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5]
        # Constrain the Default picker option to each policy's availableModels
        # instead of the built-in default, so no role gets a 400 on Default.
        # The contractors policy inherits this key.
        enforceAvailableModels: true
        permissions:
          allow: [Read, Grep, Bash, Edit]
          deny: ["WebFetch"]
        env: { HTTP_PROXY: http://proxy.example.com:8080 }

telemetry:
  forward_to:
    - url: https://otel.internal.example.com:4318
      headers:
        Authorization: Bearer ${OTEL_TOKEN}
```


[​](#client-side-managed-settings)

Client-side managed settings

Everything above configures the gateway server. You point developer machines at the gateway separately, on each device, through Claude Code’s [managed settings](managed-settings.md). The gateway can’t push the login keys itself, because they’re what tell the client where the gateway is. For the CLI, set these keys in the per-OS `managed-settings.json`. The two login keys route each developer’s `/login` to your gateway:

```python
{
  "forceLoginMethod": "gateway",
  "forceLoginGatewayUrl": "https://claude-gateway.internal.example.com",
  "parentSettingsBehavior": "merge"
}
```

`parentSettingsBehavior: "merge"` keeps Claude Desktop’s delivery of the egress allowlist to its embedded Claude Code sessions working; [Deliver policy to Claude Desktop sessions](claude-apps-gateway.md#deliver-policy-to-claude-desktop-sessions) explains the mechanism and where the opt-in must sit. To stop developers from bypassing the gateway with a cloud provider variable or an `ANTHROPIC_BASE_URL` of their own, add `"allowedProviders": ["gateway"]` to the same file. Claude Code then refuses every session on the machine that isn’t set up for a Cloud gateway, and admits a gateway only when it is the one `forceLoginGatewayUrl` names or one whose URL the file’s `env` block sets as `ANTHROPIC_BASE_URL`. `claude gateway` refuses to run on a machine that sets the list, so keep the key off the gateway host. See the [`allowedProviders`](../02-Claude-Code-CLI/settings-reference.md#allowedproviders) entry in the settings reference. Requires Claude Code v2.1.285 or later. Deploy the `managed-settings.json` file to each device, typically via your MDM platform. The file path differs by platform. See [where each mechanism stores the policy](managed-settings.md#where-each-mechanism-stores-the-policy). By default, a registry policy on Windows or a managed-preferences plist on macOS replaces the `managed-settings.json` file rather than merging with it, apart from the [exception keys and cross-source checks above](#precedence-with-other-managed-sources). All three keys in this snippet follow the highest-priority-source rule, so fleets that deliver policy through Group Policy or configuration profiles must put all three in that mechanism instead. For Claude Desktop, set the `bootstrapUrl` key in Claude Desktop’s own [managed configuration](https://claude.com/docs/third-party/claude-desktop/configuration) to `<listen.public_url>/user/bootstrap`. The sign-in flow and per-group policy then match the CLI’s once a policy opts in server-side with a `desktop` key; without the opt-in, `/user/bootstrap` returns 404. See [Claude Desktop overlay](#claude-desktop-overlay) for the server-side half. Claude Code honors [`forceLoginGatewayUrl`](../02-Claude-Code-CLI/settings-reference.md#forcelogingatewayurl), [`gatewayInternalNetworks`](../02-Claude-Code-CLI/settings-reference.md#gatewayinternalnetworks), and the `"gateway"` value of [`forceLoginMethod`](../02-Claude-Code-CLI/settings-reference.md#forceloginmethod) from a managed source on the machine: `managed-settings.json`, the macOS plist or Windows HKLM registry, or a policy helper. Setting them in the gateway payload doesn’t configure the gateway sign-in. For a developer’s own `~/.claude/settings.json`, see [Set the gateway URL in user settings](claude-apps-gateway.md#set-the-gateway-url-in-user-settings). Leave `forceLoginMethod` and `forceLoginOrgUUID` out of the payload. Claude Code still reads both keys from the payload for its startup credential check, so a developer who keeps an Anthropic-issued credential on the machine gets the startup exit described under [Administrator policy requires a Cloud gateway sign-in](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in) even after they sign in.


[​](#related)

Related

- [Claude apps gateway overview](claude-apps-gateway.md): quickstart and developer connection
- [Deployment guide](claude-apps-gateway-deploy.md): IdP setup, container image, Kubernetes and Cloud Run, and operations
- [Spend limits](claude-apps-gateway-spend-limits.md): per-developer caps and the Admin API

---

## Claude apps gateway deployment and operations - Claude Code Docs

Source: https://code.claude.com/docs/en/claude-apps-gateway-deploy
Path: 13-Enterprise-Admin/claude-apps-gateway-deploy.md

## On this page

- [Identity provider setup](#identity-provider-setup)
- [Deployment](#deployment)
  - [Choose an address for the gateway](#choose-an-address-for-the-gateway)
  - [Container image](#container-image)
  - [Kubernetes](#kubernetes)
  - [Cloud Run](#cloud-run)
  - [Push the gateway URL to developer machines](#push-the-gateway-url-to-developer-machines)
  - [Large rollouts](#large-rollouts)
- [Operations](#operations)
  - [Logs](#logs)
  - [Health](#health)
  - [Concurrent upstream requests](#concurrent-upstream-requests)
  - [Keep API_TIMEOUT_MS above the gateway’s time to first byte](#keep-api_timeout_ms-above-the-gateways-time-to-first-byte)
  - [Retry an unanswered request sooner with the first-byte deadline](#retry-an-unanswered-request-sooner-with-the-first-byte-deadline)
  - [Outage behavior](#outage-behavior)
  - [Readiness grace period](#readiness-grace-period)
  - [JWT secret rotation](#jwt-secret-rotation)
  - [Postgres](#postgres)
  - [Size the connection pool](#size-the-connection-pool)
  - [Upgrades](#upgrades)
- [Security](#security)
  - [Data flow](#data-flow)
  - [Threat model summary](#threat-model-summary)
  - [User-code brute-force resistance](#user-code-brute-force-resistance)
  - [Compliance posture](#compliance-posture)
  - [Plugin marketplace requests](#plugin-marketplace-requests)
- [Troubleshooting](#troubleshooting)
  - [Request headers too large after sign-in](#request-headers-too-large-after-sign-in)
  - [Long thinking ends in proxy timeouts](#long-thinking-ends-in-proxy-timeouts)
  - [Confirm the cause](#confirm-the-cause)
  - [Pick a value](#pick-a-value)
  - [Deliver the value in a policy](#deliver-the-value-in-a-policy)
  - [Long thinking on an Anthropic API upstream](#long-thinking-on-an-anthropic-api-upstream)
- [Related](#related)

Claude apps gateway

# Claude apps gateway deployment and operations

Copy pageCopy page

Register the gateway with your IdP, build the container, deploy on Kubernetes or Cloud Run, and operate it: health checks, secret rotation, upgrades, and security.

Copy pageCopy page

**Plan your gateway’s network first.** At sign-in, Claude Code refuses a Claude apps gateway whose hostname resolves to a public IP address, even one the internet can’t reach.A Claude apps gateway can push settings to users’ machines, including hooks that run shell commands. The check helps keep users from accidentally signing in to a malicious gateway on the public internet. Keep your own gateway off the internet too.Choose the gateway’s address before you choose where it runs. Usually that’s a private address that users reach on your internal network or over a VPN. If your internal network uses public IPv4 ranges, you can list one range that holds both the gateway and your users’ machines. Claude Code takes that match as a sign that the gateway is on your internal network. See [Choose an address for the gateway](#choose-an-address-for-the-gateway). If neither fits your network, contact your Anthropic account team.

This page covers the operational side of running [Claude apps gateway](claude-apps-gateway.md): registering an OAuth client in your identity provider (IdP), deploying the gateway as a container, and running it day-to-day. For every option in the `gateway.yaml` file the gateway reads at boot, see the [Configuration reference](claude-apps-gateway-config.md). A production deployment follows four steps in order, and the sections below match them. The first two are where you make choices; the second two are reference material to consult once it’s running.

1.  [Set up your identity provider](#identity-provider-setup): register the OAuth client and check the per-IdP notes for Okta, Entra, and Google
2.  [Deploy the gateway](#deployment): build a pinned container image and run it on Kubernetes, Cloud Run, or your own platform. This section also covers cost, bypass, multiple-gateway, and serverless decisions
3.  [Set up operations](#operations): logs, health probes, outage behavior, secret rotation, and upgrades. Reference for when you’re setting up monitoring and runbooks
4.  [Review the security posture](#security): what data flows where, the threat model, and compliance answers. Reference for a security review

If a sign-in or boot fails along the way, go straight to [Troubleshooting](#troubleshooting), which is keyed on the error you see.


[​](#identity-provider-setup)

Identity provider setup

Register a confidential OAuth/OpenID Connect (OIDC) web application with a single redirect URI, `https://<gateway>/oauth/callback`, and assign it to the users or groups who should have gateway access. The gateway authenticates to the IdP with the registration’s client secret, or with a certificate you upload to the registration if your IdP uses [certificate credentials](claude-apps-gateway-config.md#certificate-client-authentication) instead. Any OIDC-compliant IdP works: Okta, Microsoft Entra ID, Google Workspace, Keycloak, Dex, PingFederate, and others. The IdP must meet three requirements:

- Serves `/.well-known/openid-configuration`, over HTTPS in production; the gateway accepts an [`http://` issuer](claude-apps-gateway-config.md#oidc), and a loopback issuer additionally requires `CLAUDE_GATEWAY_ALLOW_LOOPBACK=1`
- Supports the authorization-code flow. PKCE (Proof Key for Code Exchange) is on by default; disable it with `oidc.use_pkce: false` for IdPs that don’t support it
- Returns `email` and optionally `groups` in the id_token, or serves them from the userinfo endpoint with `oidc.userinfo_fallback: true`

For private PKI, set `oidc.ca_cert_pem`. A few providers handle email and group claims differently:

- **Okta**: the org authorization server at `https://example.okta.com` returns a thin id_token that omits `email` and `groups`, so set `oidc.userinfo_fallback: true` whenever you use it as `issuer`. A custom authorization server such as `https://example.okta.com/oauth2/default` that includes `email` and optionally `groups` in the id_token emits them directly and needs no fallback. Okta emits `groups` only when the `groups` scope is requested in `oidc.scopes` and the app’s groups claim filter allows it; `userinfo_fallback` can’t fill a claim the IdP wasn’t asked for.
- **Microsoft Entra ID**: `issuer` = `https://login.microsoftonline.com/<tenant-id>/v2.0`. Entra emits group Object IDs rather than names, so use the GUIDs in `managed.policies.match.groups`, or use App Roles for human-readable names. If your tenant emits roles under `roles` instead of `groups`, set `oidc.groups_claim: roles`.
- **Google Workspace**: `issuer` = `https://accounts.google.com`. Google’s id_token doesn’t carry groups. To use group-based `allowed_groups` or `managed.policies` with Google as the IdP, configure [`oidc.google_groups`](claude-apps-gateway-config.md#oidc), which looks up each user’s groups through the Admin SDK Directory API using a service account with domain-wide delegation. Without it, use `oidc.allowed_email_domains` for membership gating and `managed.policies.match.email_domain` for policy assignment. Google also ignores the standard `offline_access` scope. For refresh tokens, set `oidc.scopes: [openid, profile, email]` and `oidc.extra_auth_params: { access_type: offline, prompt: consent }`.

Refresh tokens let the gateway renew a developer’s session silently, without sending the developer back to the browser. They also drive deprovisioning, because when the IdP disables a user, the next refresh fails and the session ends within `ttl_hours`. The gateway requests `offline_access` by default to get a refresh token. If your IdP requires explicit consent for offline access, configure the OAuth client to allow it.If your IdP can’t issue refresh tokens at all, the gateway still works, but there is no silent renewal, so developers re-run the browser login when their session expires. To keep that from happening every hour, raise [`session.ttl_hours`](claude-apps-gateway-config.md#session) to `8` or `12`. The tradeoff is deprovisioning latency, because without refresh tokens a disabled user keeps access until the longer TTL elapses.


[​](#deployment)

Deployment

The gateway is a single stateless Linux binary that coordinates through Postgres, so deploy it the way you deploy any other stateless service in your environment. Keep it inside your network, where your developers can reach it over HTTPS and it can reach your IdP, and treat it like any service holding a production credential. A few decisions shape the deployment beyond where it runs:

- **Cost**: no separate license or per-seat fee. The gateway is part of the `claude` binary, so you pay for inference through your existing commitment, plus the compute it runs on.
- **Bypass**: the gateway doesn’t enforce that the only route to a model goes through it. A developer with their own credential can still call the provider directly, so closing that path is a network policy decision, for example blocking egress to `api.anthropic.com` except from the gateway. Blocking that egress also breaks the [WebFetch domain safety check](data-usage.md#webfetch-domain-safety-check), which calls `api.anthropic.com` from each developer’s machine. Set `skipWebFetchPreflight: true` in the managed policy to disable it.
- **Multiple gateways**: each is a separate deployment with its own config, and the CLI stores trust and credentials per gateway hostname, so teams can use different gateways without conflict. To serve multiple OIDC issuers, run separate instances.
- **Serverless**: Cloud Run works if you set `min-instances: 1` to avoid cold OIDC discovery. Lambda and Cloud Functions don’t work, because the gateway is a long-running HTTP server.

Every production topology here puts an L7 proxy, such as an Ingress, Cloud Run’s front end, or an ALB, in front of plain-HTTP replicas. Set [`listen.trusted_proxies`](claude-apps-gateway-config.md#listen) to the proxy’s source ranges so the gateway reads client IPs from `X-Forwarded-For`. The gateway honors the header only when the TCP peer is trusted. The [Google Cloud](claude-apps-gateway-on-gcp.md) and [AWS](claude-apps-gateway-on-aws.md) worked examples have concrete values per topology. Without trusted proxies, every request appears to come from the proxy’s IP, which collapses per-IP rate limits into one shared bucket and records the proxy’s IP in audit events. Don’t redirect requests to the gateway’s device-authorization and token endpoints, for example with an HTTP-to-HTTPS or host-canonicalization rewrite at the ingress. Claude Code doesn’t follow redirects on those requests, so an ingress rule that redirects them breaks sign-in and token refresh. Give the proxy any idle timeout longer than the gateway’s keepalive interval, which depends on the upstream:

- On every upstream except `provider: anthropic`, the gateway writes an SSE `ping` once a stream has been silent for about 15 seconds.
- On `provider: anthropic`, the gateway passes the response through unchanged, including the Anthropic API’s own pings.

A default such as the ALB’s 60 seconds is enough to keep a quiet stream open. The [AWS worked example](claude-apps-gateway-on-aws.md#troubleshooting) raises it to an hour anyway, and its troubleshooting row covers gateways older than v2.1.229, which sent nothing during quiet periods on the upstreams that now get pings.


[​](#choose-an-address-for-the-gateway)

Choose an address for the gateway

Claude Code accepts a gateway’s address in one of two ways:

- **Private address**: put the gateway behind an internal load balancer or VPN, with a hostname that resolves only to private addresses, such as RFC 1918 or CGNAT `100.64.0.0/10`. Users’ machines can be on any address. The [private-network prerequisite](claude-apps-gateway.md#prerequisites) lists the accepted ranges.
- **Declared block**: if your internal network uses public IPv4 space your organization owns, list the block in the `gatewayInternalNetworks` managed setting. The gateway and the user’s machine must both be in that block. See [Allow a gateway on public address space you own](claude-apps-gateway.md#allow-a-gateway-on-public-address-space-you-own).

If no single block contains both, give the gateway a private address instead.


[​](#container-image)

Container image

Build your own image around the native `claude` binary from the standard Claude Code release:

1.  Download the Linux build for your image architecture from a pinned release; see [Install a specific version](../01-Getting-Started/setup.md#install-a-specific-version) for the download URL.
2.  Verify it against the release’s GPG-signed `manifest.json` as described in [Binary integrity and code signing](../01-Getting-Started/setup.md#binary-integrity-and-code-signing).
3.  Copy it into the build context.

Mirror the release into your internal registry if your builds can’t reach the release host, and pin the version your fleet runs. Beyond the binary, the image needs:

- **A glibc-based image**: the glibc build’s only dynamic dependencies are glibc libraries. Musl-based images need the `linux-x64-musl` or `linux-arm64-musl` build plus additional packages; see [Alpine Linux setup](../01-Getting-Started/setup.md#alpine-linux-and-musl-based-distributions).
- **A writable state directory**: the gateway runs as any user, but minimal images have no writable home. Set `CLAUDE_CONFIG_DIR` to a writable path such as `/tmp/.claude`.
- **The container command**: `claude gateway --config /etc/claude/gateway.yaml`, with the config file mounted read-only and secrets supplied as environment variables; the gateway listens on `listen.port`, default `8080`.


[​](#kubernetes)

Kubernetes

Run the gateway as a Deployment, like any stateless service:

- Mount the config from a ConfigMap and secrets from a Secret; reference secrets in the YAML via `${file:/path/to/secret}` or as environment variables
- Terminate TLS at the Ingress and set `listen.public_url` to the Ingress hostname
- Point the readiness probe at `GET /readyz` and the liveness probe at `GET /healthz`

For a complete worked example on AWS, covering ECS Fargate or EKS, Amazon RDS, and AWS Secrets Manager, see [Deploy on AWS](claude-apps-gateway-on-aws.md). Prefer the platform’s workload identity over static keys; the [`upstreams` reference](claude-apps-gateway-config.md#upstreams) has per-platform setup details. For a cross-cloud pairing, such as an Amazon Bedrock upstream on GKE, set explicit credentials in the upstream’s `auth` block instead.


[​](#cloud-run)

Cloud Run

Configure the service as follows:

- Leave `listen.port` at its default of `8080`, which matches Cloud Run’s default `PORT`, or set `port: ${PORT}`
- Set `public_url` to the externally reachable origin. For production this is normally an internal load balancer’s hostname, because `/login` [rejects public addresses](claude-apps-gateway.md#prerequisites) and the `*.run.app` URL resolves to one, so the Cloud Run URL alone works only for a `curl` or browser smoke test. The exception is a network where `*.run.app` resolves privately through Private Service Connect and a Cloud DNS private zone; in that topology the Cloud Run URL is a valid `public_url`. The [Google Cloud worked example](claude-apps-gateway-on-gcp.md#deploy-the-gateway) covers both.
- Mount the config as a secret volume
- Set `min-instances: 1` to avoid a cold OIDC discovery on first request

For a complete worked example on Google Cloud, covering Cloud Run or GKE, Cloud SQL, and Secret Manager, see [Deploy on Google Cloud](claude-apps-gateway-on-gcp.md).


[​](#push-the-gateway-url-to-developer-machines)

Push the gateway URL to developer machines

Once the gateway is serving, push `forceLoginMethod`, `forceLoginGatewayUrl`, and `parentSettingsBehavior: "merge"` to each developer’s machine through managed settings, via MDM or by writing the per-OS `managed-settings.json` directly. Once you deploy the keys, Claude Code stops using a leftover API key or claude.ai login on the machine, so plan the push together with your sign-in instructions. [Administrator policy requires a Cloud gateway sign-in](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in) describes the messages developers see. See [where each mechanism stores the policy](managed-settings.md#where-each-mechanism-stores-the-policy) for the file paths, and [Client-side managed settings](claude-apps-gateway-config.md#client-side-managed-settings) for the Claude Desktop `bootstrapUrl` equivalent.


[​](#large-rollouts)

Large rollouts

Sign-in is rate limited per client IP address, and the defaults suit a small team. Each address gets 30 sign-in starts and 10 code submissions every 10 minutes. A rollout to thousands of developers can reach those limits on the first morning, for one of two reasons:

- **The gateway can’t see past your load balancer.** Without [`listen.trusted_proxies`](claude-apps-gateway-config.md#listen), every developer appears to come from the load balancer’s address and shares one limit. Set it before anything else. The gateway logs a warning the first time it ignores an `X-Forwarded-For` header.
- **Many developers share a few NAT or VPN egress addresses.** They share those addresses’ limits even when `trusted_proxies` is right. Raise [`rate_limits`](claude-apps-gateway-config.md#http-tuning) to fit.

To size `max`, divide the developers by the egress addresses they share. Estimate how many of those sign in within one `window_seconds` period, which is 10 minutes by default. Then double it to cover retries and developers who sign in to both Claude Code and Claude Desktop. For example, 10,000 developers behind 4 egress addresses sign in evenly over an hour. That is 2,500 developers per address and about 420 of them in each 10 minutes, which you double and round up to 1,000. The example below sets both limits to 1,000:

```python
rate_limits:
  device_authorization: { max: 1000, window_seconds: 600 }
  device_verify: { max: 1000, window_seconds: 600 }
```

`device_verify` is what stops someone from guessing another developer’s sign-in code, so raise it only as far as your estimate needs. Even at these limits, a code is 8 characters from a 20-character alphabet and expires after 10 minutes, so guessing stays impractical; see [User-code brute-force resistance](#user-code-brute-force-resistance). When your IdP issues refresh tokens, Claude Code renews sessions silently, so you can put the limit back after the rollout. Without refresh tokens, developers sign in again every [`session.ttl_hours`](claude-apps-gateway-config.md#session). Size both limits for that steady rate too and leave them raised. When a limit is reached, Claude Code v2.1.274 or later shows `The gateway is limiting sign-in attempts right now`. A gateway on v2.1.274 or later shows `Too many attempts came from your network address` on the verification page, with the settings to check. It also writes a `sign-in refused` log line that names the setting to change.


[​](#operations)

Operations

Once the gateway is serving traffic, day-to-day operation is reading its logs, probing its health, and rotating its secrets on your schedule. The subsections cover each, plus what Postgres holds and how upgrades and rollbacks behave.


[​](#logs)

Logs

The gateway writes two streams to stderr, both JSON-friendly:

- **Audit events**: single-line JSON per security-relevant event. Pipe stderr to your log aggregator. The events emitted include `config.load`, `session.mint`, `session.refresh`, `device.authorize`, `device.verify`, `device.callback`, `auth.denied`, `access.denied`, `access.public_client`, `inference`, `managed.serve`, `desktop_bootstrap.serve`, `desktop_bootstrap.denied`, `spend.blocked`, `admin.denied`, `admin.limit.upsert`, and `admin.limit.delete`. Fields vary by event:
  - Successful mint and refresh events carry `sub`, `email`, `client_ip`, and the result
  - `auth.denied` and `access.denied` carry the reason and client IP, plus the request path for `auth.denied`, since no user identity exists at those denials. Two `access.denied` reasons change what the event carries:
    - `xff_unparseable`: the event also carries the `X-Forwarded-For` entry that couldn’t be read
    - `client_ip_unknown`: the event carries no client IP, because the connection had no peer address while an `access_control` list was set
  - `access.public_client` carries the client IP of the first request per process to arrive from a public address while `access_control.allow_cidrs` is empty. The gateway serves the request as usual; the event signals that the gateway may be reachable from the public internet. See the [`access_control` reference](claude-apps-gateway-config.md#http-tuning) for what counts as public and for the recommended allow list.
  - `inference` records which upstream served the request and the response status
  - `desktop_bootstrap.denied` records a rejected Claude Desktop bootstrap fetch with the reason (`not_configured`, `policy_not_opted_in`, or `no_policy_matched`) and the user’s identity
  - `admin.denied` records a rejected admin-API auth attempt with the client IP, method, path, and a reason, without the presented key material: `invalid_key` when an `x-api-key` was presented but matched no configured key, `bearer_rejected` when only an `Authorization` header was presented and it didn’t verify as a gateway session in `admin.admin_groups`, or `no_credentials` when neither header was presented
- **Operational logs**: human-readable `[gateway]`-prefixed lines for boot, warnings, and upstream errors. The `CLAUDE_GATEWAY_LOG_LEVEL` environment variable controls verbosity and accepts `debug`, `info`, `warn`, or `error`, with `info` as the default. At `debug`, each sign-in and refresh also logs the names, not the values, of the claims in the id_token, plus the names of the userinfo claims when `userinfo_fallback` supplied any, so you can diagnose `email_claim` and `groups_claim` settings without logging PII. It doesn’t affect audit events, which are always emitted.


[​](#health)

Health

The gateway serves `GET /healthz` as a liveness probe and `GET /readyz` as a readiness probe. `/readyz` verifies the store is reachable. If you set [`store.readiness_grace_seconds`](claude-apps-gateway-config.md#store), `/readyz` keeps reporting ready for up to that many seconds after the store stops answering. Both endpoints are exempt from `access_control.allow_cidrs`, so probes keep working on a locked-down listener. The OAuth discovery document at `/.well-known/oauth-authorization-server` also returns `200` only after config load, OIDC discovery, upstream client construction, and Postgres migration all succeed, so it doubles as an end-to-end boot check.


[​](#concurrent-upstream-requests)

Concurrent upstream requests

By default, each gateway replica sends at most 256 requests upstream at the same time. A streaming response counts against the limit until the stream ends. A request that arrives while a replica is at the limit waits inside the gateway for a free slot. The developer sees a response that is slow to start or appears to hang. On a `provider: anthropic` upstream, a request that waits longer than [`timeouts.upstream_ttfb_ms`](claude-apps-gateway-config.md#http-tuning) gives up on that upstream, and fails with a 502 when no later upstream serves it. The startup log line that contains `upstream requests:` shows the limit in effect. While a replica has more requests open than the limit, it also logs a warning that contains `client requests are open`, at most once a minute. To serve more requests at once, you have two options:

- Add replicas.
- Raise the limit on each replica. Set the `BUN_CONFIG_MAX_HTTP_REQUESTS` environment variable on the gateway container to a whole number from 1 to 65535, then restart the container.

A replica fills its limit at a request rate of about the limit divided by the average number of seconds a request stays open. For example, if requests stay open for 10 seconds on average, a replica at the default limit of 256 fills it at about 26 requests a second. If you autoscale on CPU, a replica at the limit queues requests without triggering a scale-out, so set the target below the CPU level your replicas show when they log the `client requests are open` warning.

Every open request holds memory in the gateway process while it streams and while it waits for a slot. If you keep the limit at 256, memory on an overloaded replica still grows, because waiting requests keep their request bodies. Size the container’s memory for the number of requests open at peak, and watch memory when you change the limit. A replica that runs out of memory is killed and drops every stream it holds.


[​](#keep-api_timeout_ms-above-the-gateways-time-to-first-byte)

Keep `API_TIMEOUT_MS` above the gateway’s time to first byte

If you lower [`API_TIMEOUT_MS`](network-config.md#streaming-idle-watchdogs) to retry an unanswered request sooner, keep it above the time the gateway takes to send the first bytes of a streaming response. A request that waits longer times out, and so does each retry that waits as long, until the turn ends with `Request timed out` unless the [first-byte deadline](#retry-an-unanswered-request-sooner-with-the-first-byte-deadline) ends it.

- **`provider: anthropic`**: set `API_TIMEOUT_MS` above the longest time your upstream takes to send its first event.
- **Every other upstream**: set it more than 20 seconds above the longest time your upstream takes to return response headers. The gateway sends its first [keepalive `ping`](#deployment) about 15 seconds after those headers, unless the upstream’s first event comes sooner. A gateway older than v2.1.229 sends no pings, so use the `provider: anthropic` rule there.

On top of that time, allow for any [wait for a free slot](#concurrent-upstream-requests) and for the wait on a down upstream before [failover](claude-apps-gateway-config.md#multiple-upstreams). Keep the value above the longest [non-streaming request](network-config.md#streaming-idle-watchdogs) too.


[​](#retry-an-unanswered-request-sooner-with-the-first-byte-deadline)

Retry an unanswered request sooner with the first-byte deadline

To make Claude Code retry an unanswered streaming request sooner, give it a first-byte deadline by setting [`CLAUDE_STREAM_FIRST_BYTE_TIMEOUT_MS`](../02-Claude-Code-CLI/env-vars.md) on your developers’ machines. The deadline limits how long the first attempt waits for response headers. Without it, that attempt can wait 5 to 6 minutes. Requires Claude Code v2.1.307 or later on the developer’s machine. Set the variable to a positive whole number of milliseconds, in a [gateway policy’s `env` block](#deliver-the-value-in-a-policy) or a developer’s shell. Keep the value above your gateway’s longest [time to first byte](#keep-api_timeout_ms-above-the-gateways-time-to-first-byte), or Claude Code can abort and re-send healthy requests whose headers take longer. This example sets 60 seconds in a developer’s shell:

```python
export CLAUDE_STREAM_FIRST_BYTE_TIMEOUT_MS=60000
```

An unanswered re-send can wait 5 to 6 minutes, until the [body idle timeout](network-config.md#streaming-idle-watchdogs) ends it and Claude Code retries it within the [retry budget](../02-Claude-Code-CLI/errors.md#tune-retry-behavior). If you lowered `API_TIMEOUT_MS` only to retry sooner, remove that value, or one unanswered re-send can end the turn with [No response from API](../02-Claude-Code-CLI/errors.md#no-response-from-api). To confirm the deadline fired, have a developer start Claude Code with [debug logging](network-config.md#verify-your-configuration), then search the log for `[first-byte] no response headers`.


[​](#outage-behavior)

Outage behavior

If Postgres goes down, the gateway itself keeps serving signed-in developers and new sign-ins fail. Whether developers actually keep working depends on how your orchestrator handles readiness:

- **Existing sessions**: bearer tokens validate locally with the JWT secret, session refreshes don’t touch the store, and the gateway process can still serve inference
- **New sign-ins**: fail until Postgres recovers, because the device flow and its rate-limit counters live in Postgres
- **[Spend-limit enforcement](claude-apps-gateway-spend-limits.md#postgres-availability)**: fails open by default during the outage, so inference still flows; flip it to fail closed if you’d rather block than run unmetered
- **Readiness**: by default `/readyz` reports not-ready as soon as Postgres is unreachable, so every replica fails its readiness check at once. Where traffic only reaches replicas that pass the check, all traffic, including inference the gateway could still serve, fails until Postgres recovers. The liveness probe on `/healthz` keeps passing throughout.

If your IdP goes down, existing sessions work until `ttl_hours` and new logins fail. A session refresh gets a try-again answer and succeeds once the IdP is back. Set a longer `ttl_hours` if your IdP has frequent maintenance windows.


[​](#readiness-grace-period)

Readiness grace period

To keep signed-in developers working through a short Postgres outage such as a database failover, set [`store.readiness_grace_seconds`](claude-apps-gateway-config.md#store) to longer than the failover takes, for example `300`. With spend limits on and the default fail-open behavior, requests through a replica that stays ready are unmetered until Postgres recovers, so keep the value as low as covers your failover. If you set [`enforcement.fail_closed_on_error: true`](claude-apps-gateway-config.md#enforcement), the gateway refuses signed-in developers’ inference with the `429` `spend limit unavailable` message until Postgres recovers, even while replicas still pass their readiness check. The setting requires Claude Code v2.1.282 or later on the gateway server. An earlier gateway refuses to start when it finds the key, so upgrade every replica before you add it. [Upgrades](#upgrades) covers rolling back. If you point the readiness probe at `/healthz` instead, replicas also keep passing it through an outage, but `/healthz` never reports not-ready, so a replica whose Postgres connection doesn’t recover keeps passing too.


[​](#jwt-secret-rotation)

JWT secret rotation

Rotate the signing secret in stages so existing sessions stay valid:

1.  Generate a new secret. Prepend it to the `session.jwt_secret` array.
2.  Roll the deployment. New tokens sign with the new secret; old tokens still verify.
3.  After `ttl_hours` plus a margin, remove the old secret and roll again.

Rotation is also the only way to force sessions out before they expire: bearer tokens validate locally against the JWT secret, so there is no per-session revocation. Replacing the secret outright, without keeping the old one in the array, invalidates every outstanding session at once. For individual offboarding, deprovision the user in your IdP; their session ends within `ttl_hours`.


[​](#postgres)

Postgres

The gateway stores its state in a PostgreSQL database:

- **Database**: PostgreSQL itself, self-hosted or managed, at the [minimum version](claude-apps-gateway.md#prerequisites) or later. Databases that only implement the Postgres protocol, such as distributed SQL databases, aren’t supported.
- **Address**: `store.postgres_url` takes one host. If the database has several nodes, use the address in front of them, such as your managed service’s endpoint, a load balancer, or a virtual IP. Set a [readiness grace period](#readiness-grace-period) longer than a failover takes.

The gateway holds five data tables plus a `_migrations` table, all created by its boot-time migrations:

| Table              | Contents                                                                      | Retention                                                       |
|--------------------|-------------------------------------------------------------------------------|-----------------------------------------------------------------|
| `kv`               | Device grants (10-minute TTL) and rate-limit counters                         | TTL per row                                                     |
| `spend`            | Per-principal period-to-date spend counters, in cents                         | `admin.spend_retention_months`, default 13                      |
| `spend_limits`     | Configured spend caps                                                         | Until deleted via the API                                       |
| `admin_audit`      | Admin API mutation trail                                                      | `admin.audit_retention_days`, default 365                       |
| `principal_emails` | Each principal’s last-seen email, display name, and IdP groups. Contains PII. | `admin.identity_retention_days` since last activity, default 90 |

A 30-second loop expires `kv` rows past their TTL, and an hourly sweep enforces the retention windows on the spend tables, so nothing grows without bound. Without [spend limits](claude-apps-gateway-spend-limits.md) configured, only `kv` is written. The gateway applies its own schema migrations at boot and on every upgrade, so its database role needs rights to create and alter tables. Point it at a database or schema dedicated to the gateway to keep that grant narrow. With spend limits in use, a lost database means lost spend tracking and caps, not only developer re-logins, so run regular backups. To erase one departed developer immediately rather than waiting on retention, run `DELETE FROM principal_emails WHERE principal = '<sub>'` directly; that removes the only table holding their email, name, and groups. `spend` and `admin_audit` rows reference the pseudonymous OIDC `sub` only.


[​](#size-the-connection-pool)

Size the connection pool

Each replica opens up to [`store.max_connections`](claude-apps-gateway-config.md#store) Postgres connections, five by default. Keep replicas × `store.max_connections` at or below the connections the database has left for the gateway:

- **Replicas**: count the most that run at once. Start from your autoscaling maximum, or your replica count if you don’t autoscale, and add the extra replicas a rolling update starts before it stops old ones. On Amazon ECS with default settings, and on Cloud Run, that can be as many again.
- **Connections left**: take the database’s `max_connections`, then subtract what the database reserves and what other clients hold. PostgreSQL reserves `superuser_reserved_connections`, three by default. A managed service can reserve more, so check its documentation.

For example, take an autoscaling maximum of six, rolling updates that start at most two extra replicas, and the default pool of five. That needs (6 + 2) × 5 = 40 connections. A database with a `max_connections` of 50 and two connections held by other clients has 50 − 3 − 2 = 45 left, so the total fits. If your total doesn’t fit, change one of these:

- **The database**: raise its `max_connections`
- **The replicas**: run fewer, or have your orchestrator start fewer extra replicas in a rolling update
- **The pool**: lower `store.max_connections`, but with [spend limits](claude-apps-gateway-spend-limits.md) on, keep it large enough for each replica’s load. Otherwise spend checks can [time out](claude-apps-gateway-spend-limits.md#postgres-availability) waiting for a connection, and the gateway logs `spend check failed: spend precheck timed out`.


[​](#upgrades)

Upgrades

Replicas are stateless, so a rolling restart loses no gateway state. The gateway runs schema migrations at boot, which means deploying the new binary self-migrates the database. Concurrent replicas serialize on a Postgres advisory lock, so only one applies each migration. When your orchestrator stops a replica with `SIGTERM`, as in a rolling restart or a scale-in, the gateway stops accepting new connections and lets requests and streams already in flight finish before it exits. It waits up to 25 seconds, called the drain window, then closes whatever is still open. A `SIGINT`, such as Ctrl+C in a terminal, starts the same drain, and a second signal during the drain closes the open requests and exits right away. Draining requires gateway v2.1.274 or later. Long generations can stream for minutes. On Kubernetes and Amazon ECS, raise both of these together to give those streams more time:

- **The drain window**: set the `CLAUDE_GATEWAY_DRAIN_TIMEOUT_MS` environment variable on the gateway container to a positive whole number of milliseconds, such as `120000`. The gateway ignores a value in any other form, such as `120s`, and keeps the 25-second default
- **Your orchestrator’s grace period**: `terminationGracePeriodSeconds` on Kubernetes, or `stopTimeout` on Amazon ECS

The grace period defaults to 30 seconds on both platforms. Keep it at least 5 seconds longer than the drain window, or the orchestrator kills the gateway before the drain finishes. On Kubernetes, add the duration of any `preStop` hook as well, because the grace period starts counting before the hook runs rather than when the gateway receives `SIGTERM`. Your platform may also cap how long the drain can run:

- **Amazon ECS on Fargate**: `stopTimeout` allows at most 120 seconds
- **Cloud Run**: stops an instance 10 seconds after `SIGTERM`, so open streams get at most 10 seconds there, whatever the drain window is

When the drain window ends with requests still open, the gateway logs a warning that contains `drain window over after`, counts the requests it cut, and names both settings to raise. Migrations are append-only, so rolling back to a prior binary that knows fewer migrations is safe; it ignores the extra rows. Rollback also re-validates the YAML against the older binary’s schema, so a config that adopted a key introduced by the newer release fails boot on the older one. Remove the new key before rolling back. Because you pin the gateway’s version in your own image, fixes in new Claude Code releases, including security fixes, reach your deployment only when you update the pin and redeploy. Include the gateway in the same patching cadence you use for other services that hold production credentials.


[​](#security)

Security

This section answers the questions a security review asks: what data flows through the gateway and where it goes, which attacks the design defends against, and which answers belong in a compliance questionnaire.


[​](#data-flow)

Data flow

| Data                                                                                                   | Path                                                                                                                                                                                                                                                                       | Sent to Anthropic by the gateway                   |
|--------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------|
| Inference (prompts, completions)                                                                       | CLI → gateway → your upstream                                                                                                                                                                                                                                              | Only if the Anthropic API is a configured upstream |
| Telemetry (OTLP metrics, plus [opt-in logs and traces](claude-apps-gateway-config.md#telemetry)) | CLI → gateway → your collector                                                                                                                                                                                                                                             | Never                                              |
| Identity (email, groups, sub)                                                                          | IdP → gateway → CLI; the CLI stamps it on OTLP exports. If you turn on [`forward_user_identity`](claude-apps-gateway-config.md#per-user-identity-headers-for-a-proxy-you-run), the gateway also sends the developer’s email and IdP subject as headers to your proxy | Never                                              |
| Managed settings                                                                                       | Your gateway YAML → CLI                                                                                                                                                                                                                                                    | Never                                              |
| Audit log                                                                                              | Gateway stderr → your aggregator                                                                                                                                                                                                                                           | Never                                              |


[​](#threat-model-summary)

Threat model summary

The gateway sits inside your network perimeter, but individual developer laptops aren’t treated as trusted. The design accounts for this in three ways:

- Developers hold short-lived JWTs instead of raw upstream keys. The CLI-to-gateway leg uses the RFC 8628 device grant, and the gateway’s authorization-code exchange with the IdP runs PKCE in the default configuration, so an intercepted IdP authorization code is useless.
- The device-verification page enforces same-origin POST and a per-IP rate limit per RFC 8628 §5.1. See [User-code brute-force resistance](#user-code-brute-force-resistance).
- The gateway’s requests to your IdP, your OTLP collectors, and `provider: anthropic` upstreams go through a server-side request forgery (SSRF) guard that resolves DNS, blocks link-local and cloud-metadata addresses plus loopback by default, and pins the connection to the resolved IP, so those operator-influenced URLs can’t be redirected to cloud metadata endpoints. RFC 1918 private ranges are deliberately allowed, because IdPs and OTLP collectors commonly live on private IPs. For the other providers, the gateway refuses a `base_url` that names one of those addresses or a metadata hostname when it loads the config, and the provider’s SDK then connects without the DNS check. If you turn on [proxy-only egress](claude-apps-gateway-config.md#proxy-only-egress), that address check moves to your forward proxy: the gateway hands it hostnames and the proxy’s allowlist must refuse those destinations. Set `CLAUDE_GATEWAY_ALLOW_LOOPBACK=1` in the gateway’s environment only when something the gateway must reach legitimately lives on loopback, such as a local-development IdP or a sidecar OTLP collector on `localhost`. The variable relaxes the loopback block for every operator-configured URL and also skips the boot-time warning that checks whether the pod can reach the cloud metadata endpoint, so prefer giving the collector its own internal address.

If you add your own egress controls, the gateway must reach the metadata server whenever it uses instance-metadata credentials such as workload identity. Two threats are out of scope because they are your infrastructure to secure:

- **A compromised gateway host**: the host both holds the upstream credential and distributes [managed settings](claude-apps-gateway-config.md#managed) to every connected developer, so control over the gateway’s configuration is comparable to control over your MDM. The CLI’s [approval dialog](server-managed-settings.md#approval-memory) for shell-capable settings limits silent changes but doesn’t replace host security.
- **A malicious OIDC provider**: the provider signs the id_tokens the gateway trusts, so it can assert any identity. Vetting and securing your IdP is your responsibility.


[​](#user-code-brute-force-resistance)

User-code brute-force resistance

The `user_code` a developer types into the `/device` verification page is 8 characters drawn from a 20-character alphabet, which yields 20⁸ or about 2.56×10¹⁰ combinations, and it expires after 10 minutes. The gateway applies per-IP rate limits on the device-grant endpoints, configurable via [`rate_limits`](claude-apps-gateway-config.md#http-tuning). Raise the limits if many developers sign in from a single shared corporate NAT address. [Large rollouts](#large-rollouts) shows how to size them. The limits apply only to the sign-in flow, not to inference.


[​](#compliance-posture)

Compliance posture

- **Data residency**: the gateway’s own data plane sends nothing to Anthropic unless the Anthropic API is a configured upstream; when it is, your existing data-handling agreement applies to the inference path. Telemetry, audit, identity, and settings go only to the destinations you configure.
- **Host-process traffic**: the host process is the Claude Code CLI. `claude gateway` runs under the same third-party rules as Amazon Bedrock and Google Cloud’s Agent Platform deployments and sends nothing to Anthropic. Before v2.1.227, the host process sent startup telemetry such as product version and platform, which setting `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` in the container environment turned off. Those releases also sent one `HEAD` request at boot, with no body or credentials, to `/api/hello` on `https://api.anthropic.com`, or on `ANTHROPIC_BASE_URL` when the environment set it, unless the environment also set a proxy variable such as `HTTPS_PROXY` or an mTLS client certificate. They ignored the response, so blocking that request at the egress firewall didn’t affect the gateway.
- **Client analytics**: the CLI disables its own usage analytics and error reporting while signed in to a gateway. Before the first sign-in, the CLI still sends startup events to Anthropic, including on machines whose managed settings force gateway sign-in. To keep those off too, deliver [`DISABLE_TELEMETRY`](managed-settings.md#turn-telemetry-off-for-your-organization) in the same [client-side managed settings](claude-apps-gateway-config.md#client-side-managed-settings) that force gateway sign-in.
- **Error reporting**: the CLI turns error reporting off whenever its model requests go to any endpoint other than Anthropic’s first-party API, such as Amazon Bedrock or a custom `ANTHROPIC_BASE_URL`.
- **Client machines**: developers’ CLIs still send WebFetch hostname checks and version checks to Anthropic unless `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` and `skipWebFetchPreflight: true` are set. [Plugin marketplace requests](#plugin-marketplace-requests) have their own off switches. See [data usage](data-usage.md).
- **Survey ratings**: while signed in to a gateway, the CLI disables the Anthropic-bound rating upload together with the analytics streams, so it doesn’t send ratings to Anthropic.
- **Transcript sharing**: choosing Yes on a survey’s transcript-share prompt writes a local file under `~/.claude/feedback-bundles/` instead of uploading to Anthropic.
- **Client updates**: update checks are separate from gateway traffic. Pin versions through your own distribution and set `DISABLE_UPDATES` if laptops must not fetch releases. `DISABLE_AUTOUPDATER` stops only background updates while `claude update` still works.
- **TLS**: serve `public_url` over HTTPS in production, either from the gateway’s own listener via `listen.tls` or from a TLS-terminating ingress in front of plain-HTTP replicas, with `listen.public_url` set in both cases. The gateway doesn’t refuse plain HTTP. The IdP must serve HTTPS in production, and Postgres supports `?sslmode=require`. Set `Strict-Transport-Security` at your ingress.
- **Vulnerability disclosure**: follow [Reporting security issues](security.md#reporting-security-issues)


[​](#plugin-marketplace-requests)

Plugin marketplace requests

Claude Code fetches plugin marketplaces directly from each developer’s machine, not through the gateway. [Network access requirements](network-config.md#network-access-requirements) lists the hosts. The first time a developer starts an interactive terminal session, Claude Code registers the official marketplace, `claude-plugins-official`. It downloads the catalog from `downloads.claude.ai` and, if that fails, clones it from `github.com`. [Which marketplaces and plugins auto-update](../08-Plugins-Skills/plugins-loading.md#which-marketplaces-and-plugins-auto-update) covers later refreshes. `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` doesn’t stop the first registration. Either of these managed settings does:

- **A marketplace list**: a [`strictKnownMarketplaces`](../08-Plugins-Skills/plugins-org.md#allowlist-with-strictknownmarketplaces) allowlist that leaves the marketplace out, or a [`blockedMarketplaces`](../08-Plugins-Skills/plugins-org.md#blocklist-with-blockedmarketplaces) entry that names it
- **An environment variable**: `CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL` set to `"1"` in the managed [`env` block](../08-Plugins-Skills/plugins-org.md#turn-updates-off-for-the-whole-fleet)

The first registration can run before the developer signs in to the gateway, when no gateway policy has arrived. To cover that first start, deliver your choice in [client-side managed settings](claude-apps-gateway-config.md#client-side-managed-settings) as well as in the gateway policy’s [`cli` or `code` block](claude-apps-gateway-config.md#what-goes-in-cli).


[​](#troubleshooting)

Troubleshooting

For questions and feedback, use [Claude Code support](https://support.claude.com/en/collections/14445694-claude-code), or open an issue on the [Claude Code GitHub repository](https://github.com/anthropics/claude-code/issues). You can also contact your Anthropic account team. When reporting a problem, include:

- **Gateway issue**: the gateway’s stderr for the relevant window, your `gateway.yaml` with secrets redacted, the gateway version, shown on the landing page at `/` and in the `x-cc-gateway-version` response header on `/managed/settings`, and what changed recently
- **Login issue**: the developer runs `claude --debug-file ./claude-debug.txt`, reproduces, and sends that file plus the gateway’s audit log for the same window
- **Inference issue**: the model requested, the upstreams configured, and the gateway’s audit log for the request, which records which upstream served it and the response status

The gateway’s stderr includes the audit event stream, the audit log records developer identities, and the debug file records hook and MCP server output from the developer’s machine. Review and redact these before posting to a public issue.

| Symptom                                                                                                                                                                                                                                                                                                                   | Cause                                                                                                                                                                                                                                                                                                                                                                                                                | Fix                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| A developer’s `/login` shows the standard account picker instead of the **Cloud gateway** screen                                                                                                                                                                                                                          | `forceLoginMethod` or `forceLoginGatewayUrl` isn’t set in managed settings on that machine                                                                                                                                                                                                                                                                                                                           | Deploy the [managed settings file](claude-apps-gateway.md#set-the-gateway-url) to the device; `/login` reads the gateway URL from there                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| A developer’s requests fail with `Not signed in to the Cloud gateway — run /login.`                                                                                                                                                                                                                                       | The machine’s managed settings set `forceLoginMethod: "gateway"` or `forceLoginGatewayUrl`, and the session has no gateway sign-in. A leftover claude.ai login doesn’t satisfy the requirement.                                                                                                                                                                                                                      | Have the developer run `/login` and complete the gateway sign-in. See also [Administrator policy requires a Cloud gateway sign-in](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| Claude Desktop reports that its bootstrap configuration couldn’t be fetched                                                                                                                                                                                                                                               | `/user/bootstrap` returned 404: the policy matching the user doesn’t carry a `desktop` key, or no policy matched. The gateway’s audit log records each rejection as `desktop_bootstrap.denied` with the reason.                                                                                                                                                                                                      | Add a `desktop` block to the policy that matches the user, or to the `match: {}` base layer; an empty `desktop: {}` suffices. See [Claude Desktop overlay](claude-apps-gateway-config.md#claude-desktop-overlay).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| Startup shows `Gateway login is configured in managed settings, but this Claude Code build does not include Cloud gateway support.`                                                                                                                                                                                       | The installed Claude Code build predates gateway support                                                                                                                                                                                                                                                                                                                                                             | Have the developer update Claude Code to a release that includes Cloud gateway support                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| Startup exits with `Administrator policy requires a Cloud gateway sign-in on this machine`                                                                                                                                                                                                                                | The developer’s environment sets `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN`, their settings configure an [`apiKeyHelper`](../02-Claude-Code-CLI/settings-reference.md#apikeyhelper), or an API key from an earlier Claude Console login is still saved                                                                                                                                                                            | Have the developer clear each that applies: unset the variable, remove the `apiKeyHelper` entry, or run `claude auth logout` to remove the saved key. A session that selects a cloud provider with `CLAUDE_CODE_USE_*` then starts with no sign-in; for every other session, have them start `claude` and sign in with `/login`. See also [Administrator policy requires a Cloud gateway sign-in](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in).                                                                                                                                                                                                                                                                   |
| Startup or `/login` reports `Claude Code may not be enabled for your organization` after a 403 on the managed settings load                                                                                                                                                                                               | The gateway, or something in front of it, answered the `/managed/settings` request with 403. The gateway’s own settings route never answers 403. The status comes from the [`access_control`](claude-apps-gateway-config.md#http-tuning) IP checks or from a proxy or WAF in front of the gateway. The audit log records an IP-check denial as `access.denied` with the reason. The developer stays signed in. | Check the audit log for `access.denied` at the time of the failure and fix the `access_control` lists or the front end, then have the developer start `claude` again                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| CLI `/login`: `The gateway is limiting sign-in attempts right now`, or `Request failed with status code 429` on older versions. The `/device` page may show `Too many attempts` to developers who haven’t tried before                                                                                                    | The per-IP sign-in rate limit was reached. Either `listen.trusted_proxies` doesn’t cover the load balancer, so every developer shares its address, or many developers share a NAT or VPN egress address. Audit events with `result: rate_limited` show the same one or few `client_ip` values.                                                                                                                       | Set `listen.trusted_proxies` to the load balancer’s source ranges first, then raise `rate_limits` if developers still share addresses. See [Large rollouts](#large-rollouts).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| CLI `/login`: `Gateway hosts must be on your organization's private network; <host> resolves to the public (or unrecognized) address <ip>`                                                                                                                                                                                | The gateway hostname resolves to at least one public IP address. Claude Code checks each resolved address and requires every one to be private. A common cause is a dual-stack name where one family resolves to a public address, including AWS internal dual-stack load balancers, which return public-range AAAA addresses.                                                                                       | Have the gateway name resolve only to private addresses on developer machines. For a dual-stack name, drop the public-range record or serve a separate internal-only DNS name. See the [private-network prerequisite](claude-apps-gateway.md#prerequisites). If the address is public space your organization owns and uses internally, [declare that block](claude-apps-gateway.md#allow-a-gateway-on-public-address-space-you-own) instead.                                                                                                                                                                                                                                                                                   |
| CLI `/login`: `Gateway login would go through proxy <proxy>, which is not on a private network`                                                                                                                                                                                                                           | An `HTTPS_PROXY` or `HTTP_PROXY` applies to the gateway host and the proxy’s hostname resolves to a public address. A proxy whose host resolves only to private addresses is allowed and doesn’t trigger this error                                                                                                                                                                                                  | Add the gateway host to `NO_PROXY` on the developer’s machine so the connection is direct, or use a proxy whose hostname resolves to private addresses. The message names the exact `NO_PROXY` entry to add                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| CLI `/login`: `Claude Code only signs in to <host> from inside its declared network <block> (managed settings), and this machine is connecting from <ip>, outside it`                                                                                                                                                     | The gateway is on a block declared in [`gatewayInternalNetworks`](claude-apps-gateway.md#allow-a-gateway-on-public-address-space-you-own), and the developer’s machine reached it from an address outside that block: a VPN address pool, a container or WSL2 NAT segment, or a network that isn’t yours                                                                                                       | Have the developer run `/login` from the host OS on your network. If the address shown is also your organization’s own public space, replace the gateway’s entry with a block that covers both, up to `/8`; a second, overlapping entry is refused. If no block covers both, see [Choose an address for the gateway](#choose-an-address-for-the-gateway)                                                                                                                                                                                                                                                                                                                                                                                    |
| CLI `/login`: `Every address for gateway host <host> must be inside its declared network <block>, and it also resolves to <ip>`                                                                                                                                                                                           | The gateway’s name resolves to an address outside the block declared in [`gatewayInternalNetworks`](claude-apps-gateway.md#allow-a-gateway-on-public-address-space-you-own): a second site, or an IPv6 record on a dual-stack name. Under a declared block every record must be inside that one IPv4 block, private and IPv6 addresses included                                                                | Publish only records inside the block for the gateway name on developer machines, or serve a separate internal-only name                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| CLI `/login`: `<host> is on the declared network <block>, which Claude Code checks over a direct connection, not through an HTTP proxy`                                                                                                                                                                                   | An `HTTPS_PROXY` or `HTTP_PROXY` applies to a gateway on a declared block                                                                                                                                                                                                                                                                                                                                            | On the developer’s machine, add the `NO_PROXY` entry the message names                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| CLI `/login`: a message starting `gatewayInternalNetworks in managed settings`                                                                                                                                                                                                                                            | The value breaks one of the [validation rules](claude-apps-gateway.md#allow-a-gateway-on-public-address-space-you-own), and the message names which. Until you fix it, Claude Code refuses every new gateway `/login` on the machine, gateways on private addresses included; existing sign-ins keep working                                                                                                   | In the managed settings source you deploy, correct the entry the message names, then rerun `/login`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| CLI `/login`: `Could not resolve the configured HTTP proxy`                                                                                                                                                                                                                                                               | The hostname in `HTTPS_PROXY` or `HTTP_PROXY` doesn’t resolve from the developer’s machine, typically because it isn’t connected to the corporate network                                                                                                                                                                                                                                                            | Have the developer connect to your network or VPN and retry, or fix the proxy URL                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| CLI `/login`: `Could not resolve gateway host <host>`                                                                                                                                                                                                                                                                     | The machine can’t resolve the gateway’s internal DNS name, typically because it isn’t on the corporate network                                                                                                                                                                                                                                                                                                       | Have the developer connect to your network or VPN, then retry `/login`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| Boot exits with a config validation error naming `store.postgres_url`                                                                                                                                                                                                                                                     | No Postgres configured; the gateway requires Postgres                                                                                                                                                                                                                                                                                                                                                                | Set `store.postgres_url`. For local development, use a throwaway container: `docker run --rm -p 5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust postgres`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| Boot exits: `store.postgres_url in <path> is not a URL the gateway can read`, or before v2.1.290 a bare `Invalid URL` or `URI error`                                                                                                                                                                                      | The URL can’t be parsed, for example because it lists more than one host or its password has an unencoded `/`, `?`, `#`, or `%`                                                                                                                                                                                                                                                                                      | Name [one host](#postgres), and move the password into [`store.password`](claude-apps-gateway-config.md#store)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Boot exits: `requires the native binary`                                                                                                                                                                                                                                                                                  | Running under Node instead of the native binary                                                                                                                                                                                                                                                                                                                                                                      | Install Claude Code with one of the [standalone install methods](../01-Getting-Started/setup.md)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| Boot exits with an OIDC discovery error after `config.load`                                                                                                                                                                                                                                                               | `oidc.issuer` unreachable, or TLS chain not trusted                                                                                                                                                                                                                                                                                                                                                                  | Check the issuer is reachable from the pod and serves `/.well-known/openid-configuration`. Set `ca_cert_pem` for private PKI. If the pod reaches the IdP only through a forward proxy, set [`oidc.use_proxy: true`](claude-apps-gateway-config.md#idp-requests-through-a-forward-proxy); on versions before v2.1.227, give the pod a direct route to each of the IdP’s endpoints instead. If the pod also can’t resolve the IdP’s hostname, or the proxy refuses `CONNECT` to an IP address, see [Proxy-only egress](claude-apps-gateway-config.md#proxy-only-egress), which requires v2.1.277 or later.                                                                                                                        |
| Boot exits with a Postgres permission error                                                                                                                                                                                                                                                                               | The database role lacks DDL rights on its schema                                                                                                                                                                                                                                                                                                                                                                     | Grant the role `CREATE` on the gateway’s schema so it can create and alter its tables at boot                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| Log: `remaining connection slots are reserved` or `sorry, too many clients already`, from PostgreSQL. It shows in running replicas’ `postgres connection closed` warnings, or in a new replica’s boot log. There, `remaining connection slots are reserved` can follow `attempt 1 of 3`, and the replica may still start. | The replicas ask for more connections than the database has left for the gateway, and PostgreSQL refuses the extra ones                                                                                                                                                                                                                                                                                              | Make the total fit, as [Size the connection pool](#size-the-connection-pool) describes: run fewer replicas, lower `store.max_connections`, or raise the database’s `max_connections`. If the total fits except during a rolling update, you can instead have your orchestrator stop an old replica before it starts a new one, if it supports that.                                                                                                                                                                                                                                                                                                                                                                                         |
| Log: `could not connect to Postgres at boot, attempt 1 of 3`, with a reason other than `remaining connection slots are reserved`                                                                                                                                                                                          | The database wasn’t reachable yet when the gateway started, for example on a cold instance whose network is still coming up                                                                                                                                                                                                                                                                                          | If the gateway then finishes booting, no action is needed. When the database isn’t reachable, the gateway tries the connection three times, two seconds apart, before it exits. If it exits with `could not connect to Postgres`, check `store.postgres_url`, including that it names one host, and the network path to the database. If the attempts time out rather than being refused, raise [`store.connect_timeout_seconds`](claude-apps-gateway-config.md#store) to give each one longer.                                                                                                                                                                                                                                       |
| Log: `Postgres <version> is below the supported floor`                                                                                                                                                                                                                                                                    | The database runs a PostgreSQL version older than your gateway version supports. The gateway keeps running.                                                                                                                                                                                                                                                                                                          | Upgrade the database. On PostgreSQL 11, 12, or 13, you can instead upgrade the gateway to meet the [PostgreSQL requirement](claude-apps-gateway.md#prerequisites).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `/oauth/callback` shows “Sign-in could not be completed”                                                                                                                                                                                                                                                                  | Email domain rejected, id_token validation failed, or `email_verified` is explicitly `false`, which the gateway always rejects with no override                                                                                                                                                                                                                                                                      | Check `allowed_email_domains` and that the IdP returns a verified `email` claim. For `email_verified: false`, fix the IdP-side verification. If your IdP emits email under a different claim name, set `oidc.email_claim`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| Log: `token exchange failed request_id=<id>: id_token missing email claim`                                                                                                                                                                                                                                                | The IdP isn’t including `email` in the id_token by default. This rejection fires only when `allowed_email_domains` is set; without it, a missing email mints a session with no email                                                                                                                                                                                                                                 | Configure the IdP to emit `email` in the id_token. Okta: add `email` to a custom authorization server’s ID-token claims. Entra: add `email` as an optional claim on the app registration. PingFederate: enable an OpenID Connect Policy that emits `email`. If the IdP serves `email` from the userinfo endpoint but won’t include it in the id_token, such as the Okta org authorization server, set `oidc.userinfo_fallback: true`.                                                                                                                                                                                                                                                                                                       |
| Log: `refresh failed request_id=<id>: invalid_token (…) (at userinfo_no_id_token, …)`, and developers see `Cloud gateway session expired` every `session.ttl_hours`                                                                                                                                                       | The IdP accepted the refresh token but returned no id_token with it, so the gateway asked the IdP’s userinfo endpoint for the user’s claims. The IdP rejected the refreshed access token there. The gateway answers `temporarily_unavailable`, so Claude Code keeps the refresh token but can’t renew the session. Gateway versions before v2.1.260 log the same line without the `(at …)` detail.                   | Set [`oidc.scope_on_refresh: true`](claude-apps-gateway-config.md#oidc), available in gateway v2.1.260 or later, so the refresh request asks for `openid` again. Some IdPs, such as Okta, return an id_token on refresh only when asked. On PingFederate, enable **Return ID Token On Refresh Grant** under **Applications \> OAuth \> OpenID Connect Policy Management** instead. The key doesn’t change PingFederate’s behavior. For other IdPs that still omit it, check whether the userinfo endpoint accepts access tokens issued by a refresh. As a stopgap, raise [`session.ttl_hours`](claude-apps-gateway-config.md#session). See [Identity provider setup](#identity-provider-setup) for the deprovisioning tradeoff. |
| A developer signs in, then every request from that session fails with a `431` error                                                                                                                                                                                                                                       | The session token in every request’s `Authorization` header lists the developer’s IdP groups, so for a developer in many groups the headers can total more than the gateway accepts                                                                                                                                                                                                                                  | See [Request headers too large after sign-in](#request-headers-too-large-after-sign-in) for which limit applies and what to change                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| Every Amazon Bedrock request returns 502; log shows `Could not load credentials from any providers`                                                                                                                                                                                                                       | On EC2, IMDSv2’s default hop limit of 1 blocks the instance-metadata request from inside the container. Boot and `/readyz` pass anyway because the AWS SDK resolves instance credentials on the first request, not at client construction                                                                                                                                                                            | Raise the hop limit with `aws ec2 modify-instance-metadata-options --instance-id <id> --http-put-response-hop-limit 2`, or set it in the launch template. The change applies to every container on the instance. Prefer ECS task roles where available, which read credentials from the ECS container-credentials endpoint and avoid the change entirely, or apply the change on a dedicated gateway instance to limit the exposure.                                                                                                                                                                                                                                                                                                        |
| At peak load, responses are slow to start or appear to hang, or fail with a 502 `all upstreams failed` while the upstream is healthy                                                                                                                                                                                      | A replica has more requests open than it sends upstream at once, so the extra requests wait inside the gateway. On a `provider: anthropic` upstream, a request that waits longer than `timeouts.upstream_ttfb_ms` gives up on that upstream, which produces the 502 when no later upstream serves it. The log shows a warning that contains `client requests are open`.                                              | Add replicas, or raise the limit on each replica. See [Concurrent upstream requests](#concurrent-upstream-requests).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| On an upstream other than `provider: anthropic`, a turn with a long thinking pause fails. The proxy in front of the gateway logs one request that stays open for about 12.5 minutes, then a run of `504` errors                                                                                                           | Claude Code ended a stream that had delivered its first response event and then carried only keepalive pings. It re-sent the request without streaming, and each reply takes longer than the proxy’s timeout.                                                                                                                                                                                                        | Raise `CLAUDE_STREAM_IDLE_TIMEOUT_MS` for your developers. See [Long thinking ends in proxy timeouts](#long-thinking-ends-in-proxy-timeouts).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| On `provider: anthropic`, a turn with a long thinking pause fails. The proxy logs one request open for about 20 minutes, then `502` or `504` errors                                                                                                                                                                       | Claude Code ended a stream that had delivered its first response event and then carried only keepalive pings. It re-sent the request without streaming, and each reply takes longer than the gateway’s wait on the upstream, `timeouts.upstream_ttfb_ms`, or the proxy’s timeout.                                                                                                                                    | Raise `CLAUDE_STREAM_IDLE_TIMEOUT_MS` for your developers. See [Long thinking on an Anthropic API upstream](#long-thinking-on-an-anthropic-api-upstream).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| IdP error: unknown or unsupported scope                                                                                                                                                                                                                                                                                   | The IdP rejects scopes it doesn’t recognize                                                                                                                                                                                                                                                                                                                                                                          | Set `oidc.scopes` to exactly the list your IdP accepts; it must include `openid`. The default is `openid profile email offline_access`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| Sessions don’t silently renew after setting `oidc.scopes`                                                                                                                                                                                                                                                                 | `offline_access` was dropped from the override                                                                                                                                                                                                                                                                                                                                                                       | Add `offline_access` back if your IdP supports it. Without a refresh token, developers re-run the browser login every `session.ttl_hours`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| Browser shows “This request came from another site and was blocked”                                                                                                                                                                                                                                                       | Cross-site form POST, blocked as CSRF protection. Expected for embedded or proxied pages                                                                                                                                                                                                                                                                                                                             | Open the verification link directly                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| Chrome blocks the Approve button with “Refused to send form data … violates … Content Security Policy directive: form-action”, but the same page works in Safari or Firefox                                                                                                                                               | Chrome enforces `form-action` against the entire redirect chain. Your IdP redirects onward to a second host that isn’t allowlisted.                                                                                                                                                                                                                                                                                  | Add each additional origin in the redirect chain to `oidc.form_action_origins`. Open Chrome DevTools → Console on the Approve page to see which origin was blocked.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| Sign-in completes at the IdP but the callback fails, with a CSP error in Chrome or “this sign-in link has expired” in Safari                                                                                                                                                                                              | The IdP returned the code via `response_mode=form_post`, which auto-submits it cross-origin via POST to `/oauth/callback`. Chrome blocks that under a strict CSP; Safari allows the submit but the callback reads only the query string.                                                                                                                                                                             | Make sure your IdP honors `response_mode=query`, which the gateway requests explicitly so the callback is a plain redirect                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| Login works locally but fails behind an ALB                                                                                                                                                                                                                                                                               | `public_url` still names the local or inner `http://` origin, so the IdP gets the wrong `redirect_uri`                                                                                                                                                                                                                                                                                                               | Set `listen.public_url` to the external `https://` origin and register `<public_url>/oauth/callback` with the IdP                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| Developer sees the trust prompt repeatedly                                                                                                                                                                                                                                                                                | TLS cert is rotating per replica or per request                                                                                                                                                                                                                                                                                                                                                                      | Use a stable cert at the ingress, or terminate TLS once and run replicas over plain HTTP internally                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| CLI `/login`: “Could not verify the gateway’s TLS certificate” or `SELF_SIGNED_CERT_IN_CHAIN`                                                                                                                                                                                                                             | Gateway’s TLS chain is signed by a private CA not in the CLI host’s trust store                                                                                                                                                                                                                                                                                                                                      | Claude Code reads the OS trust store by default on the native binary and on Node 22.15 or later; [`CLAUDE_CODE_CERT_STORE`](network-config.md#ca-certificate-store) controls this behavior. If the CA is installed in the OS trust store, ensure developers are on a current runtime. Otherwise set `NODE_EXTRA_CA_CERTS` to the CA certificate PEM before launching. The first-connect fingerprint prompt still applies.                                                                                                                                                                                                                                                                                                             |
| CLI `/login` completes the browser sign-in, then the session ends with `Cloud gateway sign-in was not completed` and a TLS certificate mismatch                                                                                                                                                                           | On the first request after sign-in, the gateway presented a certificate that doesn’t match the fingerprint Claude Code pinned, so Claude Code kept no gateway credential. The usual causes are replicas behind one address that serve different certificates, or something on the network path that intercepts TLS.                                                                                                  | Serve one certificate for the hostname, for example by terminating TLS once at the ingress, then have the developer run `/login` again. If that certificate differs from the pinned one, Claude Code shows the [trust prompt](claude-apps-gateway.md#connect-developers) again with a warning that the certificate changed.                                                                                                                                                                                                                                                                                                                                                                                                           |
| CLI `/login` stops with `The gateway's TLS certificate changed during sign-in: it no longer matches the one you trusted`                                                                                                                                                                                                  | A sign-in request reached a server whose certificate doesn’t match the one the developer accepted when `/login` started: replicas behind one address serving different certificates, TLS interception on the path, or a certificate rotation while the sign-in was in progress.                                                                                                                                      | Serve one certificate for the hostname, then have the developer start the sign-in again and review the new certificate at the [trust prompt](claude-apps-gateway.md#connect-developers).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |

The `Cloud gateway sign-in was not completed` message names the gateway hostname. When Claude Code has both the pinned fingerprint and the presented one, the message also shows the first 16 characters of each. If Claude Code reports `couldn't load your organization's managed settings` after a gateway sign-in, Claude Code names the reason, restarts in place, and resumes the conversation. If Claude Code can’t restart, for example in a background session, Claude Code ends the session and keeps the sign-in.


[​](#request-headers-too-large-after-sign-in)

Request headers too large after sign-in

A developer’s requests can fail with a `431` error after sign-in when the developer belongs to many IdP groups. The gateway answers `431` when a request’s headers total more than 256 KiB, or more than [`limits.max_request_header_bytes`](claude-apps-gateway-config.md#http-tuning) if you set it. It writes no log line or audit event for these requests. Gateway versions before v2.1.284 answer `431` above 16 KiB. Start with the first of these that applies to your gateway:

- **Gateway older than v2.1.284**: upgrade the gateway
- **`limits.max_request_header_bytes` set**: raise the value or remove the key
- **Neither applies, or `431` continues afterward**: have your IdP emit fewer groups. [Identity provider setup](#identity-provider-setup) covers how Okta, Microsoft Entra ID, and Google Workspace supply groups

When you trim the groups claim, keep the groups you named in these settings, which decide a developer’s access, policy, and spend caps:

- **[`oidc.allowed_groups`](claude-apps-gateway-config.md#oidc)**: decides who can sign in
- **[`admin.admin_groups`](claude-apps-gateway-config.md#admin)**: decides who can call the admin API with their gateway session
- **`match.groups` in [`managed.policies`](claude-apps-gateway-config.md#managed)**: decides which policy applies to a developer
- **`rbac_group` [spend caps](claude-apps-gateway-spend-limits.md)**: decide which group caps apply to a developer


[​](#long-thinking-ends-in-proxy-timeouts)

Long thinking ends in proxy timeouts

If your upstream is `provider: anthropic`, use [Long thinking on an Anthropic API upstream](#long-thinking-on-an-anthropic-api-upstream) instead: you raise `CLAUDE_STREAM_IDLE_TIMEOUT_MS` there too, but the times and errors differ. If turns fail after long thinking and your proxy logs one request open for about 12.5 minutes, then a run of `504` errors, raise [`CLAUDE_STREAM_IDLE_TIMEOUT_MS`](../02-Claude-Code-CLI/env-vars.md) in a gateway policy. For example, with a 240-second idle timeout on the proxy, the run is 10 requests, each cut at 240 seconds.

- **The first request**: a stream that delivers its first response event, `message_start`, and then only pings while Claude thinks. With default settings, Claude Code ends that stream after about 12.5 minutes.
- **The other 10**: the same request, sent [without streaming](../02-Claude-Code-CLI/errors.md#streaming-response-ended-before-any-complete-data-was-received) and then retried, 10 sends in all by default. They get no pings, so a slow reply hits the proxy’s timeout.

You don’t need a longer proxy timeout for this fix. If you raise only the proxy’s timeout, requests without streaming still fail once a reply takes longer than Claude Code’s own wait, 300 seconds by default.


[​](#confirm-the-cause)

Confirm the cause

Have a developer reproduce the failure with `claude --debug-file ./claude-debug.txt`, then search that file for this line:

```python
Error streaming, falling back to non-streaming mode: Stream idle timeout - no chunks received
```

If your developers already set [`CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK`](../02-Claude-Code-CLI/env-vars.md) to `1`, you see no requests without streaming, and the turn ends with `API Error: Stream idle timeout - no chunks received`. The same fix applies.


[​](#pick-a-value)

Pick a value

Pick a `CLAUDE_STREAM_IDLE_TIMEOUT_MS` value whose second column covers your longest thinking pause, or start with `1800000` if you don’t know it. A stream can stay on pings alone for about 7.5 minutes longer than the value:

| `CLAUDE_STREAM_IDLE_TIMEOUT_MS` | A stream can stay on pings alone for |
|---------------------------------|--------------------------------------|
| `300000`, the default           | About 12.5 minutes                   |
| `900000`                        | About 22.5 minutes                   |
| `1800000`                       | About 37.5 minutes                   |

If an upstream hangs while the gateway keeps sending pings, your developers also wait that long before Claude Code gives up on the stream. A connection that goes fully silent still ends after 5 to 6 minutes, because the [body idle timeout](network-config.md#streaming-idle-watchdogs) doesn’t follow this variable.


[​](#deliver-the-value-in-a-policy)

Deliver the value in a policy

Add the `env` entry to each policy in `gateway.yaml` that already delivers Claude Code settings to the developers you want to cover. Put it under the `code`, `cli`, or `settings` key that policy already has. To try the value first, start with a policy that matches one group. See [`managed`](claude-apps-gateway-config.md#managed) for how policies match and inherit. This example adds the entry to a `match: {}` policy keyed `cli`:

```python
managed:
  policies:
    - match: {}
      cli:
        env:
          CLAUDE_STREAM_IDLE_TIMEOUT_MS: "1800000"
```

With the `cli` key, you cover terminal, IDE extension, and Agent SDK sessions. For Claude Desktop’s Code tab, see [Choose `cli` or `code`](claude-apps-gateway-config.md#choose-cli-or-code). Restart the gateway so that it reads the edited file. Then have developers restart Claude Code to get `CLAUDE_STREAM_IDLE_TIMEOUT_MS`.


[​](#long-thinking-on-an-anthropic-api-upstream)

Long thinking on an Anthropic API upstream

If `provider: anthropic` is the model’s only upstream and your proxy logs one request open for about 20 minutes, then `502` or `504` errors, raise [`CLAUDE_STREAM_IDLE_TIMEOUT_MS`](../02-Claude-Code-CLI/env-vars.md) in a gateway policy.

- **The long request**: a stream that delivers `message_start` and then only pings while Claude thinks. Claude Code ends it after about 20 minutes with default settings, and later once you raise the variable.
- **The errors**: requests that Claude Code then sends [without streaming](../02-Claude-Code-CLI/errors.md#streaming-response-ended-before-any-complete-data-was-received). Each one fails with a `502` from the gateway, or with your proxy’s `504` if the proxy’s timeout is shorter than the gateway’s wait. That wait is [`timeouts.upstream_ttfb_ms`](claude-apps-gateway-config.md#http-tuning), which you don’t need to raise for this fix.

To confirm the cause, see [Confirm the cause](#confirm-the-cause). Pick the value whose time covers your longest thinking pause: about 20 minutes at the default, 30 at `900000`, and 45 at `1800000`. Then follow [Deliver the value in a policy](#deliver-the-value-in-a-policy).


[​](#related)

Related

- [Claude apps gateway overview](claude-apps-gateway.md): quickstart and developer connection
- [Configuration reference](claude-apps-gateway-config.md): every `gateway.yaml` option

---

## Deploy Claude apps gateway on AWS - Claude Code Docs

Source: https://code.claude.com/docs/en/claude-apps-gateway-on-aws
Path: 13-Enterprise-Admin/claude-apps-gateway-on-aws.md

## On this page

- [Architecture](#architecture)
- [Prerequisites](#prerequisites)
  - [Set your environment variables](#set-your-environment-variables)
- [Deploy the gateway](#deploy-the-gateway)
- [Terraform reference](#terraform-reference)
- [Troubleshooting](#troubleshooting)
- [Telemetry](#telemetry)
  - [Client metrics, logs, and traces](#client-metrics-logs-and-traces)
  - [Gateway logs](#gateway-logs)
  - [Container metrics](#container-metrics)
  - [Spend](#spend)
- [Cost attribution](#cost-attribution)
  - [Per developer with assume_role](#per-developer-with-assume_role)
  - [Per team with application inference profiles](#per-team-with-application-inference-profiles)
  - [Let a developer choose which team to bill](#let-a-developer-choose-which-team-to-bill)
- [Next steps](#next-steps)

Claude apps gateway

# Deploy Claude apps gateway on AWS

Copy pageCopy page

A worked example of running Claude apps gateway on AWS: ECS Fargate or EKS, Amazon RDS for PostgreSQL, AWS Secrets Manager, and IAM-role auth to Amazon Bedrock.

Copy pageCopy page

This page walks through one way to run Claude apps gateway on AWS. The configuration is a working example for customer-managed infrastructure rather than a supported production deployment; use it to see how the pieces fit together before adapting it to your own environment. For the platform-agnostic requirements, see the [deployment guide](claude-apps-gateway-deploy.md).

This example provisions Claude apps gateway on AWS with Amazon Bedrock as the model upstream, using either [Amazon ECS](https://aws.amazon.com/ecs/) on [AWS Fargate](https://aws.amazon.com/fargate/) or [Amazon EKS](https://aws.amazon.com/eks/) for compute. [Okta](https://www.okta.com/) is the example identity provider (IdP), but any OpenID Connect (OIDC) compliant IdP works; see [Identity provider setup](claude-apps-gateway-deploy.md#identity-provider-setup) for per-IdP details.

Bedrock isn’t the only Claude upstream on AWS. The gateway also supports Claude Platform on AWS, the Anthropic-operated Claude API with AWS authentication and AWS Marketplace billing, in place of Bedrock or alongside it. Its upstream entry, credentials, and IAM permissions differ from this page’s Bedrock-scoped ones; the [Claude Platform on AWS upstream reference](claude-apps-gateway-config.md#claude-platform-on-aws) covers what changes, and the rest of this page applies unchanged.


[​](#architecture)

Architecture

The gateway runs as a private HTTPS endpoint on your network that developers sign in to through your IdP. Their Claude Code sessions reach Claude models on Amazon Bedrock through the gateway’s IAM role, so no model credentials land on developer machines. The reference configuration provisions:

- **Amazon ECS on AWS Fargate** service or **Amazon EKS** Deployment running the gateway container
- **Amazon ECR** repository for the gateway image
- **Amazon RDS for PostgreSQL** instance in private subnets, not publicly accessible, for the gateway’s [store](claude-apps-gateway-config.md#store)
- **AWS Secrets Manager** secrets for the JWT signing key, the OIDC client secret, and the Postgres URL
- **IAM role** with `bedrock:InvokeModel`, `bedrock:InvokeModelWithResponseStream`, and `bedrock:CountTokens`, attached as the ECS task role or bound via IAM Roles for Service Accounts (IRSA) on EKS
- **Internal Application Load Balancer** for HTTPS


[​](#prerequisites)

Prerequisites

The walkthrough creates the gateway’s own resources, but it builds on network and identity infrastructure you already have. Before you start, you need:

- An AWS account with permission to create the [resources above](#architecture)
- The [AWS CLI v2](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) installed and [authenticated](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-authentication.html), and [Docker](https://docs.docker.com/get-started/get-docker/) installed locally
- A [VPC](https://docs.aws.amazon.com/vpc/latest/userguide/what-is-amazon-vpc.html) with at least two [private subnets](https://docs.aws.amazon.com/vpc/latest/userguide/configure-subnets.html) in different Availability Zones, with outbound internet access through a [NAT gateway](https://docs.aws.amazon.com/vpc/latest/userguide/vpc-nat-gateway.html); the internal load balancer needs subnets in two AZs, and the gateway needs egress to Bedrock and your IdP
- An Okta OIDC web application with redirect URI `https://<gateway-host>/oauth/callback`; see [Identity provider setup](claude-apps-gateway-deploy.md#identity-provider-setup)
- A TLS hostname for the gateway, typically an internal DNS name in a [Route 53 private hosted zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/hosted-zones-private.html) pointing at the load balancer, with an [ACM certificate](https://docs.aws.amazon.com/acm/latest/userguide/gs.html) for that name, imported or issued by [AWS Private CA](https://docs.aws.amazon.com/privateca/latest/userguide/PcaWelcome.html)


[​](#set-your-environment-variables)

Set your environment variables

Every command on this page reads four values from your shell: `AWS_REGION`, `ACCOUNT_ID`, `VPC_ID`, and `PRIVATE_SUBNETS`. Pick a US region where Bedrock serves the Claude models you need. The walkthrough relies on the gateway’s built-in model catalog, which resolves to `us.anthropic.*` inference profiles, and the IAM policy grants those ARNs. In a non-US region, add a [`models:` block](claude-apps-gateway-config.md#models) with that geo’s inference-profile IDs and change the IAM policy’s ARN prefix to match. If you don’t have the VPC ID at hand, list your VPCs with `aws ec2 describe-vpcs`, then list that VPC’s subnets to find two private ones in different Availability Zones:

```python
aws ec2 describe-subnets --filters "Name=vpc-id,Values=<your-vpc-id>" \
  --query 'Subnets[].{ID:SubnetId,AZ:AvailabilityZone,CIDR:CidrBlock}' --output table
```

Export all four before continuing:

```python
export AWS_REGION=us-east-1   # a US region where Bedrock serves the Claude models you need
export ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export VPC_ID=<your-vpc-id>
export PRIVATE_SUBNETS="<subnet-id-a> <subnet-id-b>"
```


[​](#deploy-the-gateway)

Deploy the gateway

The steps below provision the full deployment with `aws` commands.

1

Create the security groups

Three security groups chain the traffic path: your corporate network reaches the load balancer on 443, the load balancer reaches the gateway on 8080, and the gateway reaches Postgres on 5432. Nothing else is reachable. How you attach them depends on the compute track:

- On ECS Fargate, the deploy step attaches `$ALB_SG` to the load balancer and `$GW_SG` to the service.
- On EKS, the AWS Load Balancer Controller creates its own frontend security group for the ALB, so `$ALB_SG` and `$GW_SG` go unused: the deploy step’s `inbound-cidrs` annotation restricts the listener to your corporate network, and the database security group admits the cluster’s security group instead.

```python
ALB_SG="$(aws ec2 create-security-group --group-name claude-gateway-alb \
  --description "Claude gateway ALB" --vpc-id "$VPC_ID" \
  --query GroupId --output text)"
GW_SG="$(aws ec2 create-security-group --group-name claude-gateway-svc \
  --description "Claude gateway service" --vpc-id "$VPC_ID" \
  --query GroupId --output text)"
DB_SG="$(aws ec2 create-security-group --group-name claude-gateway-db \
  --description "Claude gateway Postgres" --vpc-id "$VPC_ID" \
  --query GroupId --output text)"

aws ec2 authorize-security-group-ingress --group-id "$ALB_SG" \
  --protocol tcp --port 443 --cidr <your-corporate-cidr>
aws ec2 authorize-security-group-ingress --group-id "$GW_SG" \
  --protocol tcp --port 8080 --source-group "$ALB_SG"
aws ec2 authorize-security-group-ingress --group-id "$DB_SG" \
  --protocol tcp --port 5432 --source-group "$GW_SG"
```

2

Create the IAM roles and submit the use case form

The gateway runs with a dedicated task role whose only permission is invoking Claude models on Bedrock. Per the [Bedrock upstream reference](claude-apps-gateway-config.md#amazon-bedrock), the policy must cover both the cross-region inference-profile ARNs and the underlying foundation-model ARNs:

```python
cat > bedrock-invoke.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream", "bedrock:CountTokens"],
    "Resource": [
      "arn:aws:bedrock:${AWS_REGION}:${ACCOUNT_ID}:inference-profile/us.anthropic.*",
      "arn:aws:bedrock:*::foundation-model/anthropic.*"
    ]
  }]
}
EOF
cat > ecs-trust.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "ecs-tasks.amazonaws.com" },
    "Action": "sts:AssumeRole"
  }]
}
EOF

aws iam create-role --role-name claude-gateway-task \
  --assume-role-policy-document file://ecs-trust.json
aws iam put-role-policy --role-name claude-gateway-task \
  --policy-name bedrock-invoke --policy-document file://bedrock-invoke.json
```

ECS also needs an execution role, which the ECS agent itself uses to pull the image from ECR and inject the Secrets Manager values created later. It is separate from the task role the gateway’s AWS SDK uses at runtime:

```python
aws iam create-role --role-name claude-gateway-execution \
  --assume-role-policy-document file://ecs-trust.json
aws iam attach-role-policy --role-name claude-gateway-execution \
  --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
cat > secrets-read.json <<EOF
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"],
    "Resource": [
      "arn:aws:secretsmanager:${AWS_REGION}:${ACCOUNT_ID}:secret:gateway-jwt-secret-??????",
      "arn:aws:secretsmanager:${AWS_REGION}:${ACCOUNT_ID}:secret:gateway-oidc-client-secret-??????",
      "arn:aws:secretsmanager:${AWS_REGION}:${ACCOUNT_ID}:secret:gateway-postgres-url-??????"
    ]
  }]
}
EOF
aws iam put-role-policy --role-name claude-gateway-execution \
  --policy-name read-gateway-secrets --policy-document file://secrets-read.json
```

The policy names one ARN per secret rather than a bare `gateway-*` wildcard, which in a shared account would also match unrelated secrets; the trailing `-??????` matches exactly the random six-character suffix Secrets Manager appends to every secret’s ARN. A trailing `-*` would be a plain prefix glob and would also match longer names such as `gateway-postgres-url-prod`.The IAM policy grants the gateway permission to call Bedrock, and Bedrock enables model access by default in commercial regions. The remaining account-level gate is Anthropic’s one-time use case form: if no one in your account has submitted it, open the [Amazon Bedrock console](https://console.aws.amazon.com/bedrock/), select an Anthropic model from the Model catalog, and complete the form. Access is granted immediately after submission; see [Claude Code on Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md#1-submit-use-case-details) for the AWS Organizations form and the IAM permissions the submitter needs.The EKS track reuses both policy documents on an IRSA role instead of the two ECS roles; see the deploy step.

3

Provision Amazon RDS for PostgreSQL

The instance runs Postgres 16 in the private subnets, with no public address and storage encryption on.First, create the subnet group that places the database in the private subnets, and a parameter group with `rds.force_ssl=1` so the server rejects plaintext connections. The engine version is pinned once because the parameter group’s family must match the engine major version the instance runs:

```python
aws rds create-db-subnet-group --db-subnet-group-name claude-gateway-db \
  --db-subnet-group-description "Claude gateway" --subnet-ids $PRIVATE_SUBNETS

PG_VERSION=16
PG_FAMILY="postgres${PG_VERSION}"
aws rds create-db-parameter-group --db-parameter-group-name claude-gateway-db \
  --db-parameter-group-family "$PG_FAMILY" \
  --description "Claude gateway - require TLS on every connection"
aws rds modify-db-parameter-group --db-parameter-group-name claude-gateway-db \
  --parameters "ParameterName=rds.force_ssl,ParameterValue=1,ApplyMethod=immediate"
```

Then create the instance with a generated master password:

```python
PGPASS="$(openssl rand -hex 24)"
aws rds create-db-instance --db-instance-identifier claude-gateway-db \
  --engine postgres --engine-version "$PG_VERSION" \
  --db-instance-class db.t4g.micro \
  --allocated-storage 20 --db-name claude_gateway \
  --master-username gateway --master-user-password "$PGPASS" \
  --db-subnet-group-name claude-gateway-db \
  --db-parameter-group-name claude-gateway-db \
  --vpc-security-group-ids "$DB_SG" \
  --no-publicly-accessible --storage-encrypted
```

The literal `--master-user-password` argument is visible in the process table and in audit/EDR logs while the command runs, the same exposure the secrets step’s note covers. On a shared or monitored host, pass the password via `--cli-input-json` from a `0600` file instead, the way the bundle’s `setup.sh` does.Wait for the instance to come up, which can take several minutes, then read its private endpoint and assemble the connection string the gateway will use:

```python
aws rds wait db-instance-available --db-instance-identifier claude-gateway-db
DB_HOST="$(aws rds describe-db-instances --db-instance-identifier claude-gateway-db \
  --query 'DBInstances[0].Endpoint.Address' --output text)"
GATEWAY_POSTGRES_URL="postgres://gateway:${PGPASS}@${DB_HOST}:5432/claude_gateway?sslmode=verify-full"
```

`sslmode=verify-full` makes the gateway verify the RDS server certificate’s chain and hostname, not only encrypt. The trust anchor is the [AWS RDS certificate bundle](https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem), which the image build step below copies to `/etc/claude/rds-global-bundle.pem` and trusts via `NODE_EXTRA_CA_CERTS`. Don’t append a libpq-style `sslrootcert=` parameter to the URL: the gateway’s driver reads only `sslmode` from the query string and would forward `sslrootcert` to Postgres as a startup parameter, which the server rejects.The ECS service or EKS pods must run in this VPC so they can reach the instance’s private endpoint, and the `claude-gateway-db` security group only admits the gateway’s security group.

4

Write gateway.yaml

The `upstreams` block points at Bedrock with `auth: {}`, so the gateway authenticates via the AWS default credential chain from the task role on ECS or the IRSA role on EKS. See the [configuration reference](claude-apps-gateway-config.md) for every field.Two `listen` fields describe what fronts the gateway:

- `public_url`: the external `https://` origin, required for any non-loopback bind; see the [`listen` reference](claude-apps-gateway-config.md#listen). The gateway builds the IdP `redirect_uri` and its discovery document only from this value, never from `X-Forwarded-*` headers.
- `trusted_proxies`: the front end’s source ranges. The gateway honors `X-Forwarded-For` only when the TCP peer is in this list, then walks the chain past trusted hops, so per-IP sign-in rate limits and audit events record developer IPs instead of the load balancer’s.

On both tracks the front end is an internal ALB, whether created directly or by the AWS Load Balancer Controller, and an ALB’s nodes take addresses from the subnets it is attached to, so set `trusted_proxies` to those subnets’ CIDRs. This trusts every host in those subnets as a proxy. Keep the ALB’s ingress source, your corporate CIDR, from overlapping them, and don’t share the subnets with untrusted workloads that could spoof client IPs via `X-Forwarded-For`.The ALB’s client port preservation attribute, `routing.http.xff_client_port.enabled`, can stay at either setting: with it on, the ALB writes the client as `203.0.113.7:54321` or `[2001:db8::1]:54321`, and the gateway reads both with the port dropped.

gateway.yaml

```python
listen:
  host: 0.0.0.0
  port: 8080
  public_url: https://claude-gateway.internal.example.com
  trusted_proxies: [<your-alb-subnet-cidrs>]

oidc:
  issuer: https://example.okta.com
  client_id: 0oa1example2
  client_secret: ${OIDC_CLIENT_SECRET}           # EKS: ${file:/secrets/oidc-client-secret}
  allowed_email_domains: [example.com]
  # The Okta org authorization server returns a thin id_token that omits
  # email and groups; the gateway fills them from /userinfo.
  userinfo_fallback: true
  # Okta emits groups only when the `groups` scope is requested and the
  # app's groups claim filter allows them.
  scopes: [openid, profile, email, offline_access, groups]

session:
  jwt_secret: ${GATEWAY_JWT_SECRET}              # EKS: ${file:/secrets/jwt-secret}
  ttl_hours: 8                                   # bounds deprovision latency; lower
                                                 # toward 1 for tighter revocation

store:
  postgres_url: ${GATEWAY_POSTGRES_URL}          # EKS: ${file:/secrets/postgres-url}
  # readiness_grace_seconds: 300                 # keep passing the health check
                                                 # through an RDS failover

upstreams:
  - provider: bedrock
    region: <your-region>                        # match $AWS_REGION so the IAM
                                                 # policy's ARNs cover it
    auth: {}                                     # AWS default credential chain:
                                                 # ECS task role, or IRSA on EKS
```

Only the `oidc` block is Okta-specific. To use Microsoft Entra ID instead, set `issuer` to `https://login.microsoftonline.com/<tenant-id>/v2.0`, drop `userinfo_fallback` and the `groups` scope, and note that Entra emits group Object IDs rather than names, so [`managed.policies`](claude-apps-gateway-config.md#managed) must match on the GUIDs, or on App Roles with `oidc.groups_claim: roles`. See [Identity provider setup](claude-apps-gateway-deploy.md#identity-provider-setup).

5

Store secrets in AWS Secrets Manager

Create three secrets; the execution role from the IAM step can already read them:

```python
aws secretsmanager create-secret --name gateway-jwt-secret \
  --secret-string "$(openssl rand -base64 32)"
aws secretsmanager create-secret --name gateway-oidc-client-secret \
  --secret-string '<your-okta-client-secret>'
aws secretsmanager create-secret --name gateway-postgres-url \
  --secret-string "$GATEWAY_POSTGRES_URL"
```

Note the ARN each call prints; the ECS task definition references secrets by ARN.

Literal `--secret-string` arguments are visible in the process table and in audit/EDR logs while each command runs. On a shared or monitored host, put the value in a `0600` file and pass `--secret-string file://<path>` instead. The bundle’s `setup.sh` keeps secret values off process argv the same way, passing `0600` temporary files to `--cli-input-json`.

Unlike the secrets, `gateway.yaml` itself contains no secret values, because every credential resolves at boot through [`${VAR}` or `${file:...}` expansion](claude-apps-gateway-config.md#secret-expansion). How everything reaches the container differs by track:

- On ECS, the next step’s build copies `gateway.yaml` into the image at `/etc/claude/gateway.yaml`, and the task definition injects the three secrets as environment variables via its `secrets` field, so the YAML references `${GATEWAY_JWT_SECRET}`, `${OIDC_CLIENT_SECRET}`, and `${GATEWAY_POSTGRES_URL}`.
- On EKS, mount `gateway.yaml` from a ConfigMap and the secrets as files at `/secrets`, referenced as `${file:/secrets/...}`. Source the Kubernetes Secrets from Secrets Manager with External Secrets Operator or the Secrets Store CSI driver’s AWS provider, or create them directly with `kubectl`.

6

Build and push the image to Amazon ECR

Build the image per the [container image requirements](claude-apps-gateway-deploy.md#container-image), placing the `linux-x64` glibc binary at `./claude` in the build context. Write your own Dockerfile per those requirements or start from the bundle’s [`Dockerfile`](https://github.com/anthropics/claude-code/blob/main/examples/gateway/aws/Dockerfile), which copies the filled-in `gateway.yaml` from the previous steps into the image at `/etc/claude/gateway.yaml`. On ECS that embedded copy is how the configuration reaches the container, which is why the build comes after the file is written. The EKS track instead mounts `gateway.yaml` from a ConfigMap at deploy, so the embedded copy is unused there.The image also carries the AWS RDS certificate bundle as the trust anchor for the connection string’s `sslmode=verify-full`, so download it into the build context first. AWS rotates the bundle (new regional CAs get appended), so download it per build rather than pinning a checksum or committing it:

```python
curl -fL --proto '=https' -o rds-global-bundle.pem \
  https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem
```

The container image requirements don’t cover the bundle, so if you write your own Dockerfile, add the two lines that copy and trust it; the bundle’s `Dockerfile` already includes both:

```python
COPY rds-global-bundle.pem /etc/claude/rds-global-bundle.pem
ENV NODE_EXTRA_CA_CERTS=/etc/claude/rds-global-bundle.pem
```

Create the ECR repository and sign Docker in to it. Immutable tags mean the `<version>` tag the deploy step pins cannot later be silently re-pointed at a different image:

```python
aws ecr create-repository --repository-name claude-gateway \
  --image-tag-mutability IMMUTABLE \
  --image-scanning-configuration scanOnPush=true
aws ecr get-login-password --region "$AWS_REGION" \
  | docker login --username AWS --password-stdin \
    "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
```

Build and push the image. The task definition below runs `linux/amd64`, so the platform must match here; for Fargate on ARM64 (Graviton), build `linux/arm64` with the `linux-arm64` binary and set `cpuArchitecture` to `ARM64` instead:

```python
docker build --platform=linux/amd64 \
  -t "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/claude-gateway:<version>" .
docker push "${ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/claude-gateway:<version>"
```

7

Deploy

- ECS Fargate

- EKS

Create the cluster and a log group for the gateway’s stderr, which carries both its audit events and operational logs. Retention is a separate call, and without one CloudWatch keeps the logs forever; align the 90 days with your audit retention policy:

```python
aws ecs create-cluster --cluster-name claude-gateway
aws logs create-log-group --log-group-name /ecs/claude-gateway
aws logs put-retention-policy --log-group-name /ecs/claude-gateway \
  --retention-in-days 90
```

Write the task definition. The task role carries the Bedrock permission and the execution role injects the secrets; use the secret ARNs from the Secrets Manager step:

claude-gateway-task.json

```python
{
  "family": "claude-gateway",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "1024",
  "memory": "2048",
  "runtimePlatform": { "cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX" },
  "executionRoleArn": "arn:aws:iam::<account-id>:role/claude-gateway-execution",
  "taskRoleArn": "arn:aws:iam::<account-id>:role/claude-gateway-task",
  "containerDefinitions": [
    {
      "name": "gateway",
      "image": "<account-id>.dkr.ecr.<region>.amazonaws.com/claude-gateway:<version>",
      "portMappings": [{ "containerPort": 8080 }],
      "secrets": [
        { "name": "GATEWAY_JWT_SECRET",   "valueFrom": "<gateway-jwt-secret ARN>" },
        { "name": "OIDC_CLIENT_SECRET",   "valueFrom": "<gateway-oidc-client-secret ARN>" },
        { "name": "GATEWAY_POSTGRES_URL", "valueFrom": "<gateway-postgres-url ARN>" }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/claude-gateway",
          "awslogs-region": "<region>",
          "awslogs-stream-prefix": "gateway"
        }
      }
    }
  ]
}
```

Register it:

```python
aws ecs register-task-definition --cli-input-json file://claude-gateway-task.json
```

Put an internal ALB in front with a target group that health-checks the gateway. `--ip-address-type ipv4` matters: an internal dual-stack ALB publishes public-range AAAA records, which the `/login` private-network check rejects:

```python
ALB_ARN="$(aws elbv2 create-load-balancer --name claude-gateway \
  --scheme internal --type application --ip-address-type ipv4 \
  --subnets $PRIVATE_SUBNETS --security-groups "$ALB_SG" \
  --query 'LoadBalancers[0].LoadBalancerArn' --output text)"

TG_ARN="$(aws elbv2 create-target-group --name claude-gateway \
  --protocol HTTP --port 8080 --vpc-id "$VPC_ID" --target-type ip \
  --health-check-path /readyz \
  --query 'TargetGroups[0].TargetGroupArn' --output text)"
```

Add the HTTPS listener. `--ssl-policy` pins a modern TLS floor, since omitting it falls back to the legacy `ELBSecurityPolicy-2016-08` default, which still accepts TLS 1.0/1.1.The ALB closes a connection after 60 seconds with no data by default. The gateway’s keepalive pings keep streams inside that default, so raising the timeout adds margin above the ping cadence; the [Troubleshooting](#troubleshooting) row on dropped streams covers the mechanism and older gateways. The commands below add the listener and raise the timeout:

```python
aws elbv2 create-listener --load-balancer-arn "$ALB_ARN" \
  --protocol HTTPS --port 443 \
  --ssl-policy ELBSecurityPolicy-TLS13-1-2-2021-06 \
  --certificates CertificateArn=<your-acm-certificate-arn> \
  --default-actions Type=forward,TargetGroupArn="$TG_ARN"

aws elbv2 modify-load-balancer-attributes --load-balancer-arn "$ALB_ARN" \
  --attributes Key=idle_timeout.timeout_seconds,Value=3600
```

Create the service. The deployment circuit breaker rolls a deployment whose tasks keep failing, from a bad image or an unbootable config, back to the last steady state instead of relaunching failing tasks forever:

```python
aws ecs create-service --cluster claude-gateway --service-name claude-gateway \
  --task-definition claude-gateway --desired-count 1 --launch-type FARGATE \
  --deployment-configuration "deploymentCircuitBreaker={enable=true,rollback=true}" \
  --health-check-grace-period-seconds 60 \
  --network-configuration "awsvpcConfiguration={subnets=[$(echo $PRIVATE_SUBNETS | tr ' ' ',')],securityGroups=[$GW_SG],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=$TG_ARN,containerName=gateway,containerPort=8080"
```

The 60-second grace period gives a cold task time to pull the image, connect to the store, and answer its first health check before ECS starts counting failures against the deployment.The target group’s health check on `GET /readyz` verifies the store is reachable, so a task that can’t reach Postgres never enters rotation. To keep tasks passing the check through a short database outage such as an RDS failover, set `store.readiness_grace_seconds` as described in [Outage behavior](claude-apps-gateway-deploy.md#outage-behavior), which also covers the `/healthz` alternative.The tasks run in private subnets with no public IP, so all egress (to Bedrock, your IdP, Secrets Manager, ECR, and CloudWatch Logs) goes through the NAT gateway. To keep Bedrock traffic off the public path, create a `bedrock-runtime` interface VPC endpoint and point the upstream’s `base_url` at it, as shown in the [Bedrock upstream reference](claude-apps-gateway-config.md#amazon-bedrock); the IdP still needs internet egress.Finish by giving developers a privately resolvable hostname: in a Route 53 private hosted zone, alias the gateway’s internal DNS name to the ALB, and set `listen.public_url` to that hostname. The ALB’s own `*.elb.amazonaws.com` name resolves to private addresses on an internal ALB, but it can’t carry your ACM certificate, so use your own name.Update the OAuth client’s authorized redirect URI to `<public_url>/oauth/callback` before the first sign-in. After changing `public_url`, rebuild and push the image under a new tag, register a new task definition revision, and redeploy. On ECS the setting lives in the image’s embedded `gateway.yaml`, and the gateway builds its public origin only from that setting, ignoring `X-Forwarded-Host` and `X-Forwarded-Proto`. `X-Forwarded-For` is honored for client IPs only when `listen.trusted_proxies` is set.

This track needs `kubectl` and `eksctl` installed locally, and an existing EKS cluster with an IAM OIDC provider and the AWS Load Balancer Controller installed. The cluster must be on `$VPC_ID` so pods can reach the RDS private endpoint, and the `claude-gateway-db` security group must admit the cluster’s pod or node security group in place of `$GW_SG`.On EKS the gateway gets its Bedrock credentials through IRSA rather than the ECS roles. The `ecs-tasks.amazonaws.com` trust policy from the IAM step does not apply here; IRSA needs a role whose trust policy federates on the cluster’s OIDC provider, scoped to `system:serviceaccount:claude-gateway:gateway`. `eksctl create iamserviceaccount` creates that role, attaches the policies, and annotates the Kubernetes service account with the role ARN in one step. Turn the two policy documents from the IAM step into managed policies it can attach:

```python
BEDROCK_POLICY_ARN="$(aws iam create-policy --policy-name claude-gateway-bedrock-invoke \
  --policy-document file://bedrock-invoke.json --query Policy.Arn --output text)"
SECRETS_POLICY_ARN="$(aws iam create-policy --policy-name claude-gateway-secrets-read \
  --policy-document file://secrets-read.json --query Policy.Arn --output text)"

kubectl create namespace claude-gateway
eksctl create iamserviceaccount --cluster <your-cluster> --region "$AWS_REGION" \
  --namespace claude-gateway --name gateway --role-name claude-gateway \
  --attach-policy-arn "$BEDROCK_POLICY_ARN" \
  --attach-policy-arn "$SECRETS_POLICY_ARN" \
  --approve
```

The secrets policy is needed only when the pods read Secrets Manager themselves, as the Secrets Store CSI driver’s AWS provider does using the mounting pod’s service account; drop it if you create the Kubernetes Secrets another way. The provider needs both of the policy’s actions: it calls `DescribeSecret` when it reconciles rotated secrets, so a `GetSecretValue`-only grant mounts on the first deploy but stops picking up rotations.Deploy the gateway as a standard Deployment plus a Service and an Ingress, as described in [Kubernetes deployment](claude-apps-gateway-deploy.md#kubernetes), with:

- `serviceAccountName: gateway`
- `gateway.yaml` mounted from a ConfigMap and the secrets mounted at `/secrets`
- the readiness probe pointed at `GET /readyz`

For the front end, an Ingress managed by the AWS Load Balancer Controller provisions the internal ALB. Annotate it with:

- `alb.ingress.kubernetes.io/scheme: internal` and `alb.ingress.kubernetes.io/target-type: ip`
- `alb.ingress.kubernetes.io/ip-address-type: ipv4`, so no public-range AAAA records are published for the `/login` [private-network check](claude-apps-gateway.md#prerequisites) to reject
- `alb.ingress.kubernetes.io/inbound-cidrs: <your-corporate-cidr>`, so the controller-managed frontend security group admits only your corporate network in place of its `0.0.0.0/0` default
- `alb.ingress.kubernetes.io/certificate-arn` with the ACM certificate
- `alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS13-1-2-2021-06`, so the listener doesn’t fall back to the legacy default policy that accepts TLS 1.0 and 1.1
- `alb.ingress.kubernetes.io/load-balancer-attributes: idle_timeout.timeout_seconds=3600`, a margin above the gateway’s streaming keepalive; see [Troubleshooting](#troubleshooting)

With IRSA, the AWS SDK reads a projected service-account token and exchanges it with AWS STS, so the pod never needs the EC2 instance metadata service; an egress NetworkPolicy may block `169.254.169.254` for gateway pods. The node hop-limit problem in [Troubleshooting](#troubleshooting) below applies only to clusters that skip IRSA and rely on node instance roles.

8

Push the gateway URL to developer machines

The gateway is now running, but developers can’t reach it from `/login` until the gateway URL is on their machines. Deploy the full [managed settings snippet](claude-apps-gateway.md#set-the-gateway-url), with `forceLoginMethod`, `forceLoginGatewayUrl`, and the `parentSettingsBehavior: "merge"` opt-in, to each device via MDM. There is no gateway option in the login picker for a developer to select manually.


[​](#terraform-reference)

Terraform reference

The companion bundle at [`examples/gateway/aws`](https://github.com/anthropics/claude-code/tree/main/examples/gateway/aws) packages this page as code:

- **`setup.sh`** scripts the provisioning walkthrough above with the same `aws` commands, on the ECS Fargate track. It is idempotent: existing resources are detected and skipped, so re-running it is safe, and any default can be overridden via environment variable. You still create the Okta OIDC client secret and the ACM certificate yourself: a run without them skips the ECS/ALB deploy, names the missing inputs, and prints the `create-secret` command; create both and re-run. The Bedrock use case form and the Route 53 alias print as next steps rather than running automatically, and the client MDM push stays a manual step from this page.
- **`gateway.yaml.example`** is the configuration template from the gateway.yaml step, with the optional keys included commented out. Copy it to `gateway.yaml` and replace every `REPLACE_ME` before building.
- **`Dockerfile`** builds the runtime image from the prebuilt `linux-x64` binary and copies in your filled-in `gateway.yaml` at `/etc/claude/gateway.yaml`, plus the AWS RDS certificate bundle that anchors the store’s `sslmode=verify-full`. `setup.sh` downloads the bundle only when it isn’t already in the build context; delete the file and rebuild under a new tag to pick up an AWS CA rotation. The config file holds no secret values, since every credential resolves at boot through `${VAR}` expansion. A config edit therefore means a rebuild under a new tag; `setup.sh` automates this by tagging images with a hash of the file.
- **`terraform/`** provisions the same ECS Fargate scope declaratively: the security groups, IAM roles, ECR repository, RDS instance, Secrets Manager secrets, and the ECS service behind the internal ALB. The VPC and private subnets stay prerequisites, passed in as variables. Terraform creates the ECR repository but doesn’t build the image, and the service definition references the image, so the apply is two passes: a targeted apply for the repository, then the build and push, then the full apply. The bundle’s `terraform/README.md` covers the variables, remote state, and teardown.

Like this page, the bundle is a working example for customer-managed infrastructure rather than a supported production deployment; review and adapt it to your own environment before relying on it.


[​](#troubleshooting)

Troubleshooting

For gateway boot and login errors, see the platform-agnostic [troubleshooting table](claude-apps-gateway-deploy.md#troubleshooting). The entries below are specific to AWS.

| Symptom                                                                                                                                    | Cause                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | Fix                                                                                                                                                                                                                                                                                                            |
|--------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| CLI `/login`: `Gateway hosts must be on your organization's private network; <host> resolves to the public (or unrecognized) address <ip>` | The gateway name resolves to at least one public address. A dual-stack internal ALB publishes public-range AAAA records, and the [private-network check](claude-apps-gateway.md#prerequisites) requires every resolved address to be private                                                                                                                                                                                                                                                                                          | Create the ALB with `--ip-address-type ipv4`, or serve a separate internal-only DNS name with no public AAAA record                                                                                                                                                                                            |
| Every Bedrock request returns 502; log shows `Could not load credentials from any providers`                                               | The task runs on the ECS EC2 launch type without a task role, or the pod runs on an EKS node without IRSA, so credentials come from instance metadata, which IMDSv2’s default hop limit of 1 stops inside a container. Neither track on this page is affected: Fargate task roles and IRSA don’t use instance metadata                                                                                                                                                                                                                      | Prefer task roles and IRSA. Where instance credentials are unavoidable, raise the hop limit with `aws ec2 modify-instance-metadata-options --instance-id <id> --http-put-response-hop-limit 2`; the [platform-agnostic table](claude-apps-gateway-deploy.md#troubleshooting) covers the tradeoffs        |
| Bedrock requests return `403 AccessDeniedException`                                                                                        | The account hasn’t submitted Anthropic’s one-time use case form, the automatic AWS Marketplace subscription that starts on the account’s first invoke hasn’t finished yet, or the task role’s policy is missing the inference-profile or foundation-model ARNs                                                                                                                                                                                                                                                                              | Submit the use case form from the Bedrock console’s Model catalog; if it was just submitted or this is the account’s first invoke, retry after a few minutes. Grant `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream` on both ARN families.                                                    |
| Bedrock returns a `ValidationException` saying on-demand throughput isn’t supported                                                        | A custom `models:` entry maps to a bare foundation-model ID that the region serves only through inference profiles                                                                                                                                                                                                                                                                                                                                                                                                                          | Map the model to its cross-region inference profile ID (`us.anthropic.*`) instead; the built-in catalog already does this                                                                                                                                                                                      |
| ECS task stops with `ResourceInitializationError` before the gateway logs anything                                                         | The execution role can’t read the Secrets Manager secrets, or the private subnets have no path to Secrets Manager or ECR                                                                                                                                                                                                                                                                                                                                                                                                                    | Grant `secretsmanager:GetSecretValue` on the three `gateway-` secrets’ ARNs to the execution role, and provide egress via the NAT gateway, or, without one, interface endpoints for Secrets Manager, ECR, and CloudWatch Logs, which the `awslogs` driver needs at the same stage, plus an S3 gateway endpoint |
| Gateway boot exits with a Postgres connection-timeout error                                                                                | The database security group doesn’t admit the gateway’s security group on 5432, or the service runs outside the database’s VPC                                                                                                                                                                                                                                                                                                                                                                                                              | Allow 5432 from the gateway’s security group on the database’s, and run the service in the same VPC as the DB subnet group                                                                                                                                                                                     |
| Gateway boot exits with a Postgres TLS certificate verification error                                                                      | The connection string sets `sslmode=verify-full` but the image doesn’t trust the RDS CA bundle: the bundle wasn’t copied into the image, or `NODE_EXTRA_CA_CERTS` doesn’t point at it                                                                                                                                                                                                                                                                                                                                                       | Add the build step’s two Dockerfile lines that copy the bundle and set `NODE_EXTRA_CA_CERTS`, then rebuild, push under a new tag, and redeploy                                                                                                                                                                 |
| Streaming responses drop mid-stream after a quiet period                                                                                   | A gateway older than v2.1.229 on a Bedrock or Claude Platform on AWS upstream sends nothing while the upstream is quiet, for example during extended thinking with no streamed output. The ALB closes a connection after 60 seconds with no data by default, so it cuts the stream at that gap. Gateways v2.1.229 and later keep a quiet stream under that timeout: on those upstreams the gateway emits an SSE `ping` event once about 15 seconds pass with no stream data, and on an Anthropic API upstream it relays the API’s own pings | Update the gateway to v2.1.229 or later, or set the `idle_timeout.timeout_seconds` attribute to `3600`, via `modify-load-balancer-attributes` or the `load-balancer-attributes` Ingress annotation on EKS                                                                                                      |


[​](#telemetry)

Telemetry

The gateway gives you per-developer usage metrics without any per-machine OTEL configuration. Claude Code emits OpenTelemetry (OTLP) metrics, logs, and opt-in traces; [Monitoring usage](monitoring-usage.md) covers everything the CLI reports. In sessions signed in through `/login`, the CLI [stamps each export](monitoring-usage.md#standard-attributes) with the authenticated IdP identity attributes `user.id`, `user.email`, and `user.groups`, so usage rolls up per developer. The gateway itself is an authenticated OTLP relay. Set [`telemetry.forward_to`](claude-apps-gateway-config.md#telemetry) together with `listen.public_url`, and it pushes the OTEL exporter settings to every connected client and forwards their OTLP traffic verbatim to each destination you list. Each destination opts into metrics, logs, and traces independently, and the default is metrics only; see the [`telemetry` reference](claude-apps-gateway-config.md#telemetry) for the per-signal fields and their sensitivity tradeoffs. The gateway doesn’t buffer, aggregate, or store telemetry, so where the data lands is entirely the collector’s exporter configuration. Client telemetry is off by default; configuring `telemetry.forward_to` is what turns it on for connected developers, and each interactive client shows a security approval dialog for the pushed settings, as described in the [configuration reference](claude-apps-gateway-config.md#telemetry). On AWS, each signal maps to a destination as follows.


[​](#client-metrics-logs-and-traces)

Client metrics, logs, and traces

Point `telemetry.forward_to` at an OpenTelemetry collector, such as the [AWS Distro for OpenTelemetry (ADOT) collector](https://aws-otel.github.io/), and export from there to Amazon CloudWatch, Amazon Managed Service for Prometheus, or any OTLP backend. Run the collector as its own internal service reachable over `https://`; the [`telemetry` reference](claude-apps-gateway-config.md#telemetry) covers the loopback exception and `CLAUDE_GATEWAY_ALLOW_LOOPBACK`.


[​](#gateway-logs)

Gateway logs

On ECS Fargate, no extra setup: the `awslogs` driver delivers the gateway’s stderr, which carries its audit events and operational logs, to the `/ecs/claude-gateway` log group created above. On EKS, pod logs don’t reach CloudWatch by default, so the audit trail is lost until you install log collection: the Amazon CloudWatch Observability add-on with container log capture enabled, or a Fluent Bit DaemonSet. On either track, query the logs with CloudWatch Logs Insights and drive alarms from metric filters.


[​](#container-metrics)

Container metrics

Enable Container Insights on the cluster with `aws ecs update-cluster-settings --cluster claude-gateway --settings name=containerInsights,value=enabled` for per-task CPU, memory, and network. On EKS, install the Amazon CloudWatch Observability add-on.


[​](#spend)

Spend

Telemetry shows usage after the fact; [spend limits](claude-apps-gateway-spend-limits.md) are the gateway’s live per-developer view and enforcement.


[​](#cost-attribution)

Cost attribution

The gateway signs every Bedrock request with its own principal, the ECS task role or EKS IRSA role, so by default AWS sees all of that spend under one IAM principal. There are two ways to split it in AWS’s own billing data, and they combine.


[​](#per-developer-with-assume_role)

Per developer with `assume_role`

Create a second IAM role that holds the Bedrock permissions and trusts the gateway’s principal, grant that principal `sts:AssumeRole` on it, and set [`assume_role`](claude-apps-gateway-config.md#per-developer-aws-cost-attribution) with `session_name: email` on the Bedrock upstream. The gateway then assumes that role once per developer per hour with the session name set to their email and signs their requests with the result. Requires a gateway running Claude Code v2.1.281 or later. The role can also be in another AWS account: see [Bedrock in another AWS account](claude-apps-gateway-config.md#bedrock-in-another-aws-account). In Terraform, next to the task role in the [Terraform bundle](#terraform-reference):

```python
resource "aws_iam_role" "bedrock_user" {
  name = "claude-gateway-bedrock-user"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{ Effect = "Allow", Action = "sts:AssumeRole", Principal = { AWS = aws_iam_role.task.arn } }]
  })
}
resource "aws_iam_role_policy" "bedrock_user_invoke" {   # same Bedrock policy as the task role's
  role   = aws_iam_role.bedrock_user.id
  policy = aws_iam_role_policy.bedrock_invoke.policy
}
resource "aws_iam_role_policy" "task_assume_bedrock_user" {
  role   = aws_iam_role.task.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [{ Effect = "Allow", Action = "sts:AssumeRole", Resource = aws_iam_role.bedrock_user.arn }]
  })
}
```

With `assume_role` set the gateway signs every Bedrock call, the free `CountTokens` call for spend metering included, with the assumed role’s credentials, so the gateway’s principal needs a Bedrock policy of its own only for an upstream without `assume_role`. Because the gateway calls STS at request time, the private subnets need a path to `sts.<region>.amazonaws.com`. The NAT gateway from the prerequisites provides one, and so does an STS interface VPC endpoint that answers for that hostname. Each active developer costs one STS call per hour per gateway replica. Each developer’s requests reach AWS as the principal `arn:aws:sts::<account>:assumed-role/<role>/<email>`. To see spend per principal, use a billing export that includes IAM principal data; AWS’s [IAM principal cost allocation](https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/iam-principal-cost-allocation.html) page covers how to enable it and which billing tools show it.


[​](#per-team-with-application-inference-profiles)

Per team with application inference profiles

This route uses only the [`models`](claude-apps-gateway-config.md#models) and [`managed`](claude-apps-gateway-config.md#managed) sections. Create one Bedrock [application inference profile](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles-create.html) per team and model, tag each profile with the team, and activate that tag as a cost allocation tag. Then give each team its own model id in `gateway.yaml` and pin each IdP group to its team’s ids:

```python
models:
  # Include the served model's id, such as claude-opus-4-8, in each id so the gateway's spend meter can price it.
  - id: platform-claude-opus-4-8
    upstream_model:
      bedrock: arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/abc123
  - id: data-claude-opus-4-8
    upstream_model:
      bedrock: arn:aws:bedrock:us-east-1:123456789012:application-inference-profile/def456
managed:
  policies:
    - match: {groups: [team-platform]}
      cli: {availableModels: [platform-claude-opus-4-8], enforceAvailableModels: true}
    - match: {groups: [team-data]}
      cli: {availableModels: [data-claude-opus-4-8], enforceAvailableModels: true}
    - match: {}
      cli: {availableModels: [claude-opus-4-8, claude-sonnet-4-6], enforceAvailableModels: true}
```

Tell developers in a pinned team to start Claude Code with `--model platform-claude-opus-4-8`, using their team’s id, because a session started without it runs the default model, which the gateway refuses for them. The gateway enforces `availableModels` on every request, not only in the model picker, and AWS billing groups the spend by the tag you activated. Without the `match: {}` catch-all, a developer who matches no policy gets every model in the catalog and can bill either team’s profile. The costs:

- The config grows with teams times models
- The role that signs this upstream’s Bedrock requests must also be allowed to invoke the `application-inference-profile/*` ARNs. That role is the gateway’s principal, or with `assume_role` the role it assumes.

See [How requests are priced](claude-apps-gateway-spend-limits.md#how-requests-are-priced) for how the gateway’s own spend meter prices these ids.


[​](#let-a-developer-choose-which-team-to-bill)

Let a developer choose which team to bill

To let a developer who works for two teams choose which team each session bills, give that pair of teams an IdP group and a policy of its own.

1

Add a group for the pair

In your IdP, add the developer to a new group, such as `team-platform-and-data`. If they’re already signed in, have them run `/login` again, or wait for the next [silent refresh](claude-apps-gateway-config.md#managed), so the gateway sees the new group.

2

List its policy first

Put the group’s policy above the single-team policies and allow both teams’ ids in it. Copy in any other settings the two team policies set.

```python
managed:
  policies:
    - match: {groups: [team-platform-and-data]}
      cli: {availableModels: [platform-claude-opus-4-8, data-claude-opus-4-8], enforceAvailableModels: true}
    - match: {groups: [team-platform]}
      cli: {availableModels: [platform-claude-opus-4-8], enforceAvailableModels: true}
    - match: {groups: [team-data]}
      cli: {availableModels: [data-claude-opus-4-8], enforceAvailableModels: true}
    - match: {}
      cli: {availableModels: [claude-opus-4-8, claude-sonnet-4-6], enforceAvailableModels: true}
```

3

Start each session on a team's id

Have the developer pass the id of the team the work is for, such as `claude --model data-claude-opus-4-8`.

The gateway applies the [first policy that matches](claude-apps-gateway-config.md#managed), so a developer in only the two team groups gets the `team-platform` policy. Their sessions on the data team’s id fail with `is not in your role's availableModels allowlist`, naming Claude Code’s default model and not the id they typed. If you list both team groups in one policy’s `groups` instead, everyone in either team can bill either team’s profile. In the gateway’s own [spend limits](claude-apps-gateway-spend-limits.md#set-a-cap), the developer has one total and one cap per period, by default the lowest of their groups’ caps, whichever id a session uses. To keep one team’s lower cap from stopping work for the other, set a per-user cap for the developer.


[​](#next-steps)

Next steps

- [Configuration reference](claude-apps-gateway-config.md): every `gateway.yaml` option, including `managed.policies` and `telemetry`
- [Deployment and operations](claude-apps-gateway-deploy.md): IdP setup, health checks, JWT secret rotation, upgrades, and the security model
- [Claude apps gateway overview](claude-apps-gateway.md): quickstart and connecting developers
- [AWS samples for Claude apps gateway](https://github.com/aws-samples/anthropic-on-aws/tree/main/claude-apps-gateway): AWS-maintained deployment samples covering a range of customer environments

---

## Deploy Claude apps gateway on Google Cloud - Claude Code Docs

Source: https://code.claude.com/docs/en/claude-apps-gateway-on-gcp
Path: 13-Enterprise-Admin/claude-apps-gateway-on-gcp.md

## On this page

- [What you’ll build](#what-you%E2%80%99ll-build)
- [Prerequisites](#prerequisites)
- [Deploy the gateway](#deploy-the-gateway)
- [Terraform reference](#terraform-reference)
- [Troubleshooting](#troubleshooting)
- [Next steps](#next-steps)

Claude apps gateway

# Deploy Claude apps gateway on Google Cloud

Copy pageCopy page

A worked example of running Claude apps gateway on Google Cloud: Cloud Run or GKE, Cloud SQL for PostgreSQL, Secret Manager, and service-account auth to Google Cloud’s Agent Platform.

Copy pageCopy page

This page walks through one way to run Claude apps gateway on Google Cloud. The configuration is a working example for customer-managed infrastructure rather than a supported production deployment; use it to see how the pieces fit together before adapting it to your own environment. For the platform-agnostic requirements, see the [deployment guide](claude-apps-gateway-deploy.md).

This example provisions Claude apps gateway on Google Cloud with Google Cloud’s Agent Platform as the model upstream, using either Cloud Run or GKE for compute. Google Workspace is the example identity provider (IdP), but any OpenID Connect (OIDC) compliant IdP works; only the `oidc` block changes. See [Identity provider setup](claude-apps-gateway-deploy.md#identity-provider-setup) for per-IdP details.


[​](#what-you’ll-build)

What you’ll build

The deployment consists of:

- **Cloud Run** service or **GKE** Deployment running the gateway container
- **Artifact Registry** repository for the gateway image
- **Cloud SQL for PostgreSQL** instance, private IP only, for the gateway’s [store](claude-apps-gateway-config.md#store)
- **Secret Manager** secrets for `gateway.yaml`, the JWT signing key, the OIDC client secret, and the Postgres URL
- **Service account** with `roles/aiplatform.user`, attached directly on Cloud Run or bound via Workload Identity on GKE
- **HTTPS front end** that you provide: an internal Application Load Balancer in front of Cloud Run, which this walkthrough configures the gateway for but doesn’t create, or an internal **GKE Ingress** of class `gce-internal` on GKE


[​](#prerequisites)

Prerequisites

- A GCP project with billing enabled, and permission to create the resources above
- The `gcloud` CLI, authenticated with `gcloud auth login`, and Docker installed locally
- For the GKE track: `kubectl`, and a GKE cluster on the VPC created in the walkthrough below
- Access to the Claude models you need in Model Garden, in a region that publishes them
- A Google Workspace OAuth 2.0 web-application client with redirect URI `https://<gateway-host>/oauth/callback`; see [Identity provider setup](claude-apps-gateway-deploy.md#identity-provider-setup)
- A TLS hostname for the gateway, typically an internal DNS name pointing at the load balancer

Set the project and region once:

```python
export PROJECT_ID=<your-project>
export REGION=us-east5   # a region where the Claude models you need are published in Model Garden
gcloud config set project "$PROJECT_ID"
```


[​](#deploy-the-gateway)

Deploy the gateway

The steps below provision the full deployment with `gcloud` commands.

1

Enable APIs

Enable the service APIs the walkthrough uses:

```python
gcloud services enable \
  aiplatform.googleapis.com \
  artifactregistry.googleapis.com \
  sqladmin.googleapis.com \
  secretmanager.googleapis.com \
  iamcredentials.googleapis.com \
  iam.googleapis.com \
  compute.googleapis.com \
  servicenetworking.googleapis.com \
  run.googleapis.com \
  container.googleapis.com
```

The APIs you need depend on the deployment path:

- `compute` and `servicenetworking`: needed for the private-IP Cloud SQL path
- `run`: Cloud Run only
- `container`: GKE only

2

Create the service account and grant IAM

The gateway runs as a dedicated service account with permission to call Google Cloud’s Agent Platform. It reaches Cloud SQL over the VPC with a password user, so no Cloud SQL IAM role is required:

```python
gcloud iam service-accounts create claude-gateway --display-name="Claude apps gateway"
SA="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"

gcloud projects add-iam-policy-binding "$PROJECT_ID" \
  --member="serviceAccount:${SA}" --role="roles/aiplatform.user" --condition=None
```

Then enable the Claude models for the project in Model Garden; models publish to specific regions, so check each model card.

3

Build and push the image to Artifact Registry

Build the image per the [container image requirements](claude-apps-gateway-deploy.md#container-image), using the `linux-x64` glibc binary, and push it:

```python
gcloud artifacts repositories create claude-gateway \
  --repository-format=docker --location="$REGION"
gcloud auth configure-docker "${REGION}-docker.pkg.dev" --quiet

# Cloud Run requires linux/amd64. --provenance=false avoids a buildx OCI
# image index that Cloud Run rejects.
docker build --platform=linux/amd64 --provenance=false \
  -t "${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" .
docker push "${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>"
```

4

Provision Cloud SQL for PostgreSQL

Create the instance on a VPC via Private Services Access so it has no public IP; this also satisfies projects where `constraints/sql.restrictPublicIp` is enforced:

```python
VPC=cc-gateway-vpc
gcloud compute networks create "$VPC" --subnet-mode=custom
gcloud compute networks subnets create cc-gateway-subnet \
  --network="$VPC" --region="$REGION" --range=10.0.0.0/24

# Private Services Access: one-time per VPC
gcloud compute addresses create "google-managed-services-${VPC}" \
  --global --purpose=VPC_PEERING --prefix-length=16 --network="$VPC"
gcloud services vpc-peerings connect \
  --service=servicenetworking.googleapis.com \
  --ranges="google-managed-services-${VPC}" --network="$VPC"

gcloud sql instances create claude-gateway-db \
  --database-version=POSTGRES_16 --tier=db-g1-small --region="$REGION" \
  --network="projects/${PROJECT_ID}/global/networks/${VPC}" --no-assign-ip
gcloud sql databases create claude_gateway --instance=claude-gateway-db
PGPASS="$(openssl rand -hex 24)"
gcloud sql users create gateway --instance=claude-gateway-db --password="$PGPASS"

PRIVATE_IP="$(gcloud sql instances describe claude-gateway-db \
  --format='value(ipAddresses[0].ipAddress)')"
GATEWAY_POSTGRES_URL="postgres://gateway:${PGPASS}@${PRIVATE_IP}:5432/claude_gateway?sslmode=require"
```

The Cloud Run or GKE runtime must be on, or routed into, this VPC.

5

Write gateway.yaml

The `upstreams` block points at Google Cloud’s Agent Platform with `auth: {}`, so the gateway authenticates via Application Default Credentials from the runtime service account. See the [configuration reference](claude-apps-gateway-config.md) for every field.Two `listen` fields describe what fronts the gateway:

- `public_url`: the external `https://` origin, required for any non-loopback bind; see the [`listen` reference](claude-apps-gateway-config.md#listen). The gateway builds the IdP `redirect_uri` and its discovery document only from this value, never from `X-Forwarded-*` headers.
- `trusted_proxies`: the front end’s source ranges. The gateway honors `X-Forwarded-For` only when the TCP peer is in this list, then walks the chain past trusted hops, so per-IP sign-in rate limits and audit events record developer IPs instead of the load balancer’s.

Set `trusted_proxies` to match your front end. An external GKE Ingress of class `gce` isn’t listed: it provisions a public forwarding-rule address, which the `/login` [private-network check](claude-apps-gateway.md#prerequisites) rejects.

| Front end                                                | `trusted_proxies`                                   |
|----------------------------------------------------------|-----------------------------------------------------|
| Cloud Run reached directly, no load balancer             | `[169.254.0.0/16]`                                  |
| Internal Application Load Balancer in front of Cloud Run | `169.254.0.0/16` plus your proxy-only subnet’s CIDR |
| GKE internal Ingress, class `gce-internal`               | Your proxy-only subnet’s CIDR                       |

The example below uses the internal-load-balancer-in-front-of-Cloud-Run values.

gateway.yaml

```python
listen:
  host: 0.0.0.0
  port: 8080
  public_url: https://claude-gateway.internal.example.com
  trusted_proxies: [169.254.0.0/16, <your-proxy-only-subnet-cidr>]

oidc:
  issuer: https://accounts.google.com
  client_id: <your-oauth-client-id>
  client_secret: ${OIDC_CLIENT_SECRET}           # GKE: ${file:/secrets/oidc-client-secret}
  allowed_email_domains: [example.com]
  # Google ignores offline_access; these yield refresh tokens:
  scopes: [openid, profile, email]
  extra_auth_params: { access_type: offline, prompt: consent }

session:
  jwt_secret: ${GATEWAY_JWT_SECRET}              # GKE: ${file:/secrets/jwt-secret}

store:
  postgres_url: ${GATEWAY_POSTGRES_URL}          # GKE: ${file:/secrets/postgres-url}
  # readiness_grace_seconds: 300                 # keep passing the readiness probe
                                                 # through a Cloud SQL failover

upstreams:
  - provider: vertex
    region: <your-region>                        # must match $REGION
    project_id: <your-project>
    auth: {}                                     # ADC via the runtime service account
```

Google id_tokens carry no `groups` claim. To use group-based policies in [`managed.policies`](claude-apps-gateway-config.md#managed) with Google Workspace as the IdP, configure [`oidc.google_groups`](claude-apps-gateway-config.md#oidc), which looks up each user’s groups through the Admin SDK Directory API using a service account with domain-wide delegation. Without it, match on `email_domain` instead.

6

Store secrets in Secret Manager

Create four secrets and grant `roles/secretmanager.secretAccessor` to the `claude-gateway` service account:

| Secret                       | Source                                          |
|------------------------------|-------------------------------------------------|
| `gateway-jwt-secret`         | `openssl rand -base64 32`                       |
| `gateway-oidc-client-secret` | Google Cloud Console → OAuth client             |
| `gateway-postgres-url`       | `$GATEWAY_POSTGRES_URL` from the Cloud SQL step |
| `gateway-config`             | the full `gateway.yaml` from the previous step  |

How the secrets reach the container differs by track:

- On GKE they mount as files via the Secret Manager CSI driver, and `gateway.yaml` references `${file:/secrets/...}`.
- On Cloud Run, which can’t mount multiple secrets into one directory, `gateway.yaml` mounts as a file and the other three inject as environment variables, so `gateway.yaml` references `${GATEWAY_JWT_SECRET}`, `${OIDC_CLIENT_SECRET}`, and `${GATEWAY_POSTGRES_URL}` instead.

7

Deploy

- Cloud Run

- GKE

The command below deploys for production behind an internal load balancer.

```python
gcloud run deploy claude-gateway \
  --image="${REGION}-docker.pkg.dev/${PROJECT_ID}/claude-gateway/gateway:<version>" \
  --region="$REGION" \
  --service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
  --min-instances=1 \
  --max-instances=8 \
  --timeout=3600 \
  --ingress=internal \
  --network="$VPC" --subnet=cc-gateway-subnet --vpc-egress=private-ranges-only \
  --set-secrets=/etc/claude/gateway.yaml=gateway-config:latest,GATEWAY_JWT_SECRET=gateway-jwt-secret:latest,OIDC_CLIENT_SECRET=gateway-oidc-client-secret:latest,GATEWAY_POSTGRES_URL=gateway-postgres-url:latest \
  --no-invoker-iam-check
```

Direct VPC egress, via `--network`, `--subnet`, and `--vpc-egress=private-ranges-only`, lets the service reach the Cloud SQL private IP directly. Public egress to Google Cloud’s Agent Platform endpoints and `accounts.google.com` goes directly to the internet rather than through the VPC, so no Cloud NAT is needed.

Each instance holds up to [`store.max_connections`](claude-apps-gateway-config.md#store) Postgres connections, five by default. Use [Size the connection pool](claude-apps-gateway-deploy.md#size-the-connection-pool) to check `--max-instances=8` against the connections your database has left for the gateway, and include the extra instances a rolling update starts.

The invoker IAM check must be open or disabled. The gateway runs its own OIDC and its clients carry no GCP token, so Cloud Run’s invoker check has to admit unauthenticated requests. The gateway’s OIDC sign-in authenticates the request once it reaches the container, with `allowed_email_domains` gating which domains may sign in.Two flags admit unauthenticated requests:

- `--no-invoker-iam-check`: disables the check with no `allUsers` binding to manage, and works under Domain Restricted Sharing
- `--allow-unauthenticated`: grants `allUsers` the `run.invoker` role; use it if your organization doesn’t allow `--no-invoker-iam-check`

Ingress restriction via `--ingress` is a separate, independent layer from the invoker check; keep it set to limit the service to your corporate network.By default the Cloud Run `*.run.app` URL resolves to a public address, which the `/login` [private-network check](claude-apps-gateway.md#prerequisites) rejects. Two topologies give developers a privately resolvable hostname, and Cloud Run provisions neither for you:

- **Internal Application Load Balancer**, the topology this page’s `gateway.yaml` assumes: provision an internal Application Load Balancer in front of the service with an internal DNS name and certificate, and set `listen.public_url` to that hostname. The `internal` ingress setting already admits traffic from internal Application Load Balancers; `internal-and-cloud-load-balancing` additionally admits external Application Load Balancers, whose public addresses the `/login` private-network check rejects, so no topology on this page needs it.
- **Internal-only ingress with no load balancer**: keep the deploy command as is and leave `listen.public_url` as the `*.run.app` URL, the default in the [reference assets](#terraform-reference) below. For `*.run.app` to resolve privately, your network team must already operate a Private Service Connect endpoint for Google APIs, a Cloud DNS private zone resolving `*.run.app` to it, and on-premises routing to that endpoint.

Google’s [private networking guide for Cloud Run](https://cloud.google.com/run/docs/securing/private-networking) covers the infrastructure both options need. Verify sign-in once the gateway is serving on a private hostname; until then, confirm the container booted from its logs in Cloud Run.Update the OAuth client’s authorized redirect URI to `<public_url>/oauth/callback` before the first sign-in. Redeploy after changing `public_url`, because the gateway builds its public origin only from that setting and ignores `X-Forwarded-Host` and `X-Forwarded-Proto`. `X-Forwarded-For` is honored for client IPs only when `listen.trusted_proxies` is set.

The cluster must be on the `$VPC` created in the Cloud SQL step so pods can reach the database’s private IP; VPC peering alone doesn’t work, because Cloud SQL private IP is itself a peered network and peering is non-transitive. To create a new cluster on that VPC, pass `--network="$VPC" --subnetwork=cc-gateway-subnet` to `gcloud container clusters create`.Enable Workload Identity on the cluster and its node pools, then bind the Google service account to the Kubernetes service account so pods inherit its credentials:

```python
gcloud container clusters update <cluster> --region="$REGION" \
  --workload-pool="${PROJECT_ID}.svc.id.goog"
# On a Standard cluster, existing node pools also need GKE_METADATA;
# Autopilot enables this by default.
gcloud container node-pools update <pool> --cluster=<cluster> \
  --region="$REGION" --workload-metadata=GKE_METADATA

kubectl create namespace claude-gateway
kubectl create serviceaccount gateway -n claude-gateway

gcloud iam service-accounts add-iam-policy-binding \
  "claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com" \
  --role roles/iam.workloadIdentityUser \
  --member "serviceAccount:${PROJECT_ID}.svc.id.goog[claude-gateway/gateway]"

kubectl annotate serviceaccount gateway -n claude-gateway \
  iam.gke.io/gcp-service-account="claude-gateway@${PROJECT_ID}.iam.gserviceaccount.com"
```

Deploy the gateway as a standard Deployment plus a Service and an internal Ingress, class `gce-internal`, as described in [Kubernetes deployment](claude-apps-gateway-deploy.md#kubernetes), with:

- `serviceAccountName: gateway`
- the Secret Manager CSI driver mounting secrets at `/secrets`
- the readiness probe pointed at `GET /readyz`

Attach a BackendConfig with a raised `timeoutSec` to the gateway Service: the load balancer backend service behind GKE Ingress defaults to a 30-second timeout, which cuts off long streaming responses.Don’t apply an egress NetworkPolicy that blocks `169.254.169.254` on a Workload Identity cluster; the pod must reach the metadata server for credentials. The gateway’s built-in [SSRF guard](claude-apps-gateway-deploy.md#threat-model-summary) is the defense there.The gateway logs a boot warning that the metadata endpoint is reachable and suggests applying an egress NetworkPolicy. Under Workload Identity that warning is expected, because the pod needs the endpoint.

8

Push the gateway URL to developer machines

The gateway is now running, but developers can’t reach it from `/login` until the gateway URL is on their machines. Deploy the full [managed settings snippet](claude-apps-gateway.md#set-the-gateway-url), with `forceLoginMethod`, `forceLoginGatewayUrl`, and the `parentSettingsBehavior: "merge"` opt-in, to each device via MDM. There is no gateway option in the login picker for a developer to select manually.


[​](#terraform-reference)

Terraform reference

The [reference deployment assets](https://github.com/anthropics/claude-code/tree/main/examples/gateway/gcp) automate the Cloud Run track on this page; the config and image assets apply to both tracks:

- `setup.sh`: an idempotent `gcloud` provisioner that walks the full Cloud Run path, from enabling APIs through the first deploy
- `terraform/`: the same deployment as infrastructure-as-code, for a greenfield deploy: a targeted apply to create the Artifact Registry repo, then build and push the image, then a full apply
- `gateway.yaml.example` and a `Dockerfile` for the distroless runtime image

The artifacts default Cloud Run ingress to `internal`, matching the deploy command on this page; that setting works with or without an internal Application Load Balancer in front of the service, and the artifacts don’t create the load balancer either. The artifacts also default the invoker layer to an `allUsers` `run.invoker` grant rather than `--no-invoker-iam-check`, the inverse of this page’s walkthrough; either works, and the choice depends on your organization’s policy constraints. The assets are provided as working examples, not as a supported production artifact; review and adapt them to your environment.


[​](#troubleshooting)

Troubleshooting

For gateway boot and login errors, see the platform-agnostic [troubleshooting table](claude-apps-gateway-deploy.md#troubleshooting). The entries below are specific to Google Cloud.

| Symptom                                                                                  | Cause                                                                                                                               | Fix                                                                                                                                                                                                                         |
|------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Cloud Run returns `403 Forbidden` before reaching the container                          | The invoker IAM check is still enabled                                                                                              | Deploy with `--no-invoker-iam-check`, or grant `allUsers` the `run.invoker` role with `--allow-unauthenticated`                                                                                                             |
| `--no-invoker-iam-check` rejected with `invoker_iam_disabled is not currently available` | Blocked by `constraints/run.managed.requireInvokerIam`                                                                              | Use `--allow-unauthenticated`. If Domain Restricted Sharing via `constraints/iam.allowedPolicyMemberDomains` blocks that too, use the GKE track, which exposes the gateway at the network layer with no `allUsers` binding. |
| `Container manifest type … must support amd64/linux` at deploy                           | Image was built on a non-amd64 host, or buildx emitted an OCI image index                                                           | Build with `--platform=linux/amd64 --provenance=false`                                                                                                                                                                      |
| Gateway boot exits with a Postgres connection-timeout error on Cloud Run                 | Service isn’t attached to the VPC, or Cloud SQL has no private IP on that VPC                                                       | Deploy with `--network` and `--subnet` for Direct VPC egress, and create the Cloud SQL instance with `--no-assign-ip` and `--network` pointing at the same VPC                                                              |
| Google Cloud’s Agent Platform requests return `403 PERMISSION_DENIED`                    | Runtime isn’t using the `claude-gateway` service account, or the model isn’t enabled in Model Garden for the project                | Set `--service-account` on Cloud Run or bind Workload Identity on GKE, and enable each Claude model in Model Garden for the target region                                                                                   |
| Streaming responses cut off after a fixed duration                                       | Front-end request timeout: the load balancer backend service behind GKE Ingress defaults to 30 seconds and Cloud Run to 300 seconds | Attach a BackendConfig with a raised `timeoutSec` on GKE, or deploy with `--timeout=3600` on Cloud Run                                                                                                                      |


[​](#next-steps)

Next steps

- [Configuration reference](claude-apps-gateway-config.md): every `gateway.yaml` option, including `managed.policies` and `telemetry`
- [Deployment and operations](claude-apps-gateway-deploy.md): IdP setup, health checks, JWT secret rotation, upgrades, and the security model
- [Claude apps gateway overview](claude-apps-gateway.md): quickstart and connecting developers

---

## Claude apps gateway spend limits - Claude Code Docs

Source: https://code.claude.com/docs/en/claude-apps-gateway-spend-limits
Path: 13-Enterprise-Admin/claude-apps-gateway-spend-limits.md

## On this page

- [Set a cap](#set-a-cap)
  - [Authenticate to the admin API](#authenticate-to-the-admin-api)
- [How enforcement works](#how-enforcement-works)
  - [How requests are priced](#how-requests-are-priced)
  - [Long-prompt rates](#long-prompt-rates)
  - [Postgres availability](#postgres-availability)
  - [Usage warnings in Claude Code](#usage-warnings-in-claude-code)
- [Admin API reference](#admin-api-reference)
  - [/effective](#%2Feffective)
  - [/audit](#%2Faudit)
  - [Pagination](#pagination)
- [Data lifecycle](#data-lifecycle)
- [Related](#related)

Claude apps gateway

# Claude apps gateway spend limits

Copy pageCopy page

Cap each developer’s spend through the Claude apps gateway by day, week, or month. Set limits with an Admin API and the gateway enforces them live on every request.

Copy pageCopy page

Spend limits cap how much each developer can spend through your [Claude apps gateway](claude-apps-gateway.md) in a given day, week, or month. When a developer passes their cap, the gateway returns `429` on their next request and blocks them until the period resets or an admin raises the cap. Use spend limits to give each developer, group, or the whole organization a ceiling on a credential everyone shares. By default, a Claude apps gateway forwards all inference through one shared upstream credential, so your provider’s bill attributes everything to that credential, not to individual developers. On Amazon Bedrock, [per-developer AWS cost attribution](claude-apps-gateway-config.md#per-developer-aws-cost-attribution) changes this. Without per-developer limits, one runaway agent fleet can spend the organization’s entire commitment. Spend limits are the gateway’s per-developer view and circuit breaker on top of that shared bill.


[​](#set-a-cap)

Set a cap

With the [`admin:`](claude-apps-gateway-config.md#admin) block configured in `gateway.yaml`, the gateway serves an admin API at `/v1/organizations/spend_limits` and enforces caps live on every inference request. Caps themselves are set through that API, not in `gateway.yaml`; each `POST /v1/organizations/spend_limits` request creates or replaces one cap from `{scope, amount, period}`. The API mirrors the wire shapes of Anthropic’s public [Admin API](../04-API-Reference/Other/manage-claude-admin-api.md) spend-limits endpoints, so an HTTP client written against that contract can target the gateway by changing its base URL. This request sets an org-wide default of \$500 per month for every developer:

```python
curl -sS https://claude-gateway.internal.example.com/v1/organizations/spend_limits \
  -H "x-api-key: $GATEWAY_ADMIN_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scope": {"type": "organization"}, "amount": "50000", "period": "monthly"}'
```

This request layers a tighter \$100-per-day cap on each member of the `contractors` group:

```python
curl -sS https://claude-gateway.internal.example.com/v1/organizations/spend_limits \
  -H "x-api-key: $GATEWAY_ADMIN_WRITE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"scope": {"type": "rbac_group", "rbac_group_id": "contractors"}, "amount": "10000", "period": "daily"}'
```

| Field        | Values                                      | Description                                                                                                                                                                                                                                                                                                                                                                                        |
|--------------|---------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `scope.type` | `user`, `rbac_group`, `organization`        | `user` targets one developer by their OpenID Connect (OIDC) `sub`, the stable user ID your identity provider assigns; pass it as `scope.user_id`. `rbac_group` targets an [IdP group](claude-apps-gateway-config.md#managed) by name; pass it as `scope.rbac_group_id`. `organization` is the org-wide default. The gateway accepts all three; Anthropic’s public `POST` is user-only today. |
| `amount`     | Whole-number string of USD cents, or `null` | `null` is unlimited. `"0"` is a zero cap, which blocks every request.                                                                                                                                                                                                                                                                                                                              |
| `period`     | `daily`, `weekly`, `monthly`                | A scope can hold one cap per period, and each enforces independently: a developer is blocked if over any of them.                                                                                                                                                                                                                                                                                  |

A group or organization cap is a per-seat default that each member inherits, not a shared pool. Per period, a developer’s effective cap resolves in this order: a per-user override, then the most restrictive of their group caps, then the org default, then unlimited. [`admin.group_limit_mode: max`](claude-apps-gateway-config.md#admin) flips the multi-group tie-break to least-restrictive instead.


[​](#authenticate-to-the-admin-api)

Authenticate to the admin API

Send one of:

- An `x-api-key` header matching a key in [`admin.write_keys`](claude-apps-gateway-config.md#admin) for full access, or `admin.read_keys` for `GET`-only access. Each key carries an `id` that appears in the audit log as `admin-key:<id>`, so give Terraform, CI, and each automation its own.
- A gateway bearer token whose `groups` claim includes one of [`admin.admin_groups`](claude-apps-gateway-config.md#admin). This is full access and audits as `oidc:<sub>`, so prefer it for human admins.


[​](#how-enforcement-works)

How enforcement works

On each `/v1/messages` request, the gateway looks up the developer’s caps and period-to-date spend in one Postgres query. A developer over any cap gets a `429` with `error.type: billing_error` and header `x-should-retry: false`. The message names the period and reset time, such as `spend limit reached (daily; resets 2026-08-08 00:00 UTC)`, followed by your [`admin.blocked_message`](claude-apps-gateway-config.md#admin) if set. When a developer exceeds several caps at once, the message names the cap that resets last. The response also carries a `retry-after` header with the seconds remaining until that reset. Before v2.1.225 on the gateway server, the message was `spend limit reached` with no period, reset time, or `retry-after` header. On v2.1.227 or later, the protocol reference at `<public_url>/protocol` also lists the exact usage-limit response headers and `429` body. Caps reset on UTC calendar boundaries: daily at 00:00 UTC, weekly on Monday, and monthly on the first. The gateway never blocks `/v1/messages/count_tokens`, because token counting is free.


[​](#how-requests-are-priced)

How requests are priced

After each response, a usage meter reads the token counts and adds the cost to the daily, weekly, and monthly counters. It never touches the bytes sent to the client, so a metering failure can’t break a response. The amounts are USD estimates, a circuit breaker rather than an invoice; for billing, reconcile against your provider’s usage reporting. The meter picks each request’s rates in this order:

1.  A matching [`pricing.overrides`](claude-apps-gateway-config.md#pricing) row for the upstream that served the request. Requires v2.1.227 or later.
2.  List price for the upstream model ID, the string the gateway sends to the provider, when the Claude Code cost table recognizes it. The table accepts Anthropic, Amazon Bedrock, Google Cloud’s Agent Platform, and Microsoft Foundry ID forms.
3.  List price for the [`models[].id`](claude-apps-gateway-config.md#models) you mapped to that upstream ID, for upstream strings that carry no model name, such as an Amazon Bedrock application-inference-profile ARN or a Microsoft Foundry deployment name. Requires v2.1.218 or later.
4.  The unknown-model tier of \$5/\$25 per million input/output tokens, so an ID the meter can’t place is never free. The gateway warns at boot and once per ID at runtime when it uses this tier.

Whichever rate applies, the meter then multiplies the amount by [`pricing.multiplier`](claude-apps-gateway-config.md#pricing), default `1`. Client aborts are billed too. When a stream ends without the upstream’s final usage frame, the meter bills a floor estimate of about four characters per output token for the text already sent to the client, so aborting requests early doesn’t evade a cap.


[​](#long-prompt-rates)

Long-prompt rates

The list price for [Claude Haiku 5.5](../20-Models/models-haiku-5-5-overview.md) has a [higher rate for prompts over a token threshold](../02-Claude-Code-CLI/model-config.md#haiku-5-5-context-window-and-pricing). When Haiku 5.5 serves a request whose prompt exceeds the threshold, a gateway server running Claude Code v2.1.293 or later prices the whole request at that rate. The meter counts input tokens, cache reads, and cache writes toward that threshold. If one of your `pricing.overrides` rows matches the request, that row’s rates apply at any prompt length.


[​](#postgres-availability)

Postgres availability

The pre-check queries Postgres with a two-second timeout. If the store is unreachable or times out, enforcement fails open by default: the request proceeds, the gateway logs a warning, and the response carries no `anthropic-ratelimit-unified-*` headers. Set [`enforcement.fail_closed_on_error: true`](claude-apps-gateway-config.md#enforcement) to fail closed instead, which returns the same `429 billing_error` but with the message `spend limit unavailable` and no period, reset time, or `retry-after` header. Fail-open keeps a store outage from becoming an inference outage; fail-closed guarantees no unmetered spend. Fail-open only helps while your load balancer or orchestrator still routes traffic to the gateway. See [Outage behavior](claude-apps-gateway-deploy.md#outage-behavior) for `store.readiness_grace_seconds`, which keeps replicas passing their readiness check through a short outage.


[​](#usage-warnings-in-claude-code)

Usage warnings in Claude Code

Claude Code warns a developer as they approach their cap: once utilization passes 75%, and again past 95% of their most-consumed cap. When the gateway blocks a request, Claude Code shows the gateway’s `429` message as is, including your `admin.blocked_message`. It also shows the cap in `/usage` and passes it to the developer’s [status line](../02-Claude-Code-CLI/statusline.md#spend-limit-fields) script. Each display needs a minimum Claude Code version on the developer’s machine and on the gateway server:

| What the developer sees                                                                                                                                                                         | Developer’s machine | Gateway server    |
|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------|:------------------|
| Usage warnings at 75% and 95%                                                                                                                                                                   | v2.1.225 or later   | v2.1.225 or later |
| A **Spend limit** bar in `/usage` with the percentage of their cap used and when it resets, and a `rate_limits.spend_limit` object in the status line input                                     | v2.1.251 or later   | v2.1.225 or later |
| Their estimated spend and the cap in US dollars in the **Spend limit** bar, such as “\$271.40 / \$500.00 spent this month”, and the same amounts plus the cap’s period in the status line input | v2.1.284 or later   | v2.1.284 or later |

The warnings and the percentage come from the `anthropic-ratelimit-unified-*` headers, which the gateway adds to each successful `/v1/messages` response for a developer who has a cap. The headers always describe the developer’s own cap: the gateway strips the upstream provider’s rate-limit headers, which describe your shared quota, and never forwards them. The spend a developer sees is the gateway’s own [estimate](#how-requests-are-priced), the same figure it enforces the cap with, and not an amount from your provider’s bill. Claude Code reads the dollar amounts with a separate request to the gateway. If you set `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` on developers’ machines, as [Compliance posture](claude-apps-gateway-deploy.md#compliance-posture) suggests, Claude Code skips that request. With that variable set, or with a gateway server older than v2.1.284, the bar and the status line stay percentage-only.


[​](#admin-api-reference)

Admin API reference

The endpoints below are served under `/v1/organizations/spend_limits`.

| Method and path                                | Description                                                                                                                                                  |
|------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `GET /v1/organizations/spend_limits`           | List configured caps, optionally filtered to one `scope_type` of `organization`, `rbac_group`, or `user`. Query: `?limit=&after_id=&before_id=&scope_type=`. |
| `POST /v1/organizations/spend_limits`          | Create or replace a cap for `{scope, period}`.                                                                                                               |
| `GET /v1/organizations/spend_limits/{id}`      | Fetch one cap by its `spl_`-prefixed ID.                                                                                                                     |
| `DELETE /v1/organizations/spend_limits/{id}`   | Delete one cap. Returns `{type: "spend_limit_deleted", id}`.                                                                                                 |
| `GET /v1/organizations/spend_limits/effective` | Resolved cap and to-date spend per principal per period.                                                                                                     |
| `GET /v1/organizations/spend_limits/audit`     | Admin mutation trail, newest-first. Query: `?limit=&after_id=`.                                                                                              |

Conventions mirror Anthropic’s Admin API:

- A `type` on every object
- `spl_`-prefixed IDs
- Amounts as whole-number strings of USD cents; `POST` rejects any other `currency` with `400`
- The `{type: "error", error: {type, message}, request_id}` error envelope
- A `request-id` response header on every admin response, success or error; error bodies also carry it as `request_id`

Every mutation writes a before/after row to `admin_audit` in the same transaction, attributed to `admin-key:<id>` or `oidc:<sub>`. The gateway serves the spend-limits endpoints only. Other Admin API surfaces, such as the `spend_limit_increase_requests` queue, aren’t part of the gateway’s admin API.


[​](#/effective)

`/effective`

`GET /v1/organizations/spend_limits/effective` returns Anthropic’s `SpendSummary` schema: each row is a principal for a period, with the resolved cap, period-to-date spend, and an `actor` object. Gateway-specific differences:

- `user_id` is the OIDC `sub`.
- `actor.name` and `actor.email_address` are `null` until the principal’s first inference request through the gateway. The gateway has no user directory; it records last-seen values from each user’s own session JWT.
- Each row also carries a `groups` array, the principal’s last-seen IdP groups. This is a gateway extension so an admin UI can show every cap tier that applies; Anthropic-shaped clients ignore it.
- Without a `user_ids[]` filter, it lists principals with recorded spend, because the gateway can’t enumerate all org members.

Group-sourced caps resolve against those last-seen groups with the same `group_limit_mode` tie-break that enforcement uses, so the viewer shows the cap that actually applies.

| Query parameter  | Description                                                                                             |
|------------------|---------------------------------------------------------------------------------------------------------|
| `user_ids[]`     | Repeatable. Filter to specific principals by OIDC `sub`.                                                |
| `period[]`       | Repeatable. Filter to `daily`, `weekly`, or `monthly` rows.                                             |
| `sort`           | `spend_desc` lists top spenders first. Requires exactly one `period[]`.                                 |
| `q`              | Case-insensitive substring filter over the OIDC `sub`, last-seen email, and last-seen display name.     |
| `limit` / `page` | Page size, 1–1000 with a default of 20, and the opaque cursor from the previous response’s `next_page`. |

`q=` and `user_ids[]=` ride GET query strings, so any fronting proxy or load balancer captures them in its access logs. If your PII log policy is strict, scrub these parameters there.


[​](#/audit)

`/audit`

Returns the spend-limit mutation trail: who changed which cap, with before/after snapshots, newest-first. `has_more` is exact. This endpoint follows the local Admin API conventions rather than a first-party wire shape.


[​](#pagination)

Pagination

The raw list pages by `after_id` and `before_id`, which are mutually exclusive `spl_…` IDs; results are ordered by creation and `has_more` reflects the traversal direction. `/effective` pages by the opaque `next_page` token passed back as `?page=`, with principals ordered ascending so pages stay stable while spend is being recorded. `limit` is 1–1000, default 20, on both. `/audit` pages by `after_id`, the numeric `id` of the last event on the previous page, and its `limit` defaults to 100.


[​](#data-lifecycle)

Data lifecycle

The gateway holds four spend-related tables; an hourly sweep enforces the retention windows:

| Table              | Contents                                                                      | Retention                                                                                                    |
|--------------------|-------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| `spend`            | Per-principal period-to-date counters in cents                                | [`admin.spend_retention_months`](claude-apps-gateway-config.md#admin), default 13                      |
| `spend_limits`     | The configured caps                                                           | Until deleted via the API                                                                                    |
| `admin_audit`      | The mutation trail                                                            | [`admin.audit_retention_days`](claude-apps-gateway-config.md#admin), default 365                       |
| `principal_emails` | Each principal’s last-seen email, display name, and IdP groups. Contains PII. | [`admin.identity_retention_days`](claude-apps-gateway-config.md#admin) since last activity, default 90 |

When a developer leaves, delete any per-user cap via `DELETE /v1/organizations/spend_limits/{id}`; their spend and identity rows age out on the retention windows above. To erase one person immediately, for offboarding or a data subject access request (DSAR), run `DELETE FROM principal_emails WHERE principal = '<sub>'` directly against the gateway database. That removes the only table holding their email, name, and groups. The `spend` and `admin_audit` rows reference the pseudonymous OIDC `sub` only and age out on their own windows.


[​](#related)

Related

- [`admin` and `enforcement` configuration](claude-apps-gateway-config.md#admin): enabling the admin API and tuning retention
- [Deployment guide](claude-apps-gateway-deploy.md#postgres): Postgres schema and backup guidance

---

## Claude apps gateway for Amazon Bedrock, Claude Platform on AWS, Google Cloud, and Microsoft Foundry - Claude Code Docs

Source: https://code.claude.com/docs/en/claude-apps-gateway
Path: 13-Enterprise-Admin/claude-apps-gateway.md

## On this page

- [Why Claude apps gateway](#why-claude-apps-gateway)
  - [Other gateway implementations](#other-gateway-implementations)
- [Quickstart](#quickstart)
  - [Prerequisites](#prerequisites)
  - [Steps](#steps)
- [Connect developers](#connect-developers)
  - [Set the gateway URL](#set-the-gateway-url)
  - [Set the gateway URL in user settings](#set-the-gateway-url-in-user-settings)
  - [Allow a gateway on public address space you own](#allow-a-gateway-on-public-address-space-you-own)
  - [Deliver policy to Claude Desktop sessions](#deliver-policy-to-claude-desktop-sessions)
  - [Which machines need the opt-in](#which-machines-need-the-opt-in)
  - [Set the opt-in](#set-the-opt-in)
  - [Restrict parent settings](#restrict-parent-settings)
  - [Deploy the locks](#deploy-the-locks)
  - [Lock behavior across sources](#lock-behavior-across-sources)
  - [Settings the locks don’t cover](#settings-the-locks-don%E2%80%99t-cover)
  - [How hook and skill keys in parent settings take effect](#how-hook-and-skill-keys-in-parent-settings-take-effect)
  - [Connect Claude Desktop](#connect-claude-desktop)
  - [CI pipelines and remote machines](#ci-pipelines-and-remote-machines)
  - [What’s enforced on developers](#whats-enforced-on-developers)
  - [What the organization can see](#what-the-organization-can-see)
- [Availability and limitations](#availability-and-limitations)
- [Next steps](#next-steps)

Claude apps gateway

# Claude apps gateway for Amazon Bedrock, Claude Platform on AWS, Google Cloud, and Microsoft Foundry

Copy pageCopy page

Run Claude Code through Amazon Bedrock, Claude Platform on AWS, Google Cloud, or Microsoft Foundry behind a self-hosted gateway with SSO sign-in, per-group model access, and OTLP telemetry.

Copy pageCopy page

The Claude apps gateway is designed for organizations that must, or prefer to, route inference through their own cloud provider, for example to meet [data residency](claude-apps-gateway-deploy.md#compliance-posture) requirements. If you don’t have this requirement, and want access to other features such as SCIM provisioning or Claude Code on web and mobile, Claude Enterprise may be a better fit. See the [feature availability](../02-Claude-Code-CLI/feature-availability.md) page for a full comparison of all deployment methods.Claude Code uses one sign-in at a time: a gateway sign-in takes the place of a Claude for Teams or Enterprise login. Gateway sessions don’t fetch the [server-managed settings](server-managed-settings.md) you set in the claude.ai admin console, so deliver them from the gateway’s [`managed` block](claude-apps-gateway-config.md#managed). Leave `forceLoginMethod` and `forceLoginOrgUUID` out of that block, or [some signed-in sessions exit at startup](claude-apps-gateway-config.md#client-side-managed-settings). See [Availability and limitations](#availability-and-limitations) for what gateway sessions lose.

Claude apps gateway is a self-hosted service that sits between your developers’ Claude Code clients and your model provider. Developers sign in with your corporate identity provider (IdP) instead of holding API keys or cloud credentials. The gateway holds the upstream credential, enforces model access and [managed settings](managed-settings.md) by IdP group, and relays usage telemetry to your own observability stack. It is included in the `claude` binary, so the same executable that runs Claude Code on a laptop runs the gateway server with `claude gateway --config gateway.yaml`. This page covers:

- [Why Claude apps gateway](#why-claude-apps-gateway), what it adds over running your own, and when something else fits better
- A [quickstart](#quickstart) with [prerequisites](#prerequisites) that takes a gateway from zero to a signed-in developer
- [Connecting developers](#connect-developers), including setting the gateway URL through managed settings
- [Availability and limitations](#availability-and-limitations) covering which Claude Code features work through the gateway and what the server supports

Companion pages go deeper. The [configuration reference](claude-apps-gateway-config.md) covers every option in the YAML file the quickstart writes, and the [deployment guide](claude-apps-gateway-deploy.md) covers per-IdP setup, Kubernetes and Cloud Run deployment, and operations.


[​](#why-claude-apps-gateway)

Why Claude apps gateway

The [gateway overview](gateways.md) covers what a gateway does and why you’d run one. Claude apps gateway is Anthropic’s own gateway, built into the `claude` binary and tested alongside each Claude Code release, so it forwards the `anthropic-beta` values and request fields Claude Code sends without operators maintaining a separate allowlist. Once deployed it gives you:

- **Credentials**: the upstream API key or cloud credential lives only in your infrastructure. Developers authenticate with corporate SSO and receive short-lived bearer tokens, so offboarding happens in your IdP. Deprovision a user and their gateway access expires within the session lifetime, one hour by default.
- **Access control**: your IdP groups map to model allowlists and [managed settings](managed-settings.md) policies. The gateway enforces model access server-side, rejecting requests for non-granted models, and selects each group’s managed settings policy, which the CLI applies at the [managed settings tier](../02-Claude-Code-CLI/settings.md#settings-precedence). Different teams get different models, tools, and permissions, and a developer can’t override what their policy locks.
- **Settings delivery**: the gateway delivers managed settings to signed-in clients itself, taking the place of [server-managed settings](server-managed-settings.md) from the claude.ai admin console.
- **Telemetry**: each configured destination receives [OpenTelemetry Protocol (OTLP) metrics](monitoring-usage.md) with token counts, model, user identity, and latency by default, with logs and traces as per-destination opt-ins.
- **Upstream routing**: clients speak the Anthropic Messages API to the gateway, and the gateway translates for each upstream, whether Amazon Bedrock, [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md), Google Cloud’s Agent Platform, Microsoft Foundry, or the Anthropic API, with failover between them. You can change regions, providers, or failover order without developers noticing or reconfiguring.

The gateway’s own data plane sends nothing to Anthropic infrastructure unless the Anthropic API is a configured upstream. You control where telemetry, audit logs, managed settings, and your developers’ IdP identity go, and the gateway sends none of them to Anthropic. For the remaining traffic the CLI process can send and how to close it, see [Compliance posture](claude-apps-gateway-deploy.md#compliance-posture).

For which Claude Code features work through the gateway and what the server itself supports, see [Availability and limitations](#availability-and-limitations) below. For decisions such as cost, bypass, running multiple gateways, and serverless platforms, see the [deployment guide](claude-apps-gateway-deploy.md#deployment).


[​](#other-gateway-implementations)

Other gateway implementations

If you already run an LLM gateway or API gateway that meets your needs, keep using it; [Other LLM gateways](llm-gateway.md) covers configuring Claude Code against it. The [gateway compatibility guide](llm-gateway-protocol.md) documents what Claude Code expects from any gateway: the endpoints it calls, the headers and body fields to forward, and what stops working when they’re stripped. A running Claude apps gateway also serves its own protocol reference at `GET /protocol`, which describes the endpoints it exposes to Claude Code clients: SSO sign-in, inference, managed settings delivery, model discovery, and telemetry. Fetch it with `curl https://claude-gateway.internal.example.com/protocol` from any deployed gateway, such as the one the [quickstart](#quickstart) below produces. Breaking changes to the protocol are announced in advance, but indefinite backwards compatibility isn’t guaranteed.

A gateway sign-in token identifies the developer who approved the sign-in, not the program that uses it. Claude Code signs in without a client ID or secret of its own, so a gateway has no way to verify which program is signing in. Any client can set `User-Agent` and other request fields, so use them for routing or metrics, not as proof that a request comes from Claude Code.


[​](#quickstart)

Quickstart

This quickstart walks the minimal path: register an OAuth client in your IdP, write a `gateway.yaml`, run the gateway alongside Postgres with Docker Compose, and verify sign-in end to end. It uses an Amazon Bedrock upstream; Claude Platform on AWS, Google Cloud’s Agent Platform, Microsoft Foundry, and the Anthropic API are equally supported by swapping the `upstreams` block as shown in the [configuration reference](claude-apps-gateway-config.md#upstreams). At the end you have a gateway a developer can `/login` to.

**Deploy on your private network.** Claude Code only connects to a gateway whose address is private. This is a security guard, because a trusted gateway can push settings that run commands on developer machines. Put the gateway behind an internal load balancer or VPN and give it a hostname that resolves to private IPs only. If your internal network is numbered from public IPv4 space your organization owns, see [Allow a gateway on public address space you own](#allow-a-gateway-on-public-address-space-you-own).


[​](#prerequisites)

Prerequisites

Have these in place before you start:

| You need                                | Details                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|-----------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Claude Code v2.1.195 or later           | The `claude gateway` subcommand and the gateway sign-in flow ship in v2.1.195. Earlier public builds don’t include them. Both the machine running the gateway server and each developer’s machine must be on v2.1.195 or later; run `claude update` to get the latest release. The [Claude Platform on AWS upstream](claude-apps-gateway-config.md#claude-platform-on-aws) requires Claude Code v2.1.198 or later on the gateway server.                                                                                                                                                                                                                                                                                                                                                                 |
| OpenID Connect (OIDC) identity provider | Okta, Microsoft Entra ID, Google Workspace, Keycloak, or Dex, or any other OIDC-compliant IdP such as PingFederate. The gateway runs standard OIDC discovery and the authorization-code flow against it. SAML and LDAP aren’t supported.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| PostgreSQL 11 or later                  | Backs the device sign-in flow and rate-limit counters. A managed PostgreSQL service works, including the smallest tier; see [which databases are supported](claude-apps-gateway-deploy.md#postgres). With [spend limits](claude-apps-gateway-spend-limits.md), it also holds durable spend, audit, and identity tables that should be backed up. TLS via `?sslmode=require` is recommended. PostgreSQL 11, 12, and 13 require Claude Code v2.1.290 or later on the gateway server. The PostgreSQL project no longer maintains those versions, so use a newer one where you can.                                                                                                                                                                                                                    |
| Model upstream                          | Amazon Bedrock credentials, Claude Platform on AWS credentials, Google Cloud credentials, a Microsoft Foundry resource, or an Anthropic API key. Multiple upstreams are supported with failover.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| HTTPS                                   | The gateway must be reachable over `https://` from developer laptops and from any browser used for sign-in; the gateway serves the device-verification page on the same listener. Either provide a TLS cert via `listen.tls` or run behind a TLS-terminating ingress, and set `listen.public_url` to the external origin in both cases. At `/login`, Claude Code accepts a plain `http://` origin only when the gateway host is loopback: `localhost`, `127.0.0.1`, or `::1`.                                                                                                                                                                                                                                                                                                                                  |
| Private-network address                 | At `/login`, Claude Code requires the gateway’s hostname or IP address to resolve only to private addresses: RFC 1918, link-local, CGNAT `100.64.0.0/10`, IPv6 ULA `fc00::/7`, or loopback. For a gateway you host, any public address outside a block you declare is rejected; see the [threat model](claude-apps-gateway-deploy.md#threat-model-summary) in the deployment guide. If developer machines route HTTPS through a corporate proxy, sign-in also requires the proxy host to resolve to private addresses; if it doesn’t, add the gateway host to `NO_PROXY` so the CLI connects directly. If your internal network is numbered from public IPv4 space your organization owns, [declare those blocks](#allow-a-gateway-on-public-address-space-you-own) so `/login` accepts a gateway there. |
| Linux runtime                           | The gateway server runs only on the native Linux binary. macOS works for local development. Windows isn’t supported as a server platform.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |


[​](#steps)

Steps

1

Register an OAuth client in your IdP

Decide the gateway’s hostname first, because the redirect URI must match it. Create a new OIDC web application and set the redirect URI to `https://claude-gateway.<your-domain>/oauth/callback`, where the host is the same value you set as [`listen.public_url`](claude-apps-gateway-config.md#listen) in step 3. Note the `client_id` and `client_secret`. Per-IdP instructions are in [Identity provider setup](claude-apps-gateway-deploy.md#identity-provider-setup).

2

Provision a PostgreSQL database

Use PostgreSQL 11 or later. The smallest managed tier is enough. The gateway runs its own schema migrations at boot, so the database role needs rights to create and alter tables; see [`store`](claude-apps-gateway-config.md#store).

3

Write gateway.yaml

Secrets are read via `${ENV_VAR}` expansion so the file itself can live in version control. Use a `public_url` hostname that resolves to a private IP on your network, because `/login` rejects public addresses. The minimal config has five sections, and every other field has a default:

gateway.yaml

```python
listen:
  host: 0.0.0.0
  port: 8080
  # Required unless host is a loopback address. Used for the IdP
  # redirect_uri and the discovery document.
  public_url: https://claude-gateway.internal.example.com

oidc:
  issuer: https://login.example.com        # must serve /.well-known/openid-configuration
  client_id: 0oa1example2
  client_secret: ${OIDC_CLIENT_SECRET}
  allowed_email_domains: [example.com]        # reject id_tokens outside your org
  userinfo_fallback: true                  # for IdPs whose id_token omits email/groups; harmless otherwise

session:
  jwt_secret: ${GATEWAY_JWT_SECRET}        # openssl rand -base64 32
  ttl_hours: 1                             # also bounds revocation latency on IdP deprovision

store:
  postgres_url: ${GATEWAY_POSTGRES_URL}    # add ?sslmode=require for managed Postgres

upstreams:
  - provider: bedrock
    region: us-east-1
    auth: {}                               # empty: AWS default credential chain
                                           # (IRSA, EC2/ECS task role, env vars, ~/.aws)

# Models are translated per upstream automatically. The built-in catalog
# maps claude-opus-4-8 to us.anthropic.claude-opus-4-8 and so on for every
# Bedrock-supported Claude model. Set false and add a `models:` list to
# expose only specific models.
auto_include_builtin_models: true
```

This config is enough for a working sign-in loop with the default Amazon Bedrock model catalog. Once it’s running, add per-group RBAC and managed settings via [`managed.policies`](claude-apps-gateway-config.md#managed), telemetry fan-out via [`telemetry`](claude-apps-gateway-config.md#telemetry), and multi-upstream failover, provisioned-throughput ARNs, or non-US regions via [`models`](claude-apps-gateway-config.md#models).

The Amazon Bedrock upstream needs an AWS principal with `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream` on both the `inference-profile/us.anthropic.*` ARNs and the underlying `foundation-model/anthropic.*` ARNs. It also needs Anthropic’s one-time use case form submitted for the account from the Bedrock console’s Model catalog.Supply the credential with IRSA on EKS, an ECS task role, or an EC2 instance profile rather than static keys. The [`upstreams` reference](claude-apps-gateway-config.md#upstreams) has the full IAM details, the cross-cloud credential matrix, and the `auth` blocks for the other providers.

4

Run it

Build a container image around the `claude` binary that meets the [image requirements](claude-apps-gateway-deploy.md#container-image), then run it alongside Postgres. The Compose file references the image as `registry.example.com/claude-gateway:2.1.198`; substitute your own registry and image tag:

docker-compose.yaml

```python
services:
  gateway:
    image: registry.example.com/claude-gateway:2.1.198
    ports: ["8080:8080"]
    volumes: ["./gateway.yaml:/etc/claude/gateway.yaml:ro"]
    environment:
      OIDC_CLIENT_SECRET: ${OIDC_CLIENT_SECRET}
      GATEWAY_JWT_SECRET: ${GATEWAY_JWT_SECRET}
      GATEWAY_POSTGRES_URL: postgres://gw:pw@postgres/gateway
      # AWS credentials: in production, omit these and use an instance
      # role. For local Compose testing, pass through your own:
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
      AWS_SESSION_TOKEN: ${AWS_SESSION_TOKEN}
    depends_on:
      postgres:
        condition: service_healthy
  postgres:
    image: postgres:16-alpine
    environment: { POSTGRES_USER: gw, POSTGRES_PASSWORD: pw, POSTGRES_DB: gateway }
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U gw"]
      interval: 5s
    volumes: ["pgdata:/var/lib/postgresql/data"]
volumes: { pgdata: }
```

The gateway is a single Linux binary that reads the config, connects to Postgres and applies its schema migrations, runs OIDC discovery against your IdP, builds upstream clients, and starts listening.Boot is fail-closed for the config, the Postgres connection, OIDC discovery, and upstream client construction. If any of those is unreachable or misconfigured, the gateway exits with an error rather than serving traffic in a degraded state.A successful boot doesn’t validate the inference path, because Amazon Bedrock and Google Cloud’s Agent Platform instance credentials resolve on the first request, not at boot.Watch stderr for the boot sequence. Log lines use the format `[gateway] <timestamp> <level> <message>`, audit events are single-line JSON with an `evt` field, and a startup banner, omitted below, prints between the migration and listening lines. A fresh database prints one `migration N applied` line per schema migration; an already-migrated database prints none. You should see, in order:

```python
{"ts":"2026-06-10T17:03:21.114Z","evt":"config.load","path":"/etc/claude/gateway.yaml","sha256":"…"}
[gateway] 2026-06-10T17:03:21.395Z info waiting for migration lock (another replica may be migrating; check pg_locks for key 6775156 if this persists)
[gateway] 2026-06-10T17:03:21.408Z info migration 1 applied
…
[gateway] 2026-06-10T17:03:21.431Z info migration 6 applied
[gateway] 2026-06-10T17:03:21.512Z info claude gateway listening on http://0.0.0.0:8080
```

The gateway also logs a warning that `access_control.allow_cidrs` is empty. That’s expected here, because nothing limits which client addresses the gateway serves until you set an allow list. The [`access_control` reference](claude-apps-gateway-config.md#http-tuning) has the recommended ranges.If boot exits before the `claude gateway listening on` line, the last line of stderr names the problem:

- an unreachable Postgres
- a Postgres role without DDL permission
- an unreachable or invalid OIDC discovery document
- a config schema violation with the offending field path

Fix it and restart.If you already have a TLS-terminating ingress, skip Compose and run the binary directly with `claude gateway --config gateway.yaml`. Set `public_url` to the ingress origin and bind `listen` to a loopback or cluster-internal address.

5

Verify the auth surface

Three checks confirm the gateway can authenticate a real user before you share it with a developer.The examples use the gateway’s public URL; for the local Compose setup without an ingress, substitute `http://localhost:8080` in the first two checks. The third check opens `verification_uri_complete`, which is built from `public_url`, so for local Compose set `public_url: http://localhost:8080` in `gateway.yaml`, and add `http://localhost:8080/oauth/callback` as a second redirect URI on the OAuth client from step 1, because the gateway builds the IdP `redirect_uri` from `public_url`. The verification link then opens in your local browser.In Windows PowerShell, run `curl.exe`; the bare `curl` is an alias for `Invoke-WebRequest` and rejects these flags.First, fetch the discovery document, which confirms the gateway is up, the config is valid, and all boot checks passed:

```python
curl -s https://claude-gateway.internal.example.com/.well-known/oauth-authorization-server | jq
```

```python
{
  "issuer": "https://claude-gateway.internal.example.com",
  "device_authorization_endpoint": "…/oauth/device_authorization",
  "token_endpoint": "…/oauth/token",
  "grant_types_supported": ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"]
}
```

The response includes additional fields, such as `response_types_supported` and `scopes_supported`.Second, request a device authorization, which confirms the device sign-in flow works and Postgres is reachable and writable:

```python
curl -s -X POST https://claude-gateway.internal.example.com/oauth/device_authorization | jq
```

```python
{
  "device_code": "…",
  "user_code": "WDJB-MJHT",
  "verification_uri": "https://claude-gateway.internal.example.com/device",
  "verification_uri_complete": "https://claude-gateway.internal.example.com/device?user_code=WDJB-MJHT",
  "expires_in": 600,
  "interval": 5
}
```

Third, test the browser leg by opening `verification_uri_complete` in a browser and confirming the code. You should be redirected to your IdP’s sign-in page, and after signing in, land back on the gateway with a signed-in confirmation.Use the first failing check to locate the problem:

- **First check fails**: boot didn’t complete; check stderr
- **Second check fails**: Postgres isn’t reachable from the gateway or the role can’t write; check the connection string and grants
- **Third check doesn’t reach the IdP**: check that the IdP’s redirect URI matches `https://<gateway>/oauth/callback` exactly
- **Third check reaches the IdP but bounces back with an error**: read the gateway’s audit log, which records every auth rejection with the reason, such as `email domain not allowed`

6

Log a developer in

This last step happens on a developer machine, not the server. Set `forceLoginMethod` to `"gateway"`, `forceLoginGatewayUrl` to your gateway’s `public_url`, and `parentSettingsBehavior` to `"merge"` in that machine’s [managed settings file](managed-settings.md#delivery-mechanisms), then run `/login`, press Enter on the **Cloud gateway** screen, and complete the browser sign-in. [Set the gateway URL](#set-the-gateway-url) below covers the three keys and distributing them to every developer machine.


[​](#connect-developers)

Connect developers

Developers connect from their own laptops with one browser sign-in, using their corporate work account. They don’t need a claude.ai account, an API key, or a subscription, because requests to the model go through the gateway using the organization’s upstream credential. Connection is driven by the [client-side managed settings](claude-apps-gateway-config.md#client-side-managed-settings) you push via MDM, and this section covers what the admin configures. The CLI fingerprints the gateway’s TLS leaf certificate on first connect and pins it per hostname. It checks that pin again during sign-in, on silent session refreshes, and on managed-settings fetches, while inference requests use standard TLS validation without the pin. Requests routed through an HTTPS proxy skip the pin check, so add the gateway host to `NO_PROXY` to keep them direct. Publish the expected SHA-256 fingerprint alongside the gateway URL so developers have something to compare against. The `/login` prompt shows the first 16 characters of the fingerprint as lowercase hexadecimal with no colons. To print the full fingerprint in that form from the certificate file, run:

```python
openssl x509 -noout -fingerprint -sha256 -in cert.pem | cut -d= -f2 | tr -d : | tr 'A-F' 'a-f'
```

When the certificate rotates, every developer sees the trust prompt again, so treat rotations as a planned event and republish the fingerprint. If your gateway policy includes [settings that need approval](server-managed-settings.md#security-approval-dialogs), the developer also sees that approval dialog again after accepting the new certificate, because Claude Code keys [approval memory](server-managed-settings.md#approval-memory) to the pinned certificate. A gateway can return the optional `email` field in its token response to name the account that a sign-in used. When it does, the developer confirms the account before Claude Code saves the credential. After a confirmed sign-in, `/status` shows the account. The confirmation requires Claude Code v2.1.275 or later on the developer machine; a client below that version ignores the field. The gateway server in the `claude` binary doesn’t return the field, so its sign-ins complete without the confirmation. Once the developer signs in, the [model picker](../02-Claude-Code-CLI/model-config.md) shows the models in their `availableModels` allowlist. Managed settings apply at startup and refresh hourly, and telemetry routes to your collector. Sessions refresh silently before `ttl_hours` expiry. When a refresh fails after IdP deprovisioning, Claude Code prompts the developer to log in again.


[​](#set-the-gateway-url)

Set the gateway URL

Three keys go in the per-OS [managed settings file](managed-settings.md#delivery-mechanisms) you deploy via MDM or directly on disk. For a machine with no managed settings, see [Set the gateway URL in user settings](#set-the-gateway-url-in-user-settings) instead. `forceLoginMethod` and `forceLoginGatewayUrl` open `/login` directly on the **Cloud gateway** screen with the URL filled in, and `parentSettingsBehavior: "merge"` lets Claude Desktop deliver the gateway’s egress allowlist to the Claude Code sessions it launches, explained in [Deliver policy to Claude Desktop sessions](#deliver-policy-to-claude-desktop-sessions):

```python
{
  "forceLoginMethod": "gateway",
  "forceLoginGatewayUrl": "https://claude-gateway.internal.example.com",
  "parentSettingsBehavior": "merge"
}
```

The developer presses Enter to connect. The [first-connect TLS fingerprint prompt](#connect-developers) still appears. Once the file is on a machine, a developer who hasn’t completed the gateway sign-in sees one of the messages described under [Administrator policy requires a Cloud gateway sign-in](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in). Developers who select a cloud provider through an environment variable such as `CLAUDE_CODE_USE_BEDROCK` don’t need the gateway sign-in. The login picker has no gateway option, and in managed settings `forceLoginMethod` alone, without a URL, leaves the developer at a “Contact your IT administrator” message. The login keys belong in the file you push to machines, not in the gateway’s `managed.policies[].cli` block, which only reaches clients that are already connected.


[​](#set-the-gateway-url-in-user-settings)

Set the gateway URL in user settings

On machines with no managed settings, have each developer add `forceLoginMethod` and `forceLoginGatewayUrl` to their own user settings file, `~/.claude/settings.json`. This requires Claude Code v2.1.295 or later on the developer machine. This example names a gateway at `claude-gateway.internal.example.com`:

```python
{
  "forceLoginMethod": "gateway",
  "forceLoginGatewayUrl": "https://claude-gateway.internal.example.com"
}
```

When the developer runs `/login` at the Claude Code prompt, the **Cloud gateway** screen opens on that address and they press Enter to connect. The [first-connect TLS fingerprint prompt](#connect-developers) still appears. These limits apply to keys set this way:

- **User settings only**: Claude Code reads the two keys from `~/.claude/settings.json`, not from a project’s `.claude/settings.json` or `.claude/settings.local.json`.
- **Managed settings turn them off**: once an administrator’s settings reach the machine through a managed settings file, a macOS plist or Windows HKLM policy, or a [policy helper](../02-Claude-Code-CLI/settings-reference.md#policyhelper), Claude Code ignores a gateway named in user settings.


[​](#allow-a-gateway-on-public-address-space-you-own)

Allow a gateway on public address space you own

Some organizations number their internal network from a public IPv4 block they own, such as a carrier’s own address space or a legacy `/8`, so their gateway has no private address. List those blocks in the `gatewayInternalNetworks` managed setting. `/login` then accepts a gateway inside a listed block when the developer’s machine connects to it from an address inside the same block. This requires Claude Code v2.1.268 or later on the developer machine; earlier versions ignore the key and apply the private-address rule.

`gatewayInternalNetworks` is for internal networks that happen to be numbered from public address space. It doesn’t make it safe to expose a gateway to the internet: a trusted gateway can push settings that run commands on developer machines.Keep the gateway unreachable from outside your network with your firewall or load balancer rules. Set the gateway’s [`access_control.allow_cidrs`](claude-apps-gateway-config.md#http-tuning) to the same blocks you declare here, so the gateway itself refuses clients from anywhere else. Behind a load balancer or ingress, set `listen.trusted_proxies` to that front end as well, because the gateway otherwise matches `allow_cidrs` against the front end’s own address rather than the developer’s.

Add the key to the same managed settings source as the login keys: the managed settings file, MDM profile, or registry policy. Claude Code ignores it in user, project, and server-managed settings. This example declares one block. Replace `203.0.113.0/24` with your own block. It is a documentation range, and Claude Code refuses those.

```python
{
  "gatewayInternalNetworks": ["203.0.113.0/24"]
}
```

Claude Code validates the list at `/login` before it contacts any gateway:

- Each entry is an IPv4 block written as its first address and a prefix from `/8` to `/32`.
- The list holds at most four blocks, and no two overlap.
- No block overlaps private address space: `10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `127.0.0.0/8`, `169.254.0.0/16`, and `100.64.0.0/10`. `/login` already accepts a gateway there without this key.
- No block overlaps space that is never an organization’s network: `198.18.0.0/15` and `192.0.0.0/24`, which VPN and NAT64 clients hold as local addresses; the documentation ranges `192.0.2.0/24`, `198.51.100.0/24`, and `203.0.113.0/24`; and the reserved ranges `0.0.0.0/8`, `192.88.99.0/24`, and multicast `224.0.0.0/4`. You can declare blocks inside `240.0.0.0/4`, which some large networks use as internal unicast space.

Blocks from `managed-settings.json` and its `managed-settings.d/` drop-in files combine into one list, and these limits apply to the combined list. To narrow a block, replace its entry rather than add a second, overlapping one in a drop-in; `/login` refuses the overlap. If an entry breaks a rule, or the value isn’t a list of strings, Claude Code refuses every new gateway sign-in on that machine and names the problem in the message. Sign-in to a gateway on a private address fails too, and existing sign-ins keep working. Try the value on one machine before you deploy it. Claude Code also lists a wrongly typed value among the [invalid managed settings it reports](managed-settings.md#keys-that-fail-closed). With a valid list, `/login` applies three checks to a gateway whose address is inside a listed block:

- Every address the gateway’s hostname resolves to is inside that one block. Claude Code refuses a name that also has records outside it, private and IPv6 addresses included.
- The developer’s machine connects from inside the same block. Claude Code refuses a machine behind NAT, inside a container or WSL2, or on a VPN whose address pool sits outside the block, and names the address the machine connected from.
- The connection is direct. If `HTTPS_PROXY` applies to the gateway host, `/login` refuses and names the `NO_PROXY` entry to add.

When all three pass, the [trust prompt](#connect-developers) adds a line naming the machine’s address, the gateway’s address, and the declared block that contains both. The key changes nothing for other gateways: sign-in to one on a private address works as before, and sign-in to one on a public address outside every listed block is refused as before. A declared block narrows who can sign in but doesn’t prove where a machine is, so declare only address space your organization controls. A block shared with other tenants, such as a cloud provider’s public range, lets anyone in it pass the same check.


[​](#deliver-policy-to-claude-desktop-sessions)

Deliver policy to Claude Desktop sessions

Claude Desktop runs its Cowork and Code tabs, plus the Chat tab when you enable it, on embedded Claude Code sessions and sends their model requests through the gateway. It passes policy to each of those sessions, built from the configuration the gateway serves it at `/user/bootstrap`: the model allowlist, disabled tools, and egress allowlist derived from the matched policy’s `cli` or `code` block, plus the [`desktop` overlay](claude-apps-gateway-config.md#claude-desktop-overlay). The block’s other keys, such as hooks, `env`, and scoped permission rules like `Bash(npm *)`, reach clients that sign in through `/login`. Under `code`, they also reach the Code tab’s session when the [conditions for the Code tab](claude-apps-gateway-config.md#apply-code-settings-in-the-code-tab) hold. They don’t reach Cowork or Chat sessions. Claude Desktop reads the gateway URL from its own managed configuration and signs in with its own flow, separate from the `forceLoginMethod` and `forceLoginGatewayUrl` keys in [Set the gateway URL](#set-the-gateway-url). Settings passed by a launching process are parent settings. Claude Code ignores parent settings on any machine that has an admin-deployed managed source, unless the [source that delivers the policy](managed-settings.md#which-managed-source-claude-code-uses) sets `parentSettingsBehavior: "merge"`.


[​](#which-machines-need-the-opt-in)

Which machines need the opt-in

Machines that only run Claude Desktop need it. Claude Desktop applies the model list and the disabled-tools list to embedded sessions itself, but the egress allowlist reaches them only as parent settings, in the form of `WebFetch` domain rules and sandbox network rules. Without the opt-in, those sessions run without the egress restriction, and nothing warns you. The gateway still rejects inference requests for models the policy doesn’t grant. A plugin marketplace allowlist also reaches embedded sessions only as parent settings. When you turn user-added plugin marketplaces off in Claude Desktop’s managed configuration, Claude Desktop 2.16120.0 or later hides marketplaces your organization didn’t provision and refuses installs from them. To stop embedded sessions from loading plugins already installed from those marketplaces, it sends them a `strictKnownMarketplaces` list as parent settings. Without the opt-in, Claude Code ignores that list, and those plugins keep loading. Machines where developers sign in through `/login` don’t need it; each Claude Code session fetches its policy from the gateway. Fleets whose [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) supplies managed settings can’t use it: Claude Code never merges parent settings on those fleets, because it reads managed settings from the helper’s output alone.


[​](#set-the-opt-in)

Set the opt-in

Deploy the managed settings snippet from [Set the gateway URL](#set-the-gateway-url), mirror it to any client-side source that outranks the file, then verify.

1

Deploy the opt-in in the managed settings file

The [snippet above](#set-the-gateway-url) already includes `parentSettingsBehavior: "merge"`, so the file you push to machines carries it.

2

Mirror the snippet to any source that outranks the file

Claude Code reads `parentSettingsBehavior` only from the [selected source](managed-settings.md#which-managed-source-claude-code-uses). Adding any policy key to a source can make that source the selected one, so in a client-side source, mirror the whole snippet rather than `parentSettingsBehavior` alone. [Client-side managed settings](claude-apps-gateway-config.md#client-side-managed-settings) covers fleets that deliver policy through Group Policy or configuration profiles. A managed-preferences plist on macOS or an HKLM policy on Windows outranks the `managed-settings.json` file, and the gateway’s own remote managed settings outrank both, so on machines that sign in to the gateway, also set `parentSettingsBehavior` in the gateway policy’s [`cli` or `code` block](claude-apps-gateway-config.md#managed).

3

Check which source is selected

On a machine that only runs Claude Desktop, call the Agent SDK’s [`resolveSettings()`](../05-Agent-SDK/agent-sdk-typescript.md#resolvesettings) and read `policyOrigin` on the `managed` entry in its `sources` list. The value names the selected client-side source, `plist`, `hklm`, or `file`, which is the source that must carry the snippet.Cowork and Chat sessions don’t fetch the gateway policy, so the gateway’s block isn’t the selected source for them.A Code tab session fetches it when the policy’s settings are under `code` and the [conditions for the Code tab](claude-apps-gateway-config.md#apply-code-settings-in-the-code-tab) hold. The gateway’s `code` settings are then the selected source for that session.


[​](#restrict-parent-settings)

Restrict parent settings

Once you deploy `parentSettingsBehavior: "merge"`, any host process that launches Claude Code can supply parent settings, not only Claude Desktop but also an Agent SDK application or an IDE extension. Claude Code filters parent settings against an allowlist of restrictive keys, but some allowed keys can grant access rather than restrict it. Unless you set the `allowManaged*Only` locks, permission allow rules and sandbox allowlists supplied by the host still apply. Your policy’s deny and ask rules stay in force either way; [they’re evaluated before any allow rule](../02-Claude-Code-CLI/permissions.md#manage-permissions). Claude Code forwards parent-supplied [`sandbox.credentials`](../02-Claude-Code-CLI/settings-reference.md#sandbox-credentials) entries in stripped form:

- **`deny` entries**: forwarded with only their `path` or `name` and the mode.
- **File entries with [`mode: mask`](../02-Claude-Code-CLI/sandboxing.md#mask-credential-files)**: forwarded sentinel-only, as a whole-file mask whose `injectHosts` is the empty list, so the proxy never substitutes the real value for a parent-supplied entry on any platform. All structured-masking fields are dropped too, so a parent-supplied extract pattern can’t displace a stricter mask another source sets for the same path.
- **`envVars` entries with `mode: mask`**: not forwarded. `deny` is the only restriction the parent channel can express through `envVars` entries.
- **[`awsPairs` and `sigv4`](../02-Claude-Code-CLI/sandboxing.md#re-sign-aws-requests)**: forwarded restriction-only. From `sigv4`, only `deny` values are kept, and a parent that defines a `sigv4` block at all pins all three request forms, `streaming`, `presigned`, and `sigv4a`, to `deny`. An `awsPairs` pair is never forwarded in a form that can re-sign; a pair that names one of the conventional AWS variables is replaced by an inert entry that keeps the automatic pairing of `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, and `AWS_SESSION_TOKEN` suppressed.


[​](#deploy-the-locks)

Deploy the locks

To keep parent settings as close to restriction-only as the filter supports, add all five `allowManaged*Only` locks, and the allowlists they govern, to the same sources as the merge opt-in:

```python
{
  "forceLoginMethod": "gateway",
  "forceLoginGatewayUrl": "https://claude-gateway.internal.example.com",
  "parentSettingsBehavior": "merge",
  "allowManagedPermissionRulesOnly": true,
  "allowManagedMcpServersOnly": true,
  "allowManagedHooksOnly": true,
  "allowedMcpServers": [{ "serverUrl": "https://mcp.internal.example.com/*" }],
  "sandbox": {
    "network": {
      "allowManagedDomainsOnly": true,
      "allowedDomains": ["github.com", "*.npmjs.org"]
    },
    "filesystem": {
      "allowManagedReadPathsOnly": true,
      "denyRead": ["~/"],
      "allowRead": ["~/projects"]
    }
  }
}
```

An OS policy, such as an HKLM registry policy or a managed-preferences plist, outranks this file, so deliver the whole snippet through it instead of the file. The gateway’s remote managed settings outrank the OS policy and file sources but reach only connected clients. Mirror the locks, the allowlists, and the merge opt-in into the policy’s [`cli` or `code` block](claude-apps-gateway-config.md#managed) and keep this file deployed, because machines that never connect get their policy from the file alone.


[​](#lock-behavior-across-sources)

Lock behavior across sources

Setting one lock doesn’t restrict the others; each key is documented in the [settings reference](../02-Claude-Code-CLI/settings-reference.md#all-settings). From an admin source below the winner, the two sandbox locks still apply, and `allowManagedPermissionRulesOnly` still blocks parent-supplied allow rules and `additionalDirectories`. On Claude Code v2.1.273 or later, the MCP server lock also applies from a source below the winner, and while it is on, the managed `allowedMcpServers` list comes from the highest-priority admin source that sets one. The hooks lock and `allowManagedPermissionRulesOnly`’s effect on the developer’s own rules need the winning source by default; under the `managedSourcesBehavior` merge opt-in in [how Claude Code combines managed sources](managed-settings.md#how-claude-code-combines-managed-sources), Claude Code applies the strictest value any source sets for every lock. On [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) fleets, Claude Code reads the locks from the helper’s output alone. Each lock makes Claude Code ignore the developer’s own entries for that setting, so include your organization’s allowlists next to the locks:

- **Network domains**: locking with an empty managed domain list blocks all sandboxed outbound traffic.
- **MCP servers**: locking with no `allowedMcpServers` in any admin source or in the parent-supplied settings loads every server that `deniedMcpServers` doesn’t block.
- **Read paths**: `allowRead` entries only re-allow paths inside `denyRead` regions, so pair them with a managed `denyRead`.


[​](#settings-the-locks-don’t-cover)

Settings the locks don’t cover

These parent-supplied settings pass the filter even with all five locks set:

- **`forceLoginOrgUUID`**: Claude Code honors a parent-supplied value when the highest-priority admin source doesn’t set an org UUID. Gateway sign-in doesn’t check this key. An org UUID in the highest-priority admin source blocks the parent’s value and is the one Claude Code enforces.
- **`allowedMcpServers`**: Claude Code honors a parent-supplied allowlist when no admin list is in force. `allowManagedMcpServersOnly` doesn’t block it, because the lock enforces whichever list wins as the managed value, including a parent-supplied one when no admin source supplies a list. A list in the highest-priority admin source blocks the parent’s and is the list Claude Code enforces, so set `allowedMcpServers` there, next to the lock. Before v2.1.223, a value for either key in any admin source blocked the parent’s.
- **`availableModels`**: Claude Code honors a parent-supplied model list when the winning managed source doesn’t set one. If your fleet restricts models, set `availableModels` in the winning source.
- **`allowedProviders`**: Claude Code honors a parent-supplied API provider allowlist when the winning managed source doesn’t set one. If your fleet restricts which API providers developers can use, set `allowedProviders` in the winning source. Requires Claude Code v2.1.285 or later.
- **`strictKnownMarketplaces`**: Claude Code honors a parent-supplied plugin marketplace allowlist when the winning managed source doesn’t set one. Claude Desktop 2.16120.0 or later sends one when its managed configuration turns user-added plugin marketplaces off. If your fleet restricts marketplaces, set `strictKnownMarketplaces` in the winning source. Requires Claude Code v2.1.282 or later.
- **`blockedMarketplaces`**: a parent-supplied marketplace blocklist passes and adds to any blocklist that a managed source sets, since a blocklist can only restrict further. Requires Claude Code v2.1.282 or later.
- **`strictPluginOnlyCustomization`**: this key passes the filter regardless of any lock, and it makes Claude Code ignore the developer’s own customization, including protective hooks. No lock blocks it.

Under the default first-wins setting, an admin value blocks the parent’s only when it sits in the highest-priority admin source, except for `allowedMcpServers` while the [MCP server lock](#lock-behavior-across-sources) is on. Under the `managedSourcesBehavior` merge opt-in, [how Claude Code combines managed sources](managed-settings.md#how-claude-code-combines-managed-sources) says which source’s value applies instead.


[​](#how-hook-and-skill-keys-in-parent-settings-take-effect)

How hook and skill keys in parent settings take effect

If you pass `disableAllHooks: true` as a parent setting through the Agent SDK’s `managedSettings` option, not every hook stops. Your [SDK hook callbacks](../05-Agent-SDK/agent-sdk-hooks.md) still run, and so do the administrator’s hooks, which your application can’t switch off. On a machine where an administrator deploys managed settings, Claude Code ignores parent settings unless the administrator [lets an embedding host add policy](managed-settings.md#let-an-embedding-host-add-policy).

| What you pass                                                                                | What happens                                                                                                                                                                                                                                                               |
|:---------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `disableAllHooks: true`                                                                      | Treated as `allowManagedHooksOnly: true`, so the hooks listed as [blocked under `allowManagedHooksOnly`](../02-Claude-Code-CLI/settings-reference.md#what-runs-under-allowmanagedhooksonly) stop, and so do hooks in the SDK’s [`settings` option](../05-Agent-SDK/agent-sdk-typescript.md#options). |
| [`disableSkillShellExecution: true`](../02-Claude-Code-CLI/settings-reference.md#disableskillshellexecution) | Takes effect.                                                                                                                                                                                                                                                              |
| `allowedHttpHookUrls: []`                                                                    | Takes effect unless the administrator defines `hooks` or enables a plugin. No HTTP hook runs, except for URLs another settings source lists.                                                                                                                               |
| `httpHookAllowedEnvVars: []`                                                                 | Takes effect unless the administrator defines `hooks` or enables a plugin. HTTP hook headers can include only environment variables another settings source lists.                                                                                                         |
| `allowedHttpHookUrls` or `httpHookAllowedEnvVars` with entries                               | Claude Code ignores it, since merging would widen an administrator’s list. Use the SDK `settings` option instead.                                                                                                                                                          |

Passing these keys in `managedSettings` requires Claude Code v2.1.309 or later, or TypeScript Agent SDK v0.3.309 or later.


[​](#connect-claude-desktop)

Connect Claude Desktop

[Claude Desktop](../16-Mobile-Desktop/desktop.md) connects to the same gateway through a different MDM key: set `bootstrapUrl` in Claude Desktop’s [managed configuration](https://claude.com/docs/third-party/claude-desktop/configuration) to `<listen.public_url>/user/bootstrap`, and opt the user’s policy in with a `desktop` key. [Claude Desktop overlay](claude-apps-gateway-config.md#claude-desktop-overlay) covers both halves. Requires Claude Code v2.1.203 or later on the gateway server. Claude Desktop signs the developer in through the gateway’s identity provider with the same browser SSO step, then fetches its configuration from the gateway instead of from Anthropic. Model access and policy follow the same per-group rules as the CLI. A developer who uses both the CLI and Claude Desktop signs in to each separately; the gateway session isn’t shared between them. Once connected, Claude Desktop sends model requests from every enabled tab through the gateway. It shows the Cowork and Code tabs by default. To turn on the Chat tab as well, set `chatTabEnabled` to `true` in Claude Desktop’s [managed configuration](https://claude.com/docs/third-party/claude-desktop/configuration), or in the policy’s [`desktop` block](claude-apps-gateway-config.md#claude-desktop-overlay) on a gateway running Claude Code v2.1.227 or later.


[​](#ci-pipelines-and-remote-machines)

CI pipelines and remote machines

There is no service-token flow for unattended pipelines. Gateway sign-in always runs the browser device flow, so a CI job with no developer to approve the sign-in can’t authenticate; configure those against your provider directly. Deploy `forceLoginMethod` and `forceLoginGatewayUrl` to developer machines and leave them off CI runners. On a machine whose managed settings set them, a session that authenticates with `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `apiKeyHelper` [exits at startup](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in). Once a developer has signed in, each Claude Code session on that machine uses the gateway session, including non-interactive `claude -p` runs and sessions started by the Agent SDK. Claude Code applies the [gateway policy](claude-apps-gateway-config.md#managed) to each of them. The device flow separates the polling CLI from the approving browser, so a remote development box with no display still works: the developer runs `/login` over SSH on the remote machine and opens the verification link in the browser on their laptop.


[​](#whats-enforced-on-developers)

What’s enforced on developers

These guarantees apply to every session signed in through `/login`. The embedded sessions Claude Desktop launches get their policy as described in [Deliver policy to Claude Desktop sessions](#deliver-policy-to-claude-desktop-sessions), and the telemetry bullet says where their exports go.

- **Model access**: requests for models the policy doesn’t grant return 400, and the `/model` picker is filtered to the policy’s `availableModels` allowlist. This includes the model a session starts on before the developer picks one; see [Start sessions on a model the policy allows](claude-apps-gateway-config.md#start-sessions-on-a-model-the-policy-allows).
- **Telemetry destination**: in sessions signed in through `/login`, the CLI sends its OTLP/HTTP exports to the gateway rather than to a locally set `OTEL_EXPORTER_OTLP_ENDPOINT`, unless a policy [names your collector as the endpoint](claude-apps-gateway-config.md#export-directly-to-your-collector). The gateway relays the exports it receives to the destinations in [`telemetry.forward_to`](claude-apps-gateway-config.md#telemetry).
  - In the embedded sessions [Claude Desktop launches](#connect-claude-desktop), the CLI sends its exports to the configured `OTEL_EXPORTER_OTLP_ENDPOINT`. The CLI attaches the gateway session token to those exports only when that endpoint points at the gateway itself.
  - With no destination configured for a signal, the gateway accepts and discards it.
  - If you already collect Claude Code telemetry directly, add your collector as a `forward_to` destination, or name it in a policy to skip the relay.
- **Credentials**: the gateway token is the session’s only credential. [Anthropic profiles](../02-Claude-Code-CLI/authentication.md#anthropic-profiles-and-federation-credentials) and any earlier claude.ai login are ignored while signed in, so developers don’t need to log out of claude.ai first. For a configured `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `apiKeyHelper` credential, or an API key saved by an earlier Claude Console login, see [Administrator policy requires a Cloud gateway sign-in](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in).
- **Managed settings**: locked keys can’t be overridden locally. The CLI applies the policy at startup and applies changes on each hourly poll, apart from the [changes that apply only at the next launch](server-managed-settings.md#fetch-and-caching-behavior).
- **Startup with the gateway unreachable**: signed-in sessions exit at startup with an error after about 10 seconds rather than starting without their settings.
- **Startup after the gateway ends the session**: see [Enforce fail-closed startup](server-managed-settings.md#enforce-fail-closed-startup) for the launches that open signed out of the gateway and the ones that exit when the gateway answers with a `401`.
- **Deprovisioning**: a session whose user is disabled in the IdP expires within `ttl_hours` when the next refresh fails.
- **Sign-out**: `/logout` deletes the gateway credential from the developer’s machine.
  - When the gateway’s discovery document advertises a `revocation_endpoint` on the gateway URL’s own scheme, host, and port, `/logout` also sends the stored tokens to that endpoint so the gateway can end the session on its side. The request is best effort, so sign-out completes on the developer’s machine whether or not the endpoint answers. The revocation requires Claude Code v2.1.275 or later on the developer’s machine.
  - The gateway server in the `claude` binary advertises none, so a sign-out from it ends the session on the developer’s machine only. To force sessions out server-side, see [JWT secret rotation](claude-apps-gateway-deploy.md#jwt-secret-rotation).


[​](#what-the-organization-can-see)

What the organization can see

Usage telemetry carries the developer’s identity, token counts, model, and latency to the organization’s collector. The gateway doesn’t log or store prompt or completion content. Whether richer telemetry such as logs and traces is collected, which can include commands and file paths, is the organization’s [per-destination choice](claude-apps-gateway-config.md#telemetry).


[​](#availability-and-limitations)

Availability and limitations

The table covers which Claude Code features work when developers connect through the gateway, and what the gateway server itself supports. Where something isn’t supported, the Notes column gives the alternative. The gateway delivers the [`anthropic-beta`](../04-API-Reference/Endpoints/beta-headers.md) values the CLI sends to every upstream, so operators don’t maintain a beta allowlist. For Amazon Bedrock, which ignores the header, the gateway moves the values into the request body’s `anthropic_beta` field; the other upstreams receive the header as sent.

| Feature                                                                                                                    | Status                | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
|----------------------------------------------------------------------------------------------------------------------------|-----------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Inference forwarding (Amazon Bedrock, Claude Platform on AWS, Google Cloud’s Agent Platform, Microsoft Foundry, Anthropic) | Available             | With per-upstream model translation and failover. The Amazon Bedrock upstream uses the `bedrock-runtime` endpoint and the AWS default credential chain. The [Amazon Bedrock Mantle upstream](claude-apps-gateway-config.md#amazon-bedrock-mantle-endpoint) requires Claude Code v2.1.283 or later on the gateway server, and the [Claude Platform on AWS upstream](claude-apps-gateway-config.md#claude-platform-on-aws) requires v2.1.198 or later.     |
| 1M token context window                                                                                                    | Available             | Fable models, Sonnet 5 and later, and Opus 4.7 and later run with the 1M window by default; see [Extended context](../02-Claude-Code-CLI/model-config.md#extended-context). The 1M default for the Fable and Opus models requires Claude Code v2.1.287 or later on the developer machine                                                                                                                                                                                             |
| Model access and managed settings by IdP group                                                                             | Available             | Model access is enforced server-side; managed settings are delivered per IdP group and applied by the CLI at the [managed settings tier](../02-Claude-Code-CLI/settings.md#settings-precedence)                                                                                                                                                                                                                                                                                      |
| Claude Desktop                                                                                                             | Available with opt-in | The gateway serves Claude Desktop’s configuration at `/user/bootstrap` once a policy [opts in with a `desktop` key](claude-apps-gateway-config.md#claude-desktop-overlay), and Claude Desktop sends model requests from its Cowork and Code tabs, and from the Chat tab when you enable it, through the gateway. To turn on the Chat tab, see [Connect Claude Desktop](#connect-claude-desktop). Requires Claude Code v2.1.203 or later on the gateway server. |
| Telemetry fan-out (OTLP/HTTP)                                                                                              | Available             | Identity-stamped per export; both protobuf and JSON encodings                                                                                                                                                                                                                                                                                                                                                                                                        |
| OIDC identity providers                                                                                                    | Available             | Any OIDC-compliant IdP; the gateway runs standard OIDC discovery and the authorization-code flow. See [Identity provider setup](claude-apps-gateway-deploy.md#identity-provider-setup) for per-IdP configuration                                                                                                                                                                                                                                               |
| Per-user and per-group spend limits                                                                                        | Available             | See [Spend limits](claude-apps-gateway-spend-limits.md)                                                                                                                                                                                                                                                                                                                                                                                                        |
| Server-side web search                                                                                                     | Not available         | The CLI can’t see which upstream provider the gateway routes to, so it can’t verify web search support and disables WebSearch on gateway sessions                                                                                                                                                                                                                                                                                                                    |
| [Remote Control](../02-Claude-Code-CLI/remote-control.md)                                                                                  | Not available         | The CLI shows [an error naming the gateway](../02-Claude-Code-CLI/errors.md#remote-control-requires-the-anthropic-api)                                                                                                                                                                                                                                                                                                                                                               |
| [`/design-sync`](../02-Claude-Code-CLI/commands.md#all-commands) and `/design-login`                                                       | Not available         | Both need claude.ai, which the CLI doesn’t contact on gateway sessions, so neither command appears there                                                                                                                                                                                                                                                                                                                                                             |
| Features that need feature-flag fetching, such as `/import` and `claude import`                                            | Not available         | The CLI skips the flag fetch on gateway sessions. [Features that need feature-flag fetching](../02-Claude-Code-CLI/env-vars.md#features-that-need-feature-flag-fetching) lists what that turns off                                                                                                                                                                                                                                                                                   |
| Standard prompt caching                                                                                                    | Available             | The gateway forwards `cache_control` breakpoints to every upstream. [Where the cache lives](../02-Claude-Code-CLI/prompt-caching.md#where-the-cache-lives) covers which blocks the CLI marks, including the system context it appends mid-conversation                                                                                                                                                                                                                               |
| 1-hour cache TTL                                                                                                           | Available with opt-in | The CLI uses the 5-minute TTL on gateway sessions by default. To request the 1-hour TTL, set it [in a policy](claude-apps-gateway-config.md#one-hour-prompt-cache). Set it only if your upstreams offer the 1-hour TTL for every model the gateway serves, because the request fails if an upstream rejects the TTL                                                                                                                                            |
| Auto mode                                                                                                                  | Available             | Follows the [third-party provider rules](../02-Claude-Code-CLI/permission-modes.md#enable-auto-mode-on-bedrock-agent-platform-or-foundry): only the models eligible on third-party providers can use it. Before v2.1.207, auto mode on gateway sessions required setting `CLAUDE_CODE_ENABLE_AUTO_MODE=1`, deliverable through the managed policy `env` block                                                                                                                        |
| First-party-only optimizations such as global cache scope and token-efficient tools                                        | Not available         | The CLI doesn’t enable them on gateway sessions; see the beta-header note above                                                                                                                                                                                                                                                                                                                                                                                      |
| OTLP/gRPC                                                                                                                  | Not supported         | OTLP over HTTP only                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| SAML, LDAP, and other non-OIDC auth                                                                                        | Not supported         | OIDC only. Front with an OIDC bridge if needed                                                                                                                                                                                                                                                                                                                                                                                                                       |
| Multi-tenant (multiple OIDC issuers)                                                                                       | Not supported         | One issuer per gateway. Run separate instances                                                                                                                                                                                                                                                                                                                                                                                                                       |
| Windows server                                                                                                             | Not supported         | Deploy on Linux. macOS for local development only                                                                                                                                                                                                                                                                                                                                                                                                                    |
| Helm chart                                                                                                                 | Not available         | The gateway runs as a standard stateless Deployment; see the [deployment guide](claude-apps-gateway-deploy.md#kubernetes)                                                                                                                                                                                                                                                                                                                                      |
| Admin UI                                                                                                                   | Not available         | Configuration is the YAML file; redeploy to change it                                                                                                                                                                                                                                                                                                                                                                                                                |


[​](#next-steps)

Next steps

The quickstart leaves you with a minimal config running under Docker Compose. To take it further:

- Expand `gateway.yaml` beyond the minimal config, for example to add per-group RBAC, multi-upstream failover, or telemetry destinations. The [configuration reference](claude-apps-gateway-config.md) covers every option.
- Move from Compose to a production deployment on Kubernetes or Cloud Run, set up your IdP properly, and review the security model. The [deployment and operations guide](claude-apps-gateway-deploy.md) covers per-IdP setup, container image requirements, health probes, and troubleshooting.
- Put spend caps on individual developers or groups so a runaway workload can’t consume your whole commitment. [Spend limits](claude-apps-gateway-spend-limits.md) covers the admin API and how enforcement works.
- For a complete worked example on AWS, with ECS Fargate or EKS, Amazon RDS, and Secrets Manager, see [Deploy on AWS](claude-apps-gateway-on-aws.md).
- For a complete worked example on Google Cloud, with Cloud Run, Cloud SQL, and Secret Manager, see [Deploy on Google Cloud](claude-apps-gateway-on-gcp.md).

---

## Claude Code on Console to Enterprise migration | Claude Help Center

Source: https://support.claude.com/en/articles/14128775-claude-code-on-console-to-enterprise-migration
Path: 13-Enterprise-Admin/claude-code-on-console-to-enterprise-migration.md

# Claude Code on Console to Enterprise migration

Claude Console (API) → Claude Enterprise


## Overview

This guide is built for teams migrating from Console-based Claude Code access to Claude Enterprise. Note that individual users don’t need to migrate session history for CLI sessions since these are stored locally. You only need to provision Claude Enterprise accounts for each user, and then users should switch their login method from Console to Claude Enterprise.

## Why migrate to Claude Enterprise

Claude Code on the Console API is a fast way to get developers started, but it leaves governance up to each individual machine. Claude Enterprise puts the same Claude Code your developers already use behind the controls that security, IT, and finance teams need to run it at scale. You get centralized identity and access through your existing IdP, server-managed settings that enforce tool, file, and MCP policies on every client without MDM, and granular spend caps that cascade from the org down to individual users.

Claude Enterprise adds rich analytics and audit logs (including contribution metrics like PRs and lines committed), custom data retention controls, and additional product surfaces—Claude Code cloud sessions, Claude Mobile, and Slack (Claude Tag), plus Code Review—that aren't available on Console. And because Claude Enterprise is available via AWS Marketplace, procurement can run through the channel you already have.

------------------------------------------------------------------------

## Claude Enterprise compared to Console

[TABLE]

**Docs: [Roles and permissions](roles-and-permissions.md)**, **[Purchasing and managing seats on Enterprise plans](../17-Billing-Plans/purchasing-and-managing-seats-on-enterprise-plans.md), [How am I billed for my Enterprise plan?](../17-Billing-Plans/how-am-i-billed-for-my-enterprise-plan.md)**, **[Using Claude Code with your Enterprise plan](https://support.claude.com/en/articles/11845131-using-claude-code-with-your-team-or-enterprise-plan)**

------------------------------------------------------------------------

## SCIM provisioning

Here’s what your identity team needs to do:

1.  **Update IdP group mappings** — Create or repurpose IdP groups to map users to the Claude Enterprise organization. Enable group mappings to assign seats automatically.

2.  **Ensure sufficient seats are purchased** — Before triggering a SCIM sync, verify the Claude Enterprise org has enough seats available. Users without available seats will be set to “Unassigned” status.

3.  **Confirm Claude Code access is enabled** — Confirm Claude Code is enabled for the Enterprise organization in admin settings, and — if you scope features with custom roles or groups — that your developer groups are granted Claude Code access.

4.  **Trigger a sync** — Navigate to **[Organization settings → Organization and access](https://claude.ai/admin-settings/organization)**, find **Directory sync (SCIM)**, and click “Sync.” Note that this may be slow as this can trigger a big sync.

5.  **Verify seat assignments** — After sync, check **[Organization settings → Organization and access](https://claude.ai/admin-settings/organization)** to confirm users landed on the correct seat type.

6.  **Optional: Adjust Console org mappings** — If some users should retain Console API access, keep their group mapped to the Console organization. Users can belong to both orgs simultaneously.

**Note:** Microsoft Entra SCIM changes sync every ~40 minutes. Use the “Sync” button to trigger on-demand after group changes.

If your Enterprise organization's SSO is configured as login-only, signing in does not create accounts — users must be invited manually. Enable JIT or SCIM provisioning before directing developers to log in. See **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195-set-up-jit-or-scim-provisioning).**

------------------------------------------------------------------------

## Spend limits

Claude Enterprise plans offer a hierarchical spend control system. Limits cascade—a user can never exceed the org limits.

[TABLE]

### How to set and edit spend limits

1.  Sign in as an Owner or Primary Owner.

2.  Navigate to **[Organization settings → Usage](https://claude.ai/admin-settings/usage)**.

3.  Set the Organization level limit — this is the global ceiling for all monthly spend.

4.  Set Group level limits — under the "By group" tab. See **[Manage groups and group spend limits on Enterprise plans](../17-Billing-Plans/manage-groups-and-group-spend-limits-on-enterprise-plans.md)**.

5.  Set Individual level limits — find specific users in the **Spending defaults** section under the “By member” tab.

Owners can set limits to "unlimited," but all consumption is still billed. If a user on a consumption seat hits their limit, they cannot use Claude or Claude Code until the next billing period or until an admin increases their limit.

**Docs: [Configuring spend limits](../17-Billing-Plans/how-am-i-billed-for-my-enterprise-plan.md)**, **[Manage groups and group spend limits on Enterprise plans](../17-Billing-Plans/manage-groups-and-group-spend-limits-on-enterprise-plans.md)**

------------------------------------------------------------------------

## Issuing new seats and re-authentication

### Adding seats

1.  Log in as Primary Owner or Owner.

2.  Go to **[Organization settings → Billing](https://claude.ai/admin-settings/billing)**.

3.  Click the pencil icon under Seats.

4.  Enter the new seat counts.

5.  Review and click “Upgrade” to confirm. New seats are prorated.

### Assigning users to seats

1.  Go to **[Organization settings → Members](https://claude.ai/admin-settings/members)**.

2.  Click “Add member” (or “Bulk add” for multiple).

3.  Enter the user’s @ email address.

4.  Set role (User, Admin, Owner) and send invite.

For SCIM-provisioned users, seat assignment happens automatically based on group mappings. Users default to the highest-available seat tier if no group mapping is configured.

### Re-authentication for Claude Code users

Developers currently authenticated against the Console org will need to re-authenticate against the Claude Enterprise organization:

1.  Remove any leftover Console credentials before logging in. Check for `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` environment variables in shell profiles, dotfiles, and CI configs, and remove any `apiKeyHelper` setting. Also check for `CLAUDE_CODE_OAUTH_TOKEN`, `ANTHROPIC_BASE_URL`, and the `CLAUDE_CODE_USE_BEDROCK / _VERTEX / _FOUNDRY` flags — these also override or bypass `/login`. Credentials can also hide in the env block of Claude Code's own settings files (~/.claude/settings.json, and the .claude/settings\*.json files in repos). If these remain, they silently take precedence over the new login: `/login` will appear to succeed, but usage continues billing to the old Console organization.

2.  In the terminal, run `claude` and then run `/login` to switch authentication method.

3.  Select “Claude account with subscription” as the login method.

4.  Choose the Claude Enterprise organization (not the Console org or a personal account).

5.  Authorize, return to the terminal, and run `/status` to confirm Claude Code shows your Enterprise organization.

**CI and automation**

Pipelines and scripts don't use `/login`. Either keep a Console org (and its API keys) for automation and migrate only interactive developer seats, or switch CI to an Enterprise credential: run `claude setup-token` while signed in to the Enterprise organization and set the printed token as `CLAUDE_CODE_OAUTH_TOKEN` in your CI environment. Don't remove CI credentials until one of these is in place.

**Tip for IT:** Deploy managed settings with `forceLoginOrgUUID` set to your Enterprise organization UUID as a standard part of every migration. Deliver the pin as an endpoint-managed file or MDM policy — server-managed settings only arrive after a user signs in, so they can't catch a wrong first login. If you also use server-managed settings, set `forceLoginOrgUUID` there too: the two channels don't merge. This blocks login to any other organization, and it turns the leftover-credential failure mode above from silent misbilling into a loud one: Claude Code refuses to start and tells the user that a leftover credential (`ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `apiKeyHelper`) must be removed.

**After validating the migration**

Remove migrated developers from the Console org (or rotate their keys). Removal revokes their Console login tokens and disables their Claude Code workspace key, stopping further Console billing from interactive use. API keys they created in other Console workspaces are not disabled by removal — review and disable those separately. Don't remove members until you've confirmed Enterprise access works; there is no automated rollback.

**Docs: [Purchasing and managing seats on Enterprise plans](../17-Billing-Plans/purchasing-and-managing-seats-on-enterprise-plans.md)**

------------------------------------------------------------------------

## Improvements in reporting

Moving from Console API to Claude Enterprise unlocks richer analytics for Claude Code usage:

[TABLE]

All reporting is accessible from **[Analytics](https://claude.ai/analytics/activity)** in the Claude admin panel. For programmatic access, the **[Claude Enterprise Analytics API](../04-API-Reference/Other/manage-claude-analytics-api.md)** returns per-user engagement metrics, Claude Code activity (commits, pull requests, lines of code), and usage and cost data. Analytics do not migrate: the Enterprise organization starts with fresh reporting history, and its Analytics/Admin API requires a new API key created in the Enterprise organization — Console keys don't carry over. Export any Console analytics you need for historical dashboards before cutover. Traffic that remains on Console API keys (for example CI) continues to appear only in Console reporting.

**Docs: [Claude Code Usage Analytics](../22-Safety-Policy/claude-code-usage-analytics.md)**

------------------------------------------------------------------------

## Managed policy settings — deep dive

This is the most significant upgrade for the security and IT teams. **[Server-managed settings](server-managed-settings.md)** allow centralized, enforceable control over what Claude Code can and cannot do across every developer’s machine. There are two ways to deliver them, and you don't need MDM to use the first one.

### How it works

Claude Code reads configuration from a hierarchy of settings sources. The managed-settings.json file sits at the top and cannot be overridden by user or project settings:

[TABLE]

### Two ways to deliver managed settings

**Server-managed settings (no MDM required):** An Owner or Primary Owner defines settings in **Organization settings** → **Claude Code** → **Managed settings** in the admin console. Every Claude Code client signed in to the organization fetches them automatically at startup and polls hourly for updates. This is the right choice for organizations without device management infrastructure or with users on unmanaged devices. Learn more about **[server-managed settings](server-managed-settings.md)**.

**Endpoint-managed settings:** IT deploys settings directly to devices, either through native OS policies (macOS managed preferences or the Windows registry, via Jamf, Intune, Group Policy, etc.) or as a managed-settings.json file pushed to the system paths below. Protect the file with OS-level permissions so end users cannot modify it. On enrolled devices, this provides stronger guarantees than server-managed delivery because the OS prevents user tampering.

**Note:** Both channels occupy the same highest-priority tier and use the same JSON format, but they don't merge. The first source that delivers a non-empty configuration wins: server-managed settings are checked first, and if they deliver any keys at all, endpoint-managed settings are ignored entirely. Run /status to see which managed source is active.

### File locations (endpoint-managed)

[TABLE]

### Key settings reference

These are the settings most relevant to a Console-to-Enterprise migration. Claude Code supports many more: for the complete, current list, see **[Claude Code settings](../02-Claude-Code-CLI/settings.md)**.

[TABLE]

**Deployment:** Use your MDM tool (Intune, Jamf, SCCM, Puppet, etc.) to push managed-settings.json to all developer machines. Protect the file with OS-level permissions so end users cannot modify it.

**Docs: [Claude Code settings](../02-Claude-Code-CLI/settings.md)**, **[Managed settings](../02-Claude-Code-CLI/permissions.md#managed-settings)**, **[MCP installation scopes](../06-MCP-Tools/General/mcp.md#managed-mcp-configuration)**

---

## Console Log Warning.Local

Source: https://github.com/anthropics/claude-code/blob/main/plugins/hookify/examples/console-log-warning.local.md
Path: 13-Enterprise-Admin/claude-code-plugins-hookify-examples-console-log-warning-local.md

You're adding a console.log statement. Please consider:
- Is this for debugging or should it be proper logging?
- Will this ship to production?
- Should this use a logging library instead?

---

## Claude Console roles and permissions | Claude Help Center

Source: https://support.claude.com/en/articles/10186004-claude-console-roles-and-permissions
Path: 13-Enterprise-Admin/claude-console-roles-and-permissions.md

# Claude Console roles and permissions

August 18, 2026


The Claude Console uses a role-based access system with six distinct roles: User, Claude Code User, Limited Developer, Developer, Billing, and Admin. Each role has specific permissions and capabilities designed to help teams manage their API access securely.

## Role types and permissions

### User

- Can only use the playground

- Can view MCP tunnels

- Cannot view API keys, usage logs, or billing details

### Claude Code User

- Can use the playground and **[Claude Code](../02-Claude-Code-CLI/code-home.md)**

- Can access Claude Code workspace in your org

- Can view MCP tunnels

### Limited Developer

- Can use the playground and Claude Code

- Can manage API keys and webhook endpoints

- Can view usage and cost data

- Can manage vaults and credentials

- Can view MCP tunnels  
  ​

### Developer

- Can use the playground and Claude Code

- Can manage API keys and webhook endpoints

- Can view usage and cost data

- Can manage vaults and credentials

- Can view sessions traces

- Can download files

- Can view MCP tunnels

### Billing

- Can use the playground

- Can manage billing details

- Can view usage and cost data

- Can view MCP tunnels

- Cannot access Claude Code workspace in your org

### Admin

- Can perform all actions available to User, Developer, and Billing roles

- Can manage users and their role assignments

- Can send events to active (non-archived) sessions

- Can create MCP tunnels

## Workspace-Level Permissions

- Organization Admins automatically receive Workspace Admin permissions in all Workspaces.

- Organization Billing role holders can view cost, usage, and limit values across all Workspaces.

- Organization-level roles serve as a baseline, while Workspace roles can grant additional permissions.

  - For example, a User at the organization level can be granted Admin permissions within specific Workspaces

## Important Notes

- Removing an Admin or Billing role does not automatically update the billing email in our payment processor.

- To modify the primary billing email address or add additional billing contact emails, please **[contact our Support Team](../15-Claude-AI-Features/how-to-get-support.md)**.

---

## Claude Enterprise activation promo for Claude Code and Cowork | Claude Help Center

Source: https://support.claude.com/en/articles/15282265-claude-enterprise-activation-promo-for-claude-code-and-cowork
Path: 13-Enterprise-Admin/claude-enterprise-activation-promo-for-claude-code-and-cowork.md

# Claude Enterprise activation promo for Claude Code and Cowork

August 6, 2026


We're issuing \$1,000 in usage credits to every user in a Claude Enterprise organization—up to \$10M per organization—who activates Claude Code or Claude Cowork for the first time. Credits are issued automatically and are scoped to the activating seat.

Available on usage-based Claude Enterprise plans only.

This article explains who’s eligible for the promo, how it works, and where admins can see activations in their organization. Additional offer terms apply and are available here: **[Claude Promotional Credit Offer Terms](https://www.anthropic.com/legal/promotion-credit-terms)**.

## Eligibility

## Plan eligibility

The promo is available on new and existing usage-based Claude Enterprise organizations, both sales-assisted and self-serve.

The promo isn’t available on:

- Legacy seat-based Claude Enterprise plans

- Team plans

- Pro and Max plans

Your organization’s subscription must be active and in good standing when a credit is issued and when it’s consumed.

### Seat eligibility

A seat earns a credit on its first successful message in Claude Code or Cowork, provided that:

- The user hasn’t previously sent a message in Claude Code or Cowork on any Claude Enterprise plan.

- The seat is in an eligible organization

- The seat is not associated with a free email domain (e.g. Gmail).

Each user can earn the credit on at most one Claude Enterprise organization, so a user who’s a member of multiple organizations earns the credit once.

## How the credit works

When a user in your Claude Enterprise organization sends their first successful message in Claude Code or Cowork, \$1,000 in usage credits is issued automatically to that seat. There’s nothing for admins or users to claim.

Once a credit is issued:

- It applies only to that user’s Claude Code and Cowork usage, not to Claude chat or any other product.

- It expires 90 days after issuance. Any unused portion is forfeited at expiration, with no expiration notification.

- Up to 10,000 seats per organization can earn a credit (\$10M maximum per organization). Once 10,000 seats in your organization have earned a credit, additional first-time activations don't earn one.

## Track activations in your organization

Admins can see who’s eligible by going to the **[usage page in organization settings](https://claude.ai/admin-settings/usage)** or:

- For Claude Code, go to **[Organization settings \> Claude Code](https://claude.ai/admin-settings/claude-code)**

- For Cowork, go to **[Organization settings \> Cowork](https://claude.ai/admin-settings/cowork)**

## Activation window

The activation window opens June 2, 2026 and ends on July 2, 2026 at 11:59:59 PM PT. Anthropic may contract or extend the window at its discretion.

Seats that activate during the window earn the credit. Credits issued during the window remain valid for 90 days from issuance, even if the window has closed by then.

## Temporary holds on eligibility

If we detect abuse, we may put the promotion on hold and hide the promotion admin panel. Your admin will also receive an email if that is the case. You can fill out **[this form](https://docs.google.com/forms/d/e/1FAIpQLSdVQd-gfK63yowPR0PfTZs74PuYcB1qJUHh3VxGdR7tN9rYQg/viewform)** to request a review of your organization’s recent activation activity.

---

## Claude Enterprise Admin API reference guide | Claude Help Center

Source: https://support.claude.com/en/articles/15330651-claude-enterprise-admin-api-reference-guide
Path: 13-Enterprise-Admin/claude-enterprise-admin-api-reference-guide.md

# Claude Enterprise Admin API reference guide

June 24, 2026


This guide covers **spend limits** and **spend limit increase requests** for your Claude Enterprise organization using the Claude Enterprise Admin API. Spend limits let you cap each member's usage credit spending over a recurring period, see where each member's limit is inherited from, and review or act on members' requests for a higher limit.

For per-user and time-bucketed usage and cost reporting, see the **[Analytics API reference guide](../04-API-Reference/Admin/platform-claude-com-http-beta-organization-analytics.md)**.

Claude Enterprise Admin API is currently in public beta and available to organizations on Enterprise plans with usage credits enabled.

## Overview

There are eight endpoints across two resources:

[TABLE]

Use the **spend limits** endpoints to answer, “What limit applies to each member, where does it come from, and how close are they to it?" and to set a per-user override. Use the **spend limit increase requests** endpoints to work the queue of member-submitted requests.

## Prerequisites and authentication

- Your organization must be on a Claude Enterprise plan.

- Usage credits must be enabled for your organization. Your Primary Owner can enable this under **Billing settings** in Claude.

- The Primary Owner needs to mint an Admin API key with one or both of the following scopes:

  - **`read:spend_limits`** (required for all `GET` endpoints)

  - **`write:spend_limits`** (required for `POST` and `DELETE` endpoints)

Pass the key in the **`x-api-key`** header on every request.

**Important:** Don't share API keys publicly or check them into source control.

## Create an Admin API key for your Claude Enterprise organization

1.  **Sign in as the Primary Owner**—Only the Primary Owner of your Claude Enterprise parent organization can create these keys.

2.  **Open API settings**—Go to **[Organization settings \> API](https://claude.ai/admin-settings/api-access)** and find the **Keys** section.

3.  **Click and create key**—Name the key and select the scopes you need from the **[scopes table](../04-API-Reference/Other/manage-claude-admin-api-keys.md#choose-scopes-for-a-claude-enterprise-key)**. You can combine scopes from different APIs (for example, `read:analytics` and `read:spend_limits`) on a single key.

4.  **Copy and store the secret**—Copy the displayed secret (starting with `sk-ant-api01-`) and store it in your secrets manager. The full secret is shown only once.

## Base URL

    https://api.anthropic.com

## Rate limiting

All eight endpoints share a single per-organization limit of **60 requests per minute**. Requests over the limit return **429 Too Many Requests**.

## Pagination

`GET /v1/organizations/spend_limits/effective` and `GET /v1/organizations/spend_limit_increase_requests` are paginated with an **opaque cursor**. The first request returns up to `limit` rows plus a `next_page` cursor. Pass that cursor unchanged as the `page` parameter on the next request, and repeat until `next_page` is `null`.

**Important:** Don’t change query parameters mid-sequence. Cursors are bound to the filters that issued them. If you change `user_ids[]`, `status[]`, or `actor_ids[]` and pass an old cursor, you'll get a 400 with "cursor does not match current query parameters". Start a new sequence from the first page instead.

Treat the cursor string as opaque: don't parse, modify, or construct it yourself.

## Serializing list parameters

List parameters use bracket notation: repeat the parameter name with `[]` for each value.

    user_ids[]=user_01AbCdEfGh&user_ids[]=user_01JkLmNoPq

## Error responses

[TABLE]

Error bodies have the shape:

    {"type": "error", "error": {"type": "<error_type>", "message": "..."}, "request_id": "req_..."}

`error.type` is a status-dependent discriminator: `invalid_request_error` (400), `authentication_error` (401), `permission_error` (403), `not_found_error` (404), `rate_limit_error` (429), `api_error` (500). `request_id` is always present and is the value to quote when contacting support. The Validations table under each endpoint lists the specific messages.

------------------------------------------------------------------------

## Concepts

### The spend limit hierarchy

Each member's usage credit spending is capped by an **effective spend limit**, resolved from a hierarchy of scope levels. When a member has no per-user override, they inherit the limit configured for their seat tier, their group (if your organization uses group-based limits), or the organization-wide default.

Reading `GET /v1/organizations/spend_limits/effective` returns every current member with their resolved effective limit, where that limit was resolved from (`source`), and their period-to-date spend. Setting a per-user override via `POST /v1/organizations/spend_limits` pins a member to a specific limit regardless of what they would otherwise inherit. Deleting the override returns them to the inherited limit.

### Scope

A scope identifies the level a spend limit is written or resolved at:

[TABLE]

`scope.type` is an open string. Clients should treat unknown values as opaque and fall through rather than fail. Additional scope types may be added in future.

### Period

`period` is the recurring window over which the limit is enforced and spend resets. The only value today is `"monthly"`.

`period` is an open string. Clients should treat unknown values as opaque and fall through rather than fail. Additional period values may be added in the future.

### Amounts and currency

All monetary values are strings in **minor units of the organization's billing currency** (cents, for USD). For example, `"50000"` represents 500.00 USD. Parse as a decimal and divide by 100 to display dollars. Avoid binary floating-point for large values.

`amount` is **nullable**: `null` means **unlimited** (no limit). `"0"` means usage credits are **disabled** for that member.

`period_to_date_spend` is the member's usage credit accrued since the start of the current `period`, in the same minor-unit format. It may include a fractional part (for example, `"41280.125"`).

### Increase request lifecycle

A **spend limit increase request** is created when a member clicks "request more usage" in claude.ai. Requests are not created via this API.

[TABLE]

Both `approved` and `denied` are terminal. A member has at most one `pending` request at a time.

Approving via `POST …/approve` writes the same per-user spend limit row that `POST /v1/organizations/spend_limits` writes. Setting a spend limit directly does *not* transition a pending request. Use the approve endpoint to resolve a request.

By default, Anthropic emails the member when their request is approved or denied. Pass `suppress_notification: true` on approve or deny to suppress that email (for example, when your own system notifies the member).

------------------------------------------------------------------------

## The SpendLimit object

A configured limit at one scope level.

    {
      "type": "spend_limit",
      "id": "spl_01AbCdEfGhIjKlMnOpQrSt",
      "created_at": "2026-05-01T12:00:00Z",
      "updated_at": "2026-05-03T09:14:11Z",
      "scope": { "type": "user", "user_id": "user_01AbCdEfGh" },
      "amount": "50000",
      "currency": "USD",
      "period": "monthly"
    }

[TABLE]

## The SpendSummary object

A computed per-member report row: the member's effective limit, where it came from, and their period-to-date spend. Not an addressable resource (no `id`).

    {
      "scope": { "type": "user", "user_id": "user_01AbCdEfGh" },
      "amount": "50000",
      "currency": "USD",
      "period": "monthly",
      "source": { "type": "seat_tier", "seat_tier": "enterprise_standard" },
      "spend_limit_id": "spl_01XyZaBcDeFgHiJkLmNoPq",
      "period_to_date_spend": "31402.5"
    }

[TABLE]

## The SpendLimitIncreaseRequest object

    {
      "type": "spend_limit_increase_request",
      "id": "slir_01AbCdEfGhIjKlMnOpQrSt",
      "created_at": "2026-05-04T16:22:09Z",
      "status": "pending",
      "resolved_at": null,
      "resolved_by": null,
      "actor": {
        "type": "user_actor",
        "user_id": "user_01AbCdEfGh",
        "name": "Jane Smith",
        "email_address": "[email protected]"
      },
      "spend_summary": {
        "scope": { "type": "user", "user_id": "user_01AbCdEfGh" },
        "amount": "50000",
        "currency": "USD",
        "period": "monthly",
        "source": { "type": "seat_tier", "seat_tier": "enterprise_standard" },
        "spend_limit_id": "spl_01XyZaBcDeFgHiJkLmNoPq",
        "period_to_date_spend": "48900"
      }
    }

[TABLE]

### Actor

[TABLE]

------------------------------------------------------------------------

## Spend limits

### 1. List effective spend limits

    GET /v1/organizations/spend_limits/effective

Returns **every current member** of the organization with their resolved effective limit and period-to-date spend. Members without a per-user override appear with `source.type` of `seat_tier`, `rbac_group`, or `organization`. Ex-members aren’t listed.

Requires scope: `read:spend_limits.`

#### Query parameters

[TABLE]

#### Response fields

[TABLE]

#### Example request

    curl "https://api.anthropic.com/v1/organizations/spend_limits/effective?limit=20" \
      -H "x-api-key: $ANTHROPIC_ADMIN_KEY"

#### Example response

    {
      "data": [
        {
          "scope": { "type": "user", "user_id": "user_01AbCdEfGh" },
          "amount": "50000",
          "currency": "USD",
          "period": "monthly",
          "source": { "type": "seat_tier", "seat_tier": "enterprise_standard" },
          "spend_limit_id": "spl_01XyZaBcDeFgHiJkLmNoPq",
          "period_to_date_spend": "31402.5"
        }
      ],
      "next_page": "page_..."
    }

#### Validations

[TABLE]

------------------------------------------------------------------------

### 2. Get a spend limit

    GET /v1/organizations/spend_limits/{spend_limit_id}

Returns a single spend limit by ID. Use this to inspect the row that a `SpendSummary.spend_limit_id` or a `POST` response referenced.

Requires scope: `read:spend_limits`.

#### Path parameters

[TABLE]

#### Response

A SpendLimit object.

#### Example request

    curl "https://api.anthropic.com/v1/organizations/spend_limits/spl_01AbCdEfGhIjKlMnOpQrSt" \
      -H "x-api-key: $ANTHROPIC_ADMIN_KEY"

#### Validations

[TABLE]

------------------------------------------------------------------------

### 3. Set a spend limit

    POST /v1/organizations/spend_limits

Sets a **per-user** spend limit override. Upsert: setting a limit for a user who already has one overwrites it in place.

Only `scope.type: "user"` is accepted. Seat-tier, group, and organization-level defaults are configured in claude.ai settings.

Setting a spend limit directly does *not* transition a member's pending increase request. Use the approve endpoint to resolve a request.

Requires scope: `write:spend_limits`.

#### Request body

[TABLE]

#### Response

A SpendLimit object reflecting the written override.

#### Example request

    curl -X POST "https://api.anthropic.com/v1/organizations/spend_limits" \
      -H "x-api-key: $ANTHROPIC_ADMIN_KEY" \
      -H "content-type: application/json" \
      -d '{"scope": {"type": "user", "user_id": "user_01AbCdEfGh"}, "amount": "75000"}'

#### Example response

    {
      "type": "spend_limit",
      "id": "spl_01RsTuVwXyZaBcDeFgHiJk",
      "created_at": "2026-05-11T10:02:44Z",
      "updated_at": "2026-05-11T10:02:44Z",
      "scope": { "type": "user", "user_id": "user_01AbCdEfGh" },
      "amount": "75000",
      "currency": "USD",
      "period": "monthly"
    }

#### Validations

[TABLE]

------------------------------------------------------------------------

### 4. Remove a spend limit

    DELETE /v1/organizations/spend_limits/{spend_limit_id}

Removes a **per-user** override so the member falls back to the inherited limit (seat tier, group, or organization default). Seat-tier, group, and organization-level rows can’t be deleted via this endpoint.

Requires scope: `write:spend_limits`.

#### Path parameters

[TABLE]

#### Response

    { "type": "spend_limit_deleted", "id": "spl_01RsTuVwXyZaBcDeFgHiJk" }

#### Example request

    curl -X DELETE "https://api.anthropic.com/v1/organizations/spend_limits/spl_01RsTuVwXyZaBcDeFgHiJk" \
      -H "x-api-key: $ANTHROPIC_ADMIN_KEY"


#### Validations

[TABLE]

------------------------------------------------------------------------

## Spend limit increase requests

### 5. List increase requests

    GET /v1/organizations/spend_limit_increase_requests

Lists increase requests, most recent first. Requests whose requester is no longer a member of the organization are excluded.

Requires scope: `read:spend_limits`.

#### Query parameters

[TABLE]

#### Response fields

[TABLE]

#### Example request

    curl "https://api.anthropic.com/v1/organizations/spend_limit_increase_requests?status[]=pending&limit=50" \
      -H "x-api-key: $ANTHROPIC_ADMIN_KEY"

#### Example response

    {
      "data": [
        {
          "type": "spend_limit_increase_request",
          "id": "slir_01AbCdEfGhIjKlMnOpQrSt",
          "created_at": "2026-05-04T16:22:09Z",
          "status": "pending",
          "resolved_at": null,
          "resolved_by": null,
          "actor": {
            "type": "user_actor",
            "user_id": "user_01AbCdEfGh",
            "name": "Jane Smith",
            "email_address": "[email protected]"
          },
          "spend_summary": {
            "scope": { "type": "user", "user_id": "user_01AbCdEfGh" },
            "amount": "50000",
            "currency": "USD",
            "period": "monthly",
            "source": { "type": "seat_tier", "seat_tier": "enterprise_standard" },
            "spend_limit_id": "spl_01XyZaBcDeFgHiJkLmNoPq",
            "period_to_date_spend": "48900"
          }
        }
      ],
      "next_page": null
    }

#### Validations

[TABLE]

------------------------------------------------------------------------

### 6. Get an increase request

    GET /v1/organizations/spend_limit_increase_requests/{spend_limit_increase_request_id}

Returns a single increase request.

Requires scope: `read:spend_limits`.

#### Path parameters

[TABLE]

#### Response

A SpendLimitIncreaseRequest object.

#### Example request

    curl "https://api.anthropic.com/v1/organizations/spend_limit_increase_requests/slir_01AbCdEfGhIjKlMnOpQrSt" \
      -H "x-api-key: $ANTHROPIC_ADMIN_KEY"

#### Validations

[TABLE]

------------------------------------------------------------------------

### 7. Approve an increase request

    POST /v1/organizations/spend_limit_increase_requests/{spend_limit_increase_request_id}/approve

Approves a pending request. Writes a **per-user spend limit** at `amount` for the requester and transitions the request to `approved`. The request doesn’t carry a requested amount, the admin supplies the new limit on approval.

Requires scope: `write:spend_limits`.

#### Path parameters

[TABLE]

#### Request body

[TABLE]

#### Response

The SpendLimitIncreaseRequest in status `approved`, with an additional `spend_limit` field containing the SpendLimit that was written.

#### Example request

    curl -X POST "https://api.anthropic.com/v1/organizations/spend_limit_increase_requests/slir_01AbCdEfGhIjKlMnOpQrSt/approve" \
      -H "x-api-key: $ANTHROPIC_ADMIN_KEY" \
      -H "content-type: application/json" \
      -d '{"amount": "75000", "suppress_notification": true}'

#### Example response

    {
      "type": "spend_limit_increase_request",
      "id": "slir_01AbCdEfGhIjKlMnOpQrSt",
      "created_at": "2026-05-04T16:22:09Z",
      "status": "approved",
      "resolved_at": "2026-05-11T10:05:02Z",
      "resolved_by": {
        "type": "scoped_api_key_actor",
        "scoped_api_key_id": "apikey_01ZyXwVuTsRqPoNmLkJiHg"
      },
      "actor": {
        "type": "user_actor",
        "user_id": "user_01AbCdEfGh",
        "name": "Jane Smith",
        "email_address": "[email protected]"
      },
      "spend_summary": null,
      "spend_limit": {
        "type": "spend_limit",
        "id": "spl_01RsTuVwXyZaBcDeFgHiJk",
        "created_at": "2026-05-11T10:05:02Z",
        "updated_at": "2026-05-11T10:05:02Z",
        "scope": { "type": "user", "user_id": "user_01AbCdEfGh" },
        "amount": "75000",
        "currency": "USD",
        "period": "monthly"
      }
    }

#### Validations

[TABLE]

------------------------------------------------------------------------

### 8. Deny an increase request

    POST /v1/organizations/spend_limit_increase_requests/{spend_limit_increase_request_id}/deny

Denies a pending request. Idempotent on `denied`: denying an already-denied request returns 200 with the existing resource. Denying an already-approved request is rejected so automation can distinguish a retry from a conflicting decision.

Requires scope: `write:spend_limits`.

#### Path parameters

[TABLE]

#### Request body

[TABLE]

#### Response

A SpendLimitIncreaseRequest in status denied.

#### Example request

    curl -X POST "https://api.anthropic.com/v1/organizations/spend_limit_increase_requests/slir_01AbCdEfGhIjKlMnOpQrSt/deny" \
      -H "x-api-key: $ANTHROPIC_ADMIN_KEY" \
      -H "content-type: application/json" \
      -d '{"suppress_notification": true}'

#### Validations

[TABLE]

---

## Claude Enterprise Analytics API reference guide | Claude Help Center

Source: https://support.claude.com/en/articles/13703965-claude-enterprise-analytics-api-reference-guide
Path: 13-Enterprise-Admin/claude-enterprise-analytics-api-reference-guide.md

4.  Claude Enterprise Analytics API reference guide

# Claude Enterprise Analytics API reference guide


## Overview

The Claude Enterprise Analytics API gives your organization programmatic access to engagement data for Claude and Claude Code usage within your Enterprise organization. Whether you're building internal dashboards for user activity or tracking adoption of projects, this API provides the aggregated metrics you need.

## Data aggregation

All data is aggregated **per organization, per day**. Each endpoint returns a snapshot for a single date that you specify. **Data for day (N-1) is run at 10:00:00 UTC time on day N**, and is available for querying three days after aggregation, to ensure accuracy of data.

If data is not available within the timeline above, this usually indicates a data pipeline failure that our team will need to investigate internally. We are usually aware of such problems, but please raise this to your CSM if you want a gut check, or suspect something else.

## Enabling access

In order to mint new analytics API keys, you must be a Primary Owner within your Enterprise organization. You can do so by navigating to **[claude.ai/analytics/api-keys](http://claude.ai/analytics/api-keys)**.

Some more details that might be helpful:

- You can *enable/disable* access to the public API anytime. If you disable access by toggling the switch off, all requests will be denied.

- You’ll need a key with the `read:analytics` scope in order to access the API. You can create multiple keys for your organization, but rate limits apply at the *organization* level, not the *key* level. See the “Rate limiting” section below.

- As always, *we strongly recommend handling API keys securely*: *never* share these keys publicly - they are secret, and should be shared securely.


## Base URL

All requests are sent to:

    https://api.anthropic.com/v1/organizations/analytics/

## Authentication

Every request requires an API key passed in the `x-api-key` header. Your API key must have the `read:analytics` scope. You can create and manage API keys from the claude.ai admin settings under the API Keys section.

**Example request headers:**

    x-api-key: $YOUR_API_KEY

## Pagination

Several endpoints return paginated results. Pagination uses a cursor-based approach, where the response includes a `next_page` token you pass back in your next request to retrieve the following page of results.

Two optional parameters control pagination:

**limit** (integer): Number of records per page. Defaults to 20 for the /users endpoint and 100 for all other endpoints. The maximum is 1000.

**page** (string): The opaque cursor token from the previous response's `next_page` field. Omit this on your first request.

When there are no more results, `next_page` will be `null` in the response.

## Error responses

All endpoints return standard HTTP error codes:

[TABLE]

## Rate limiting

We do have default rate limits in place. If that isn’t sufficient for your use case, we’d love to understand why. If necessary, we can adjust the rate limits for your organization—please reach out to your CSM.

## Endpoints

### 1. List user activity

`GET /v1/organizations/analytics/users`

Returns per-user engagement metrics for a single day. Each item in the response represents one user and includes their activity counts across Claude (chat) and Claude Code.

**Query parameters**

[TABLE]

**Response fields (per user)**

[TABLE]

**Example request**

    curl -X GET "https://api.anthropic.com/v1/organizations/analytics/users?date=2025-01-01&limit=3"
       --header "x-api-key: $YOUR_API_KEY"

### 2. Activity summary

`GET /v1/organizations/analytics/summaries`

Returns a high-level summary of engagement and seat utilization **per-day** for your organization for a given date range. The response is a list of days with aggregated counts within the date range. Note that the maximum difference between `ending_date` and `starting_date` must be **31 days**, and there is a three-day delay in data availability. This is useful for tracking daily active users, weekly and monthly trends, and seat allocation at a glance.

**We define “active”** if any one of the following is true:

- The user sent at least one chat message on Claude (chat).

- The user had at least one Claude Code (local or remote) session associated with the C4E org, with tool use/git activity

**Query parameters**

[TABLE]

**Response fields**

[TABLE]

**Note:** The rolling windows for weekly and monthly counts look backward from the specified date (inclusive). If data is incomplete for some days within the window (for example, if the date is less than 30 days in the past), the monthly count may undercount activity.

**Example request**

    curl -X GET "https://api.anthropic.com/v1/organizations/analytics/summaries?starting_date=2025-01-01"
       --header "x-api-key: $YOUR_API_KEY"

### 3. Chat project usage

`GET /v1/organizations/analytics/apps/chat/projects`

Returns usage data broken down by chat project for a given date. Projects are specific to Claude (chat), so this endpoint focuses on that surface. Each item shows the project name, how many unique users interacted with it, and the total number of conversations held in that project.

**Query parameters**

[TABLE]

**Response fields (per project)**

[TABLE]

**Example request**

    curl -X GET "https://api.anthropic.com/v1/organizations/analytics/apps/chat/projects?date=2025-01-01&limit=50"
       --header "x-api-key: $YOUR_API_KEY"

### 4. Skill usage

`GET /v1/organizations/analytics/skills`

Returns skill usage data across both Claude (chat) and Claude Code within your organization for a given date. Each item represents a skill and shows how many unique users used it.

**Query parameters**

[TABLE]

**Response fields (per skill)**

[TABLE]

**Example request**

    curl -X GET "https://api.anthropic.com/v1/organizations/analytics/skills?date=2025-01-01"
       --header "x-api-key: $YOUR_API_KEY"

### 5. Connector usage

`GET /v1/organizations/analytics/connectors`

Returns MCP/connector usage data across both Claude (chat) and Claude Code within your organization for a given date. Each item represents a connector and shows how many unique users used it. Connector names are normalized from various sources — for example, "Atlassian MCP server," "mcp-atlassian," and "atlassian_MCP" would all appear as "atlassian."

**Query parameters**

[TABLE]

**Response fields (per connector)**

[TABLE]

**Example request**

    curl -X GET "https://api.anthropic.com/v1/organizations/analytics/connectors?date=2025-01-01"
       --header "x-api-key: $YOUR_API_KEY"

---

## Claude Enterprise consumption guide | Claude Help Center

Source: https://support.claude.com/en/articles/14782391-claude-enterprise-consumption-guide
Path: 13-Enterprise-Admin/claude-enterprise-consumption-guide.md

# Claude Enterprise consumption guide


Claude Enterprise gives your organization access to powerful AI across chat, Claude Code, Claude Cowork, Claude Design, and Claude in the tools your teams already use, including Microsoft 365, Chrome, and Slack. With that access comes the responsibility of managing consumption effectively—ensuring your team gets maximum value while keeping usage predictable and within budget.

This guide walks Enterprise admins through the key levers available to control and optimize token consumption: setting spend caps, configuring role-based access controls, educating users, choosing the right model and effort level for the right task, and measuring what your spend produces.

------------------------------------------------------------------------

## Why consumption management matters

Claude Enterprise is priced on a per-seat, usage-based model. Your org's consumption pool is shared across all users, and some surfaces—particularly Claude Code and Cowork—consume tokens at a significantly higher rate than standard chat.

Admins who proactively configure spend limits and educate users can reduce waste and ensure that high-value use cases get the capacity they need.

------------------------------------------------------------------------

## Understanding token intensity across surfaces

[TABLE]

Other surfaces also draw on your organization's usage and appear as separate products in Analytics, the spend export, and the Analytics API: (a) Claude for M365 (Excel, PowerPoint, Word, Outlook add-ins), subject to the same org, group, and per-user spend limits and model restrictions; (b) Claude Design (beta), which bills from your organization's consumption at standard API rates with org, group, and per-user limits applying; (c) Claude in Chrome, whose side panel runs as a Claude Cowork session, so multi-step browsing tasks consume like other Cowork agentic work (manage under Organization settings \> Cowork); (d) Claude Tag (Claude in Slack, beta), whose channel work bills to the organization's usage balance rather than to individual seats, so per-user and group limits don't cap it; set the Claude Tag spend limit (and optional per-channel limits) at **[claude.ai/admin-settings/usage/claude-tag](https://claude.ai/admin-settings/usage/claude-tag)**. DMs with Claude Tag bill to the sender's own seat.

**Admin tip: Set expectations with your team**

Users running Claude Code or Cowork workflows may not realize how token-intensive their sessions are. A single Cowork task or Claude Code debug session can consume many more tokens than chat. Include this context in any user onboarding you send.

------------------------------------------------------------------------

## Role-based access controls

Role-based access controls (RBAC) let you group users and manage their access to Claude surfaces and consumption budgets as a unit rather than individual by individual. This is the most scalable way to govern usage in larger organizations.

### How to structure groups

Think about groups in terms of job function and use case, not organizational hierarchy. A few principles:

- Create groups that map to distinct usage patterns, not org chart boxes. "Engineering" and "Sales" are more useful than "North America" and "EMEA" for consumption management. Functional groups also make smart reports more useful, since reports scoped to a team produce findings specific enough to act on.

- Limit group proliferation. More than 8–10 groups becomes hard to manage. Start with 4–6 and split only if usage patterns clearly diverge.

- Use groups to gate access to high-intensity surfaces. For example: only members of the "Engineering" group can access Claude Code; other users see Chat and Cowork only. Access is granted by the custom roles you assign to each group, and it only takes effect for members whose organization role is set to Custom. Members left on the built-in User role keep everything enabled org-wide. Groups can be created manually or synced from your identity provider. See **[Set up role-based permissions on Enterprise plans](https://support.claude.com/en/articles/13930458-set-up-role-based-permissions-on-enterprise-plans)**.

- Assign group-level spend caps as a starting point, then override at the user level for outliers (e.g., a non-technical PM who needs Claude Code for a specific project).

### Group spend management

Once groups are configured:

- Review group consumption weekly during initial rollout, monthly thereafter.

- When a group consistently approaches its limit (see Time at limit on the Usage page), investigate before automatically raising it. A smart report scoped to that group shows what the spend is producing, or you can send that group a short survey to ask directly. The right response might be a lower effort cap or clearer model guidance. If the report shows high-value work, raise the limit, because members at their limit are often your top adopters.

- Consider assigning a "group owner" in each department who is responsible for reviewing usage and fielding questions from their team. This distributes the admin burden and puts someone with business context in the loop. You don't need to make these people Owners or Admins: create a custom role that grants the Analytics (Can view) admin permission—and optionally Billing (Can view) so they can see the Usage page—and assign it to a small 'usage reviewers' group. Admin permissions only apply to members whose role is set to Custom, and Analytics view access is organization-wide rather than limited to their group.

- To give a group owner a view of just their team, you can let them run smart reports scoped to their groups. See **[Let team members run smart reports for specific groups](let-team-members-run-smart-reports-for-specific-groups.md)**.

**Governance tip: Surface access as a first gate**

Before worrying about token-level limits, make sure the right people have access to the right surfaces. Giving everyone Claude Code and Cowork access on day one is the fastest way to generate unexpected consumption. Roll out higher-intensity surfaces in waves, starting with the teams most likely to use them productively.

------------------------------------------------------------------------

## Set spend limits

Spend limits are your primary tool for controlling consumption. Claude Enterprise lets admins set limits at three levels: the organization level, the group level (with RBAC), and the individual user level, plus optional pooled budgets that give a group one shared monthly amount. **Our recommended approach is to start with RBAC group-level limits and per-user limits**—these give you precise, targeted control without the risk of cutting off your entire org if a limit is hit.

### Org-level spend limits

The org-level limit is available as a hard ceiling across all users and surfaces, but use it carefully: hitting it affects everyone simultaneously, which can be disruptive. Most admins find that managing consumption at the group and user level gives them better outcomes with less operational risk.

### Group spend limit

Group spend limits let you assign a per-user monthly spend limit to an entire group, so every member of that group inherits the same limit without setting it individually. This is the most scalable way to manage consumption in mid-to-large orgs, and it's where admins should start.

Note the following precedence rules:

- **Individual limits always override group limits**, regardless of which is higher.

- **If a user belongs to multiple groups with different limits**, the **Multi-group spend limit** setting under **Spending defaults** controls whether the higher or lower limit applies. The seat-type default limit is included in this comparison.

- **Org-wide limits remain the hard ceiling.**

- **No limit anywhere = no limit.** If a member has no individual limit and none of their groups have a limit, their spend isn't capped.

**How to configure:** Organization settings → Usage → By group/tier. Set limits to either a specific dollar amount or "Unlimited."

### Pooled group budgets (beta)

A group spend cap gives each member the same individual limit. A pooled budget gives the whole group one shared monthly amount that every member draws from. Use it when a team has a fixed budget but uneven usage across members.

Every request counts against both the member's own monthly limit and the group's pool. The member stops at whichever runs out first. Usage can go slightly over a limit before it pauses. Set the per-member limit as a guardrail, not an equal share of the pool. **When the pool runs out, usage pauses for every member of the group** until an admin raises it or it resets the next month. Billing admins get email alerts at 50%, 75%, 95%, and 100% of the pool.

If a member belongs to more than one pooled group, the largest pool is used first. You can set a custom order under **Pooled budget priority**.

**How to configure:** **Organization settings \> Usage \> Spend limits \> By group/tier**. Open the group's menu, choose **Edit limits**, and set a **Pooled monthly budget** and a **Member monthly limit**. The group needs a monthly spend limit first. Owners, Primary Owners, the Billing role, and custom roles with Billing (Can manage) can set pools. In beta, the Billing permission applies to the whole org, so anyone who can edit one group's pool can edit every group's pool. Pools aren't available through the Admin API yet. See **[Manage pooled group budgets on Enterprise plans](../17-Billing-Plans/manage-pooled-group-budgets-on-enterprise-plans.md)**.

### User-level spend caps

User-level caps let you set consumption limits for individual accounts. These are essential for organizations where usage varies significantly across roles—a developer using Claude Code daily has very different needs than a marketer using chat for copywriting.

**Best practices for user-level caps:**

- Define consumption tiers based on role type before rollout. A tiered structure—e.g., light, standard, power—makes it easier to assign and adjust caps consistently.

- Start conservatively. It's easier to increase a cap based on a user's request than to walk back an overage conversation.

- Give power users (engineers, data scientists, researchers) higher or uncapped individual limits, but offset this by ensuring they use the right Claude model for the right task.

- Check Time at limit on the Usage page monthly to see how often members are reaching their limits. Also watch for users consuming very little, who may not be activated yet.

------------------------------------------------------------------------

## Model selection guidance

One of the most impactful things an admin can do is set clear guidance for users on which model to use for which tasks. Model choice has a direct and significant impact on spend.

Effort level is a second consumption lever. Users can choose how much thinking Claude applies to each response, and higher effort levels consume more tokens than lower ones. Encourage users to reserve Max effort for only the most demanding tasks and to use lower effort for routine tasks.

### The right model for the right task

[TABLE]

### Set your organization's default model

Beyond guiding users toward the right model, you can set the model that new conversations start with for everyone in your org. This is one of the most direct consumption levers available—the default shapes what the majority of users run day to day.

You have two options:

- **Anthropic recommended** — automatically updates as new models ship, so your org always starts on our current recommended general-purpose model with no manual upkeep.

- **Choose your own** — sets a specific model as the org default and holds it there until you change it. Use this when you want to standardize on a known model for consumption predictability (for example, defaulting to Opus and giving Fable to the roles that do the hardest work).

This setting applies to new conversations in chat, Claude Cowork, Claude Code (CLI 2.1.199 or later), and Claude for Microsoft 365. If the selected model isn't available in a product, Anthropic's recommended default is used. If you also pin a model for Claude Code through managed settings, that setting takes precedence for the CLI and IDE. See **[Set a default model for your organization](set-a-default-model-for-your-organization.md)**.

**Sticky defaults - always start with the default model and effort level (beta).** By default, the model picker is sticky: new conversations start on whatever model a member last used. To change that, turn on **Always start with the default model and effort level** in **Organization settings \> Models**. Every new conversation then starts on the org's default model and default effort level. Members can still change both within a conversation.

Sticky defaults upgrade automatically. When a launch changes the default model, the sticky model moves to the new default, so no one is left on an older model. Roles that set their own default model have the same switch in the role editor. It works in chat, Claude Cowork, Claude Code, Claude for Microsoft 365, Claude Design, and Claude Science. See **[Set a default model for your organization](set-a-default-model-for-your-organization.md)**.

To follow the recommended configuration above, set defaults by role through Custom Roles. For example, you might make Claude Opus the org default and Claude Fable the default for your research and analysis roles. This builds on the RBAC groups you've already set up (see **Role-based access controls** above).

**How to configure:** **Organization settings \> Models** for the org default. For role defaults, go to **Organization settings \> Roles \> select a role \> Models tab**.

### Manage model access for your organization

Beyond setting a default, you can restrict which models are available at all—a firmer lever than guidance alone. This works at two levels:

- **Organization level:** each model is enabled or disabled for everyone, including Owners and Admins. Disabling a model here removes it from every picker org-wide.

- **Custom role level:** for members on Custom roles, each role grants access to a *subset* of what's enabled at the org level. A role can't grant a model the org has disabled — the org setting is always the ceiling.

If a member belongs to multiple groups with different custom roles, access is **additive** — they get every model any of their roles grants (as long as it's enabled org-wide).

**Capping effort level by role**

Beyond restricting which models a role can use, you can cap the **maximum effort level** members on that role can select per model — a more granular version of the effort guidance already covered above. You can also set an effort cap for the whole organization, which is the highest level any role can allow. If a member has multiple roles, the highest effort cap across those roles wins.

You can also set a **default effort level** for a role's default model, so new conversations start at the level you choose, either Anthropic's recommended default or a specific level. The default can't be higher than the effort cap for that model. See **[Set a default model for your organization](set-a-default-model-for-your-organization.md#h_d5373c4106)** to learn more.

**Where this applies**

Model access and effort restrictions are enforced across most Claude products, including chat (web, desktop, mobile), Claude Cowork, and Claude Code (CLI 2.1.199 or later—earlier versions still show restricted options but requests using them are rejected). Claude in Chrome and Claude Security don't support this yet. For the current list of supported products, see **[Manage model access for your organization](https://support.claude.com/en/articles/15694740)**.

**How to configure:** **Organization settings \> Roles \> select a role \> Models tab**. Set model access, an optional effort cap per model, an optional role-level default model, default effort level, and the **Always start with the default model and effort level** switch. To manage configuration across the org, go to **Organization settings \> Models**. More details in **[Manage model access for your organization](https://support.claude.com/en/articles/15694740)**.

### Admin configuration recommendations

- If you have high-volume, low-complexity workflows (e.g., summarizing support tickets, generating first-draft emails), evaluate whether Haiku is a better fit—it can significantly reduce consumption for these use cases.

- Periodically audit which models your users are actually selecting. If most of your consumption is on Opus, that's a signal that your model guidance isn't landing.

### What to tell your users about model choice

**Sonnet is your daily driver.** It's fast, highly capable, and is designed for the vast majority of tasks—writing, analysis, coding help, and Q&A.

**Opus is for the harder, more complex work.** Use it when you're working on a genuinely complex multi-step problem, or when quality matters more than speed.

**When in doubt, start with Sonnet.** You can always switch the model mid conversation to Opus if you need more depth.

------------------------------------------------------------------------

## Using organization instructions to shape user behavior

Organization instructions let admins inject standing guidance into every Claude conversation across your organization—effectively giving Claude a system prompt that reflects your team's norms, best practices, and guardrails. This is a high-leverage tool for shifting user behavior without adding friction, because the guidance shows up in-product at the moment of use rather than in documentation users have to go find.

A few ways you can use organization instructions to manage consumption and usage patterns:

- **Nudge-against token-intensive output formats**. If you've noticed proliferation of a particular artifact type (e.g., HTML dashboards being shared in cross-functional threads where a simpler format would do), you can instruct Claude to confirm with the user before generating one. This adds a lightweight check without removing the capability entirely.

- **Point users to internal resources.** Reference your team's wiki, best-practices docs, or usage guidelines directly in the preference. Claude will surface them when relevant—steering users toward the right internal context instead of reinventing it each time.

- **Reinforce model selection norms.** Remind Claude (and by extension, users) that Sonnet is the default and Opus is reserved for specific workflows. This complements user education without requiring everyone to internalize it up front.

------------------------------------------------------------------------

## Track usage and spend

### Analytics page

The Analytics page within the user menu (**claude.ai/analytics**) is the fastest way to get a read on your org. It shows weekly active users, seat utilization, top connectors, total spend (MTD/QTD/YTD), spend by model, and a top-10 users-by-spend leaderboard. Product-specific views for chat, Claude Code, Cowork, and Claude Design break down activity for each surface. **[Learn more](../17-Billing-Plans/view-usage-analytics-for-team-and-enterprise-plans.md)**.

### Skills analytics and per-skill ROI

Each skill represents a repeatable workflow—prepping a sales call, reviewing a contract—so its cost can be weighed directly against what that workflow is worth. The Skills view in Analytics shows users, cost per use, and total uses for every skill in your org, filterable by group ("what skills is my legal team using?") or product surface.

To run an ROI analysis:

1.  Export the skills table to CSV from the Skills view.

2.  Assign a value per run to each skill—a rough estimate of what the completed task is worth, such as the employee time it replaces (e.g., "prepping a sales call is worth ~\$20 to us").

3.  Compute in your spreadsheet: (value per run − cost per use) × total uses gives the net value each skill has generated.

The calculation currently happens outside the product, but the CSV export makes it a quick spreadsheet exercise. Even rough estimates tell a compelling story: a call-prep skill costing \$0.90 per run against \$20 of value returns 20x on every use.

### Spend report CSV export

If you need a one-off detailed breakdown, you can export a per-user, per-model spend report as a CSV from the Analytics page: in the spend section ("How much is Claude costing?") on the Overview tab, click "Export spend report" and choose MTD, last month, last 90 days, or a custom range up to 90 days back.

### Analytics chat

Analytics chat lets you ask questions about your org's usage in plain language. Type a question—"show me daily spend for the last 30 days," "who are our top spenders," "what's our seat utilization rate"—and Claude returns a chart and a short written summary of what it found. You can follow up to refine, drill in, or pivot without starting over.

Use this when you have a specific question and don't want to navigate the dashboard, or when you're exploring trends and want fast back-and-forth. Results cover the last 30 days by default; specify a different range in your question if you need it. Data refreshes daily. **[Learn more](../22-Safety-Policy/use-analytics-chat-to-ask-claude-about-usage.md)**.

### Analytics API

For programmatic access, use the Claude Enterprise Analytics API. Pull a ranked list of users by tokens used or dollars spent, or look at usage and cost trends over time broken down by product, model, RBAC group, context window, region, or service tier (standard vs. fast). Grouping the cost report by RBAC group gives per-department spend for chargeback without exporting per-user rows. Each request is capped at 31 days wide, starting within the last 365 days, and no earlier than January 1, 2026.

Your Primary Owner can create a key with the read:analytics scope under **Organization settings \> API**. Cost and usage data typically lands within about four hours (occasionally up to 24) and can be revised until seven days after the end of the month it belongs to. For invoicing-grade totals, wait until that window closes before you query a month. Engagement endpoints such as users, skills, plugins, and connectors update daily with roughly a one-day lag. **[Learn more](../04-API-Reference/Other/manage-claude-analytics-api.md)** and review the **[API reference guide](../04-API-Reference/Admin/platform-claude-com-http-beta-organization-analytics.md)**.

### Per-user skills, plugin, and connector usage

The Analytics API also breaks skill, plugin, and connector usage down per user. The `skills`, `plugins`, and `connectors` endpoints each support a `group_by[]=user_id` parameter, turning the org-wide totals you see in the dashboard into one row per user per skill (or plugin, or connector)—so you can see exactly who is running a given skill, how often, and which team members have actually invoked a plugin you rolled out. You can also group by RBAC group or product surface, or filter down to a single skill, plugin, or connector by name.

This is useful for the same per-skill ROI questions above, just broken down by individual user instead of exported to a spreadsheet—for example, confirming a plugin rollout actually landed with the team it was distributed to, rather than just checking that it was installed. Learn more in the API reference: **[skills](../04-API-Reference/Admin/platform-claude-com-http-beta-organization-analytics-skills-list.md)**, **[plugins](../04-API-Reference/Admin/platform-claude-com-http-beta-organization-analytics-plugins-list.md)**, **[connectors](../04-API-Reference/Admin/platform-claude-com-http-beta-organization-analytics-connectors-list.md)**.

### Admin API

For organizations managing limits across many groups, the **[Admin API](claude-enterprise-admin-api-reference-guide.md)** moves cost-control workflows into scripts — automating increase-request reviews, flagging members near their limits, and surfacing rapidly changing usage at scale. The API reads every member's effective limit and month-to-date spend and sets or clears per-user overrides. Group, seat-type, org-level limits, and pooled group budgets are still configured in Organization settings. The Admin API's user-management endpoints (currently in beta for Enterprise organizations) also let you create groups, add or remove members, and read your custom roles programmatically. See **[User management](../04-API-Reference/Other/manage-claude-user-management.md)**.

### Spend-threshold alerts

Spend-threshold alerts notify admins at 75% and 90% of an org-level spend limit, giving you time to raise the cap before anyone is blocked mid-task. Pooled group budgets send their own alerts to billing admins at 50%, 75%, 95%, and 100% of the pool.

### In-product surveys

The analytics above tell you how much your team uses Claude; surveys tell you what they're doing with it and what's getting in their way. From **[Analytics \> Surveys](https://claude.ai/analytics/surveys)**, admins can send users a short survey that appears in Cowork or chat at a natural break in their work. Pick the audience by group, set a window, and review aggregated results in the console, including response rate, a breakdown by surface and group, and a feed of written answers, with a CSV export per survey. Learn more about **[creating surveys for your organization](https://support.claude.com/en/articles/16764057)**.

### Time at limit (Usage page)

The Usage page in **Organization settings \> Usage** opens with a view of how often members are reaching their spend limits. It shows time at limit per active seat this month, for all members and for your power users (the top 10% by spend), plotted against prior months with a forecast. It also calls out which group or limit type accounts for the most time at limit, so you know where to look first.

Use this view as your check on whether limits fit how people work. Rising time at limit means members are getting blocked mid-task. You can adjust limits from the same page: raise a default, or increase the limits of the members who reached them.

### Smart reports (beta)

Analytics shows how much each team spends. Smart reports show what that spend is producing. Scope a report to a team, a date range, and the products you want to include. Claude reviews a sample of transcripts and breaks spend down by workstream and output type.

For consumption management, focus on these sections:

- **Cost per session by type of output.** See which kinds of work are expensive to produce, and whether that cost matches their value.

- **Most common frictions and inefficiencies.** Rework loops and sessions with no usable output are spend with no return. Repeated friction often points to a connector or setting you can turn on in minutes.

- **Reusable skills and workflows to build.** Packaging repeated work as a shared skill gets the whole team a consistent result faster. Once it's rolled out, track its cost and ROI in the Skills view.

- **Most expensive sessions.** See where spend concentrates.

Smart reports are designed to help you understand adoption and plan investment, not to evaluate individual performance. For availability, setup, and how to create and share reports, see **[Get started with smart reports](../15-Claude-AI-Features/get-started-with-smart-reports.md)**.

### Surveys (beta)

You can run a short survey inside Claude to learn what people use Claude for, what it's worth to them, and where they're stuck. Then compare their answers with your spend data.

Surveys show up as a card between tasks in Claude Cowork, or after a reply in chat, and never interrupt a running task. You can send one to all members or to specific groups, pick Cowork, chat or both, and set start and end dates. Each user sees a survey only once. Results update while the survey is live: response rate, bar charts for multiple-choice questions, and a feed of written answers you can filter by group. You can also export them to CSV.

For consumption management, surveys are useful for:

- **Checking value behind high spend.** When a group is often at its limit, ask what it's using Claude for before you raise or lower the cap.

- **Filling in your ROI numbers.** Ask how much time Claude saves on key workflows, and use the answers as your value-per-run estimates in the Skills ROI analysis.

- **Finding friction.** Ask where Claude isn't helping. Answers often point to a connector, skill, or model setting to change.

**How to configure:** **Analytics \> Surveys \> New survey**. Primary Owners, Owners, Admins, and custom roles with Analytics view access can create surveys and see results. Responses go to your org's admins, and Anthropic accesses them only as needed to run the service. Don't put regulated data such as PHI in responses. Responses aren't anonymous: the export includes each respondent's email. Surveys aren't available yet for organizations using customer-managed encryption keys (CMEK). See **[Create surveys for your organization](create-surveys-for-your-organization.md)**.

------------------------------------------------------------------------

## End user education

Technology controls will get you most of the way, but user behavior drives the rest. A team that understands how consumption works will make better choices independently—and surface fewer edge cases for you to troubleshoot.

### What to communicate to end users

When you onboard users, share the following:

**How Claude is billed**

- Usage is measured in tokens. Long prompts and long conversations consume more tokens.

- Claude Code and Cowork sessions are significantly more token-intensive than chat. A single long coding session can use many more tokens than a typical chat session.

- Check your usage in settings by toggling to **Settings → Usage**.

**How to choose a model**

- Sonnet is the default and handles most tasks well. Use Opus only when Sonnet isn't getting you where you need to go.

- Your org has a default model set for new conversations; you can still pick a different model. Choose it at the start of a task.

- The model selector is visible in the interface—remind users to check it, especially if they're running complex tasks.

- Unless your admin has turned on Always start with the default model, the model selector remembers your last choice, so check it before you start a task.

- The effort level appears next to the model name. Higher effort means more thorough responses but higher token consumption, so match it to the task.

**What happens when they hit a cap**

- Users are notified in-product as they approach their spend limit and, once they hit it, can click "Request more usage" to send an increase request to admins without leaving Claude. Tell users who their approver is and your expected turnaround.

- Nothing they've already produced is lost. The request that's already in flight completes, but further requests are blocked, so a multi-step Claude Code or Claude Cowork task may pause before it finishes. They can pick the work back up as soon as an admin raises the limit, or when limits reset at 00:00 UTC on the 1st of the month.

- If your group uses a pooled budget, usage can pause for the whole group when the shared pool runs out, even if you haven't reached your own limit.

**Resources to share with users**

- **[Anthropic Academy](https://www.anthropic.com/learn)**

- **[Change the model, effort, and thinking settings](https://support.claude.com/en/articles/8664678-change-the-model-effort-and-thinking-settings)**

---

## Claude in Chrome admin controls | Claude Help Center

Source: https://support.claude.com/en/articles/13065128-claude-in-chrome-admin-controls
Path: 13-Enterprise-Admin/claude-for-chrome-admin-controls.md

# Claude in Chrome admin controls


This article explains how Team and Enterprise owners can manage Claude in Chrome for their organization.

Claude in Chrome admin controls are available for Team and Enterprise plans.

Claude in Chrome is a browser extension that allows Claude to read, click, and navigate websites on behalf of your users. As an owner, you control whether the extension is available for users to install and which sites they can access.

**Note:** On Max and Team plans, the side panel runs as a Claude Cowork session, and this is rolling out to Pro plans in the coming weeks. On Enterprise plans, the side panel runs as a Cowork session once you've enabled it for your organization—see **[Enable the Cowork side panel](claude-for-chrome-admin-controls.md#h_bdb63199e1)** below. Where the Cowork side panel isn't enabled, users have the classic side panel, and your existing organization settings apply to it unchanged.

## Access Claude in Chrome settings

To manage Claude in Chrome settings for your organization:

1.  Sign in to Claude with your Owner or Primary Owner account.

2.  Navigate to **[Organization settings \> Claude in Chrome](https://claude.ai/admin-settings/browser-extension)**.

## Enable or disable the extension

**Important:** Before enabling Claude in Chrome for your organization, review **[Use Claude in Chrome safely](https://support.claude.com/en/articles/12902428)** to understand the risks of browser-based AI, including the prompt injection classifiers, the safeguards in place, and remaining risks.

Use the **Enable for your team** toggle to enable or disable Claude in Chrome for your entire organization.

- **Team plans:** The extension is enabled by default. Disable it if you prefer users not to have access.

- **Enterprise plans:** The extension is on by default unless you've already disabled it. In organizations that have enabled HIPAA, the Claude in Chrome extension stays off until an Owner turns it on in **Organization settings \> Claude in Chrome**.

Claude in Chrome and Claude Cowork are managed separately. Enabling Claude in Chrome for your organization lets users use the extension. Whether Claude can use it within Cowork is a separate capability setting, and users' browsers still need the extension deployed or installed. Cowork also has a browser built into the Claude Desktop app that doesn't require the extension; it's off by default on Enterprise plans and managed from **[Organization settings \> Cowork](https://claude.ai/admin-settings/cowork)**. You can enable Claude in Chrome, the built-in browser, both, or neither. For Cowork admin settings, see **[Use Claude Cowork on Team and Enterprise plans](https://support.claude.com/en/articles/13455879-use-claude-cowork-on-team-and-enterprise-plans)**.

**Note:** When you enable the extension for an Enterprise organization, users are not automatically notified. You may want to communicate availability through your internal channels.

## Configure site access

Use allowlists and blocklists to control which websites Claude can access when it's working in Claude in Chrome or the Cowork built-in browser. The same list governs both, so there's no separate list to maintain for the built-in browser.

**Allowlist:** Specify which sites Claude is permitted to access by adding them to the allowlist.

**Blocklist:** Specify sites Claude should never access, regardless of other settings, by adding them to the blocklist. This adds an extra layer of protection beyond **[Claude's default blocked categories](https://support.claude.com/en/articles/12902428-using-claude-for-chrome-safely#h_34f8d5ca87)**.

**Recommendation:** Start with a more restrictive allowlist for the security of your organization's data, then expand access over time as you become comfortable with the extension's behavior.

**Note:** The allowlist and blocklist govern what URLs Claude can navigate to and act on. If a user manually types a blocked URL into the built-in browser, the page still loads with a "This site is blocked by your organization's policy" banner and Claude's tools are disabled on that page.

## Enable the Cowork side panel

On Enterprise plans, the Claude in Chrome side panel can run as a Claude Cowork session once it’s enabled for your organization. Side panel sessions are saved to users' history, move with them across surfaces, and support their skills, plugins, and connectors. Until you enable it, users see the classic side panel.

With the HIPAA configuration applied to Claude Code (local mode) and Cowork (local mode), Cowork in the cloud isn't available, so users keep the classic side panel.

**To turn on the Cowork side panel for your organization:**

1.  Enable Cowork in the cloud for your organization in **[Organization settings \> Cowork](https://claude.ai/admin-settings/cowork)**. See **[Use Claude Cowork on Team and Enterprise plans](https://support.claude.com/en/articles/13455879)**.

2.  Navigate to **[Organization settings \> Claude in Chrome](https://claude.ai/admin-settings/browser-extension)** and toggle **Enable for your team** on.

3.  Deploy the extension through your Chrome management tools, or have users install it from the **Chrome Web Store**.

If your organization already uses Claude in Chrome, users will see the Cowork side panel automatically once you enable Cowork in the cloud. Users don’t need to reinstall anything.

## Control password manager access

1Password for Claude lets macOS users complete tasks that require signing in, with 1Password filling the credential directly on the page so Claude never sees the password or one-time code. The integration is off by default for your organization.

Once enabled, eligible users will see the integration surfaced in Claude Desktop. Users also need the 1Password desktop app, the 1Password browser extension, Claude Desktop, and Claude in Chrome installed on a Mac. For setup details and requirements, see **[Get started with 1Password for Claude](https://support.claude.com/en/articles/15936181)**.

## Manage user access on Claude Desktop

Users with both Claude in Chrome and Claude Desktop installed will now have the option to start a task on the desktop app and let it handle work in the browser without switching windows.

If you want to disable this for members of your organization, you can toggle the extension off entirely, or edit your Enterprise configuration.

**Disable the Chrome extension in organization settings:**

1.  Sign in to Claude with your Owner account.

2.  Navigate to **[Organization settings \> Claude in Chrome](https://claude.ai/admin-settings/browser-extension)**.

3.  Toggle the extension off.

Alternatively, disable `isLocalDevMcpEnabled` in **[your Enterprise configuration](https://support.claude.com/en/articles/12622667-enterprise-configuration)**.

## Deployment options

Once enabled, users can access Claude in Chrome in two ways:

- **Self-service:** Users install the extension themselves from the **[Chrome Web Store](https://chromewebstore.google.com/detail/claude/fcoeoabgfenejglbffodgkkbkcdhcgfn)**.

- **Managed deployment:** Use your existing Chrome management tools (Google Workspace admin console or MDM) to deploy the extension to specific users or groups.

Most Enterprise organizations already have Chrome extension management in place. You can use these existing controls to limit which employees can install the extension during a pilot phase.

## Run a pilot

To test Claude in Chrome with a subset of users before broader rollout:

1.  Enable the extension at the organization level.

2.  Configure a restrictive allowlist limiting Claude to specific, trusted sites.

3.  Use your IT controls to limit which employees can install the extension.

4.  Share **[Use Claude in Chrome safely](https://support.claude.com/en/articles/12902428)** with pilot users.

5.  Gather feedback and expand access over time.


## How Claude in Chrome fits your existing controls

- **Role-based permissions:** Claude in Chrome has its own permission, separate from Claude Cowork. Two settings control Claude in Chrome: an organization-level toggle, and a per-role capability that admins can grant or withhold. That per-role capability applies to Enterprise organizations using custom roles. Claude in Chrome doesn't inherit a user's Cowork access.

- **Network controls:** Claude in Chrome sends its chat traffic through your existing Claude endpoints (`claude.ai`, `api.anthropic.com`, `platform.claude.com`), so any controls you've set on those apply here too. It also connects to the same bridge endpoint Claude Desktop uses (`wss://bridge.claudeusercontent.com`) and to standard telemetry services. In restrictive network environments, allow these connections. To limit which organization the extension can be used with, deploy the `forceLoginOrgUUID` Chrome enterprise policy.

- **Zero data retention (ZDR):** Not supported for Claude in Chrome, the same as Cowork.

## Educate your users

We recommend sharing these resources with users before they start using Claude in Chrome:

- **[Get started with Claude in Chrome](https://support.claude.com/en/articles/12012173-getting-started-with-claude-for-chrome)**: Installation and core capabilities

- **[Use Claude in Chrome safely](https://support.claude.com/en/articles/12902428)**: Risks and best practices

- **[Claude in Chrome permissions guide](https://support.claude.com/en/articles/12902446-claude-for-chrome-permissions-guide)**: How users control what Claude can access

---

## Claude for nonprofits partnership success guide for admins · Claude Academy

Source: https://support.claude.com/en/articles/12923668-claude-for-nonprofits-partnership-success-guide-for-admins
Path: 13-Enterprise-Admin/claude-for-nonprofits-partnership-success-guide-for-admins.md

# Claude for nonprofits partnership success guide for admins

A phased implementation framework to help nonprofit admins launch, scale, and sustain Claude adoption across their organization.

4 minClaude

[Open Claude](https://claude.ai/new)

Welcome to the Claude for nonprofits community. This guide provides everything you need to successfully launch and scale Claude at your organization.

The framework below is flexible and self-paced. Move through the phases at whatever speed works for your team.

## Phase 1: Foundation (technical setup and access)[](#phase-1-foundation-technical-setup-and-access)

#### Key resources

- [Getting started guides(opens in new tab)](getting-started-with-claude-for-enterprise.md) covering SSO, user management, and technical configuration
- [Driving adoption at your organization(opens in new tab)](https://anthropic.skilljar.com/driving-enterprise-adoption-of-claude) course with customizable templates for change management

#### Recommended actions

1.  Complete user management and technical configuration (SSO, SCIM)
2.  Set data retention and sharing policies
3.  Define success metrics using audit log data and surveys
4.  Plan your change management strategy with success criteria

#### Implementation tips

- Identify an implementation leader to complete adoption training
- Acknowledge challenges early and maintain open communication
- Establish Slack or Teams channels and office hours with designated ownership
- Bookmark the [Claude help center(opens in new tab)](https://support.claude.com/en/) for technical support

## Phase 2: Activation (first users and early wins)[](#phase-2-activation-first-users-and-early-wins)

#### Key resources

- [On-demand AI fluency courses(opens in new tab)](https://support.claude.com/)
- [Use case and workflow planning(opens in new tab)](https://support.claude.com/use-cases/workflow-improvement-planner)

#### Recommended actions

1.  Create shared projects and upload Claude documentation
2.  Recruit 3–5 champion team members for pilot feedback
3.  Select use cases strategically: start with a horizontal productivity win, then add department-specific cases

#### Implementation tips

- Launch Slack or Teams channels for ongoing discussion

- Host 30-day retrospectives to capture lessons learned

- Use Claude itself to facilitate planning. Try a prompt like:

  Help me design a 30-day pilot plan for introducing Claude to our fundraising team. Include milestones, success metrics, and potential challenges.

  

  Open in Claude

- Send baseline surveys to track progress and use cases

## Phase 3: Acceleration (scaling and integration)[](#phase-3-acceleration-scaling-and-integration)

#### Key resources

- [Use case library(opens in new tab)](https://support.claude.com/all?kind=use-case)
- [How to access audit logs(opens in new tab)](how-to-access-audit-logs.md)
- [MCP connector directory(opens in new tab)](../04-API-Reference/Other/partners-mcp.md)

#### Recommended actions

1.  Build a weekly use case spotlight schedule
2.  Track activity metrics via audit logs and surveys
3.  Enable appropriate connectors and capabilities in admin settings
4.  Participate in monthly webinars (third week of each month)

#### Implementation tips

- Monitor leading indicators: weekly active users %, users with 10+ chats %, project adoption %
- Share success stories internally on a regular basis

## Phase 4: Expansion (advanced use cases and impact)[](#phase-4-expansion-advanced-use-cases-and-impact)

#### Key resources

- [Claude train the trainer program(opens in new tab)](https://anthropic.skilljar.com/enterprise-train-the-trainer) (self-paced course)
- [Case studies hub(opens in new tab)](../18-Industry-UseCases/customers.md)

#### Recommended actions

1.  Run internal train the trainer programs to build expertise
2.  Identify new organizational functions interested in Claude adoption

#### Implementation tips

Sustainable adoption means building internal expertise. As AI becomes embedded in role expectations rather than remaining optional, your trained champions become the peer support network that scales organizational impact.

*Note: There's no "right" timeline for implementation. Whether you're moving quickly toward organization-wide adoption or taking a measured, department-specific approach, we're here to support your journey.*

- [Phase 1: Foundation (technical setup and access)](#phase-1-foundation-technical-setup-and-access)
- [Phase 2: Activation (first users and early wins)](#phase-2-activation-first-users-and-early-wins)
- [Phase 3: Acceleration (scaling and integration)](#phase-3-acceleration-scaling-and-integration)
- [Phase 4: Expansion (advanced use cases and impact)](#phase-4-expansion-advanced-use-cases-and-impact)

---

## Claude Tag launch promo for Claude Team and Enterprise | Claude Help Center

Source: https://support.claude.com/en/articles/15575654
Path: 13-Enterprise-Admin/claude-tag-launch-promo-for-claude-team-and-enterprise.md

# Claude Tag launch promo for Claude Team and Enterprise

September 2, 2026


With the launch of Claude Tag, we're issuing a one-time Claude Tag usage credit to eligible organizations—\$25000 per Claude Enterprise organization and \$2500 per Claude Team organization. Credits are issued automatically and are scoped to your organization's Claude Tag usage. The credit applies to Claude Tag usage in Slack channels, which is billed to your organization. It doesn't cover direct messages with Claude, which are billed to individual seats.

**Update: this promo has been extended.** All promotional credits now expire at 11:59 PM Pacific Time on October 1, 2026. Starting September 2, 2026, organizations must connect Claude Tag to GitHub to receive the credit.

This article explains who's eligible, how the credit works, and when it expires. Additional offer terms apply and are available here: **[Claude Promotional Credit Offer Terms](https://www.anthropic.com/legal/promotion-credit-terms)**.

## Eligibility

The promo is available to:

- Claude Enterprise organizations, both sales-assisted and self-serve, on any billing type, including legacy contracts and AWS Marketplace / partner billing. Early access program customers are included.

- Claude Team organizations

The promo isn't available to Claude Enterprise organizations on a free trial.

From September 2, 2026, the credit is issued once an admin connects Claude Tag to GitHub. Organizations that received the credit before September 2 keep it with no additional steps.

All credits expire at 11:59 PM Pacific Time on October 1, 2026. Your organization's subscription must be active and in good standing when the credit is issued and when it's used.

## How the credit works

Eligible organizations receive the credit automatically. From September 2, 2026, the credit is issued when your organization connects Claude Tag to GitHub. There's nothing else for admins or users to claim.

- The credit is one shared amount per organization, not per seat.

- What's covered: Claude Tag usage in public and private Slack channels (billed to your organization).

- What's not covered: Direct messages with Claude in Slack (billed to the individual seat), and any non-Claude Tag usage such as Claude Code, Claude Cowork, Claude chat, or the API. For more details about how Claude Tag usage is billed and the difference between channel and DM usage, see **[What is Claude Tag?](https://support.claude.com/en/articles/15594475)**

- Claude Tag usage draws down the credit first. Once the credit is used up, usage is billed to your organization as normal.

- The credit doesn't appear as a line item on your invoice.

## If your organization changes plans

The credit is a single balance whose cap follows your current plan. If a Team organization upgrades to Enterprise, the cap on the same balance increases to \$25000 in total, counting what was already used. If an Enterprise organization moves to Team, the cap drops to \$2500, and any further usage is billed normally.

## Currency

For Team organizations billed in a currency other than US dollars, the credit may be shown in your local currency.

## Track the credit in your organization

Admins can track usage of the promotional credit in Settings.

## When the credit expires

All credits expire at 11:59 PM Pacific Time on October 1, 2026, regardless of when they were issued. Any unused portion is forfeited at expiration.

## Temporary holds on eligibility

If we detect abuse, we may pause an organization's promotional credit while we review the activity.

## Frequently asked questions

### Is the credit per seat?

No. It's one shared credit per organization.

### Do we need to do anything to claim it?

If your organization received the credit before September 2, 2026, no. If your organization qualifies on or after September 2, an admin needs to connect Claude Tag to GitHub, and the credit is issued once the connection is complete.

### What can the credit be spent on?

Claude Tag usage only. It doesn't apply to Claude Code, Claude Cowork, Claude chat, or the API.

### Does the credit cover all of our Claude Tag usage?

It covers Claude Tag usage billed to your organization, such as usage in shared channels. Usage billed to an individual seat, such as direct messages with Claude, isn't covered.

### We're billed through AWS Marketplace or a reseller. Are we eligible?

Yes. All billing types are in scope, including legacy contracts, AWS Marketplace, and partner/reseller billing.

### Where can I get help?

Contact your account team, or **[reach out to Support](https://support.claude.com/en/articles/9015913)**.

---

## Communications kit - Claude Code Docs

Source: https://code.claude.com/docs/en/communications-kit
Path: 13-Enterprise-Admin/communications-kit.md

## On this page

- [Launch communications](#launch-communications)
  - [Before you send](#before-you-send)
  - [The announcement](#the-announcement)
  - [Executive sponsor variant](#executive-sponsor-variant)
  - [Pilot group variant](#pilot-group-variant)
  - [Champion recruitment DM](#champion-recruitment-dm)
- [Tips and tricks campaign](#tips-and-tricks-campaign)
  - [Get started](#get-started)
  - [Project memory](#project-memory)
  - [Control and safety](#control-and-safety)
  - [Connect your tools](#connect-your-tools)
  - [Automate your workflows](#automate-your-workflows)
  - [Day-to-day development](#day-to-day-development)
  - [Share and scale](#share-and-scale)
  - [Security and admin](#security-and-admin)
- [Quick reference](#quick-reference)
  - [FAQ responses](#faq-responses)
  - [Prompt templates](#prompt-templates)

Adoption

# Communications kit

Copy pageCopy page

Launch announcements, drip-campaign messages, and FAQ responses for rolling Claude Code out to your engineering organization.

Copy pageCopy page

This page is for administrators and engineering leads rolling Claude Code out to a team. It provides copy-ready launch announcements, a tips-and-tricks drip campaign, and one-line FAQ responses for the questions you will be asked most.

Treat everything here as draft copy, not finished copy. Rewrite each message in your organization’s voice, swap the example tasks for real bugs and modules from your own codebase, and replace the `[bracketed placeholders]` before sending. The announcements that drive adoption are the ones that read like someone at your company wrote them.


[​](#launch-communications)

Launch communications

One announcement in two formats, plus two optional variants. Pick whichever fits your rollout and rewrite it from there.


[​](#before-you-send)

Before you send

Work through this checklist before the announcement goes out. Each item closes a gap that otherwise turns into a launch-day support thread.

| Item                                                                                                  | Why it matters                                                                      |
|-------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------|
| `#claude-code` channel created and linked in the message                                              | Gives questions one place to land                                                   |
| Install command tested on at least one machine in your environment                                    | Catches proxy or firewall issues before everyone hits them at once                  |
| Security and data-handling link ready ([Data usage](data-usage.md) or your internal equivalent) | “Where does my code go?” will be the first reply                                    |
| One concrete first task chosen, a real bug or file in your codebase                                   | Generic examples don’t convert; “fix the flaky test in `auth_test.go`” does         |
| A named owner for the channel for the first 48 hours                                                  | Unanswered launch-day questions kill momentum                                       |
| A C-suite sponsor lined up to send or co-sign the announcement                                        | Exec-sent launches consistently see higher first-week adoption than admin-sent ones |


[​](#the-announcement)

The announcement

Use this as your standard org-wide rollout message. It covers what Claude Code is, gives a two-minute install path, hands readers one concrete task to try, and answers “where does my code go?” before anyone has to ask.

- Email

- Slack or Teams

```python
Subject: Claude Code is live for [Engineering / your team]

Team,

As of today you have access to Claude Code, an AI coding agent that runs in
your terminal, reads your actual codebase, and works through real tasks end
to end: debugging, refactors, tests, PRs. It is not autocomplete and it is
not a chat window. It edits files and runs your commands.

Get running in two minutes:

    curl -fsSL https://claude.ai/install.sh | bash
    cd <your-repo>
    claude

Then run /init once. Claude reads your project and writes a CLAUDE.md with
your build commands and conventions, so you stop re-explaining the basics.

Then try one of these on the repo you are already in:

  - "The test in [file] is flaky. Figure out why and fix it"
  - "Walk me through how [module] handles [X]"
  - "Look at my working diff and tell me what's risky before I push"

Where your code goes: Claude Code runs in your terminal and talks directly
to Anthropic's API. On our Team or Enterprise plan, Anthropic does not use
your code or prompts to train its models.
Details: https://code.claude.com/docs/en/data-usage
         https://code.claude.com/docs/en/security

Where to go with questions: #claude-code. [Owner name] is watching it
this week.

- [Name]

P.S. Prefer your editor? There is a VS Code extension and a JetBrains
plugin. Same agent, no terminal required.
```

```python
🚀 *Claude Code is live for [team]*

AI coding agent, runs in your terminal, reads your repo, does real work:
bugs, refactors, tests, PRs.

`curl -fsSL https://claude.ai/install.sh | bash` → `cd your-repo` → `claude`

*First thing to try* → run `/init`, then: "the test in [file] is flaky,
figure out why and fix it."

🔒 Runs in your terminal, talks directly to Anthropic's API. On our Team or
Enterprise plan, your code and prompts are not used to train models.
Data usage → https://code.claude.com/docs/en/data-usage

📚 Quickstart · VS Code · Free 1-hr course
   https://code.claude.com/docs/en/quickstart
   https://code.claude.com/docs/en/vs-code
   https://academy.claude.com/courses/claude-code-in-action

Questions → this thread. [Owner] is on point.
```


[​](#executive-sponsor-variant)

Executive sponsor variant

Send this from your sponsoring executive, such as the CTO, CIO, or SVP Engineering, under their name and from their account. Launches that go out under an exec’s name consistently see higher open rates and faster first-week activation than the same message from an admin or tooling team. It signals a company priority rather than an optional experiment. This version is deliberately stripped to one ask: install it and run it on one real task. The exec’s job is to make the ask land; the standard announcement and `#claude-code` handle the how.

- Email

- Slack or Teams

```python
Subject: One thing I'd like every engineer to try this week

Team,

We have turned on Claude Code for all of engineering. It is an AI agent
that works directly in your terminal, on your actual codebase, and the
early results from teams already using it are strong enough that I want
everyone on it this week.

I am asking for ten minutes:

    curl -fsSL https://claude.ai/install.sh | bash
    cd <your-repo>
    claude

Then hand it one real task: the bug you have been putting off, or "walk me
through how [module] works."

That is the whole ask. [Owner name] and team are in #claude-code for
anything you hit along the way.

- [Exec Name]
  [Title]
```

```python
📣 *From [Exec Name]: one thing to try this week*

We have turned on *Claude Code* for all of engineering. Early results are
strong enough that I am asking everyone to give it ten minutes on real
work this week.

`curl -fsSL https://claude.ai/install.sh | bash` → `cd your-repo` →
`claude` → hand it one real task.

That's it. Questions → #claude-code.
```


[​](#pilot-group-variant)

Pilot group variant

Use for a phased rollout. Send to the pilot cohort only.

```python
Subject: You're in the Claude Code pilot

[Name / team],

You are in the first wave of Claude Code at [company]. We picked this group
because you will put it on real problems and tell us the truth about it.

The ask: use it on at least one real task this week, then drop a note in
#claude-code-pilot covering what worked, what was annoying, and what
surprised you. That feedback decides how we roll it out to everyone else.

[Continue with "Get running in two minutes" from the standard announcement]

One extra thing for pilots: on your first multi-file change, press Shift+Tab
until you see "plan". Claude will lay out what it intends to do
without editing your source. It is the fastest way to calibrate how much to
trust it.
```


[​](#champion-recruitment-dm)

Champion recruitment DM

After launch, DM the two or three people who are most active in `#claude-code`.

```python
Hey [name], your #claude-code posts are doing more for adoption than my
announcement did. A couple of people told me your [thread / screenshot]
was why they actually tried it.

Want to make that semi-official? Low lift: mostly keep posting what you
are posting, plus first crack at new features and a direct line to the
Anthropic team. I can share a short playbook if you're in.
```


[​](#tips-and-tricks-campaign)

Tips and tricks campaign

Ready-to-paste Slack or Teams messages designed to drive feature activation after launch. Each follows the same pattern: a hook, the payoff, a “try it now” prompt, and a docs link. Drip them one or two a week in `#claude-code`, or pick the handful that match your team’s gaps. They stand alone with no required order.


[​](#get-started)

Get started

**Choosing the right model**

```python
🎯 *Tip: Match the model to the moment*

Using Opus to fix a typo burns compute. Using Haiku for a 12-file refactor
is asking for a re-do.

Claude Code runs on the same models as the Claude app, and you can switch
mid-session. *Sonnet* is the workhorse default for everyday feature work,
bugs, tests, and reviews. Reach for *Opus* on large refactors, gnarly
debugging, or anything high-stakes. Drop to *Haiku* for quick questions,
formatting, and mechanical edits where speed wins.

*Fable* is the most
capable model for your hardest, longest-running tasks; it is not the
default, so select it with `/model fable`, and note that cybersecurity and
biology content falls back to Opus automatically. Opus 5.5, Sonnet 5.5, and
Opus 5 run their own checks too: flagged content switches to an earlier model
in the same family, except that flagged biology content on Opus 5 or Sonnet
5.5 is refused.

*Try it now:* type `/model` and pick Sonnet if you haven't already. It is
the right default for most tasks.

📖 Model configuration → https://code.claude.com/docs/en/model-config
```

| Model  | Best for                                                                                                                                                                                                                                         |
|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Fable  | The hardest, longest-running tasks. Opt-in only: select it with `/model fable`. Cybersecurity or biology content triggers [automatic model fallback to Opus](../02-Claude-Code-CLI/model-config.md#automatic-model-fallback)                                     |
| Opus   | Large-scale refactors, complex debugging, architecture decisions, high-stakes changes. On Opus 5.5 and Opus 5, cybersecurity or biology content triggers [automatic model fallback or a refusal](../02-Claude-Code-CLI/model-config.md#automatic-model-fallback) |
| Sonnet | Everyday feature work, bug fixes, tests, documentation, code review. Recommended default. On Sonnet 5.5, cybersecurity or biology content triggers [automatic model fallback or a refusal](../02-Claude-Code-CLI/model-config.md#automatic-model-fallback)       |
| Haiku  | Quick questions, formatting, mechanical edits, rapid iteration                                                                                                                                                                                   |

**Quick wins to try first**

```python
🚀 *Tip: Three things to try in your first 10 minutes*

Installed Claude Code but not sure what to actually ask it? Start with the
stuff that has been bugging you all week.

  - Fix something annoying: "the test in [file] is flaky, figure out why"
  - Get oriented in code you didn't write: "walk me through how [module] works"
  - Sanity-check before you push: "look at my working diff and tell me what
    looks risky"

None of these need setup. Just `cd` into your repo and run `claude`.

*Try it now:* pick the bug you have been avoiding and paste the error
message in.

📖 Quickstart → https://code.claude.com/docs/en/quickstart
```


[​](#project-memory)

Project memory

**`/init` and CLAUDE.md**

```python
📁 *Tip: Stop re-explaining your repo every session*

Telling Claude "we use pnpm, not npm" for the fifth time? There is a
one-time fix.

Run `/init` once per repo. Claude reads your project structure and writes a
CLAUDE.md file with your build commands, architecture, and conventions.
Every future session in that repo starts from this file automatically. Keep
it under two screens. It is a cheat sheet, not documentation.

*Try it now:* open your main repo, run `claude`, type `/init`. Thirty
seconds, pays off every session after.

📖 CLAUDE.md and project memory → https://code.claude.com/docs/en/memory
```

**@-references**

```python
📎 *Tip: Stop pasting file contents into the chat*

Copying 200 lines of a component into your prompt so Claude can "see" it?
You don't have to.

Type `@` then a file path. Claude pulls the file directly into context.
Works for whole directories too.

> the styles in @src/components/Button.tsx look off, check against
> @docs/design-system.md

*Try it now:* type `@` then Tab. Autocomplete shows you every file in reach.

📖 Referencing files → https://code.claude.com/docs/en/common-workflows
```


[​](#control-and-safety)

Control and safety

**Permission modes**

```python
🛡️ *Tip: One keystroke between "look but don't touch" and "just do it"*

Sometimes you want Claude to ask before every edit. Sometimes you just want
it to ship. You shouldn't have to pick one forever.

*Shift+Tab* cycles through how much Claude can do without asking: *Manual* (the
`default` setting value) asks before file edits and most shell commands, *acceptEdits* lets file
edits and common filesystem commands
flow through while still checking before other shell commands, and *plan*
researches and proposes changes without editing your source. Plan mode is
the trust-builder, so start there for anything touching multiple files.

*Try it now:* on your next refactor, hit Shift+Tab until you see "plan",
then describe the change. You'll get a full proposal to review.

📖 Permission modes → https://code.claude.com/docs/en/permissions
```

**Checkpointing and `/rewind`**

```python
⏪ *Tip: There is an undo button for the whole conversation*

Claude went down the wrong path three turns ago and now you're untangling
it? You don't have to fix forward.

`/rewind` rolls back to an earlier point in the conversation, including the
file changes Claude made along the way. Checkpointing is automatic; you
don't set anything up.

*Try it now:* press *Esc* twice to open the rewind menu, or type `/rewind`.
Pick the point before things went sideways.

📖 Checkpointing → https://code.claude.com/docs/en/checkpointing
```


[​](#connect-your-tools)

Connect your tools

**MCP connectors**

```python
🔌 *Tip: Let Claude read your issue tracker so you don't have to paste tickets*

Copy-pasting Jira tickets into the terminal feels like a step backward.
It is.

One config file (`.mcp.json` at your project root) wires Claude into GitHub,
Jira, Linear, or whatever tracker you use. Then "what's the top-priority
issue assigned to me?" and "go ahead and fix it" happen in the same
conversation.

*Try it now:* ask Claude "set up an MCP connector for [GitHub/Jira/Linear]
in this repo". It will write the config for you.

📖 MCP connectors → https://code.claude.com/docs/en/mcp
```


[​](#automate-your-workflows)

Automate your workflows

**Skills**

```python
⚡ *Tip: Turn that prompt you keep retyping into a command*

Typed "summarize what I worked on today from git log, format it for standup"
three times this week? That's a slash command waiting to happen.

A SKILL.md file in `.claude/skills/<name>/` becomes a reusable prompt; type
`/name` to run it. Make one the second time you type a multi-step prompt
you've typed before. Easiest path: ask Claude to make it for you.

*Try it now:* type "make me a /standup skill that summarizes what I worked
on today from git log", then run `/standup` tomorrow morning.

📖 Skills → https://code.claude.com/docs/en/skills
```

**Hooks**

```python
🔔 *Tip: Get pinged when your refactor finishes*

Sitting at your desk watching Claude work through a long task? You've got
better things to do for those eight minutes.

Hooks are shell commands that fire on Claude Code events. A Stop hook that
sends a desktop notification means you can kick off a long refactor, walk
away, and get pinged the moment it's done.

*Try it now:* ask Claude "add a Stop hook that sends a desktop notification
when you finish". It will write the script and wire it up.

📖 Hooks guide → https://code.claude.com/docs/en/hooks-guide
```


[​](#day-to-day-development)

Day-to-day development

**Screenshots and images**

```python
📸 *Tip: Stop describing the error dialog. Just show it.*

Typing out "there's a red box that says something about a null reference
and it's pointing at line 47-ish"? Screenshot it.

Drag a screenshot straight into the terminal and Claude sees it: error
dialogs, UI mockups, whiteboard photos, Figma exports. *Ctrl+V* pastes from
the clipboard, or *Alt+V* on Windows and WSL.

*Try it now:* next time something visual breaks, screenshot it and paste it
right into the prompt. Then just type "what's wrong here?"

📖 Working with images → https://code.claude.com/docs/en/common-workflows
```

**Git workflows**

```python
🌿 *Tip: Hand off the whole git ceremony*

The fix took 5 minutes. The commit message, branch, and PR description
took 15. That ratio is wrong.

Claude handles the full git flow: commits with conventional messages,
branches, PRs with proper summaries. One ask: "fix the off-by-one, commit
with a conventional commit message, and open a PR." Reviewing someone
else's work? Paste the PR URL and ask Claude to walk you through the diff.

*Try it now:* after your next fix, instead of switching to your git client,
just type "commit this with a good message and open a PR".

📖 Creating pull requests → https://code.claude.com/docs/en/common-workflows
```


[​](#share-and-scale)

Share and scale

**Plugins**

```python
📦 *Tip: Someone probably already built that skill*

About to spend an hour building a `/deploy` command? Check if it
already exists.

Skills get bundled and shared as plugins. `/plugin` browses what's
available and installs in one step. Five minutes of browsing can save an
hour of building.

*Try it now:* type `/plugin` and scroll through. You'll find at least one
thing you didn't know you wanted.

📖 Plugins → https://code.claude.com/docs/en/plugins
```


[​](#security-and-admin)

Security and admin

**Security architecture**

```python
🔐 *Tip: The answer to "is this safe?" for the next time you're asked*

Someone on your team is going to ask "wait, where does my code go?"
Here's the short version you can paste.

A permission mode sets which actions Claude can take without asking you
first. The CLI runs in your terminal, talks directly to Anthropic's API,
and supports optional OS-level sandboxing for shell commands. On a Team or Enterprise plan,
Anthropic does not use your code or prompts to train its models.

*Try it now:* save these two links for the next time the question comes up.
They answer most security-review questions.

📖 https://code.claude.com/docs/en/security
📖 https://code.claude.com/docs/en/data-usage
```

**Best practices**

```python
✅ *Tip: The 4 habits that separate "tried it once" from "use it daily"*

Most people who bounce off Claude Code skipped one of these. Most people
who stick did all four in week one.

  - Start in plan mode for anything touching multiple files
  - Run /init early; context compounds
  - Review diffs before committing; Claude can be confidently wrong
  - Verify changes that touch critical paths; treat it like a sharp
    junior, not an oracle

*Try it now:* if you've only done one or two of these, pick the one you're
missing and do it on your next task. Post what changed in #claude-code.

📖 Best practices → https://code.claude.com/docs/en/best-practices
```


[​](#quick-reference)

Quick reference


[​](#faq-responses)

FAQ responses

One-line replies for the questions you will be asked most.

| Question                                 | Response                                                                                                                                                                                                |
|------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| ”Does it work in VS Code?”               | Yes. There is a VS Code extension and a JetBrains plugin with the same features, embedded in your editor. [VS Code →](../03-IDE-Integrations/vs-code.md)                                                                 |
| “Do I have to configure anything first?” | No. Install, then run `claude` in any repo. Run `/init` once and you’re set. [Quickstart →](../01-Getting-Started/quickstart.md)                                                                                        |
| “Where does my code go?”                 | The CLI runs in your terminal and sends context to Anthropic’s API for inference. On a Team or Enterprise plan, your code and prompts are not used to train models. [Data usage →](data-usage.md) |
| “Can it see my whole repo?”              | It reads what you give it access to. File reads inside your working directory don’t prompt. [Permissions →](../02-Claude-Code-CLI/permissions.md)                                                                       |
| “How is this different from Copilot?”    | Copilot autocompletes lines. Claude Code is an agent that reads files, runs commands, and makes multi-file edits. [Overview →](../02-Claude-Code-CLI/code-home.md)                                                       |
| “What should I try first?”               | A bug you’ve been putting off because it’s tedious. “The test in \[file\] is flaky, figure out why.” [Quickstart →](../01-Getting-Started/quickstart.md)                                                                |


[​](#prompt-templates)

Prompt templates

Share these starter prompts with engineers who have installed but aren’t sure what to ask. Each one is phrased the way it would be typed into a real session; replace the bracketed pieces with files from your own repo.

| Task                 | Prompt                                                                        |
|----------------------|-------------------------------------------------------------------------------|
| Fix a bug            | ”the tests in \[file\] are failing, figure out why and fix it”                |
| Understand code      | ”walk me through how \[module\] works, then tell me where the entry point is” |
| Safe refactor        | ”refactor \[module\] to \[goal\], use plan mode so I can review first”        |
| Write tests          | ”write tests for \[file\] that cover the edge cases around \[scenario\]“      |
| Review before commit | ”look at my working diff and tell me what looks risky”                        |
| Open a PR            | ”fix \[issue\], write a conventional commit, and open a PR with a summary”    |
| Make a skill         | ”make me a /ship skill that runs tests and lint before commit”                |
| Debug a stack trace  | ”here’s the stack trace, find the root cause, don’t just paper over it”       |

Claude Code ships frequently. Verify version-specific details against the [documentation home page](../02-Claude-Code-CLI/code-home.md) before distributing internally.

---

## Run Claude Code behind a corporate launcher - Claude Code Docs

Source: https://code.claude.com/docs/en/corporate-launcher
Path: 13-Enterprise-Admin/corporate-launcher.md

## On this page

- [What the launcher covers](#what-the-launcher-covers)
  - [Processes that start outside the launcher](#processes-that-start-outside-the-launcher)
  - [Helper process names in process monitors](#helper-process-names-in-process-monitors)
- [Set up the launcher](#set-up-the-launcher)
- [The launcher contract](#the-launcher-contract)
  - [Format of the launcher value](#format-of-the-launcher-value)
  - [Pass arguments to the launcher](#pass-arguments-to-the-launcher)
- [Relationship to CLAUDE_CODE_SHELL_PREFIX](#relationship-to-claude_code_shell_prefix)
- [Related resources](#related-resources)

Deployment

# Run Claude Code behind a corporate launcher

Copy pageCopy page

Route the processes Claude Code starts from its own binary, including the background service and every agent view session, through a required launcher with CLAUDE_CODE_PROCESS_WRAPPER or the processWrapper setting.

Copy pageCopy page

Some organizations require every process on a workstation to start through a mandatory launcher. The launcher applies the sandbox, network controls, or credential injection that the company’s security posture depends on, and a binary that starts without it is a policy violation. `CLAUDE_CODE_PROCESS_WRAPPER` starts every process Claude Code launches from its own binary through your launcher: the [background service](../09-Agents-Patterns/agent-view.md#the-supervisor-process), every session it hosts in [agent view](../09-Agents-Patterns/agent-view.md), and Claude Code’s relaunches after an update. Set it to your launcher’s absolute path, and Claude Code runs the launcher with the Claude Code command as its arguments. A launcher that wraps the `claude` command on your `PATH` can’t reach the background service or the sessions it hosts, because they start from the binary’s direct path without looking up `claude`.

`CLAUDE_CODE_PROCESS_WRAPPER` requires Claude Code v2.1.208 or later. Earlier versions ignore the variable and start every process unwrapped. The equivalent [`processWrapper` setting](../02-Claude-Code-CLI/settings-reference.md#processwrapper) requires v2.1.210 or later. Earlier versions ignore it as an unknown key, apply no launcher, and report no error.After deploying either form, use the [Verify step](#set-up-the-launcher) to confirm the running version applies it.


[​](#what-the-launcher-covers)

What the launcher covers

With `CLAUDE_CODE_PROCESS_WRAPPER` set, Claude Code starts each of the following processes through your launcher:

- The background service that `claude agents` and background sessions start on demand.
- The terminal host and the Claude Code session inside every agent view row, including the warm standby sessions the service keeps ready.
- Sessions the service respawns after an update or a crash.
- The relaunch Claude Code performs of itself to finish installing an update, including agent view’s restart-for-update action.
- The session processes that [Remote Control](../02-Claude-Code-CLI/remote-control.md) starts. Requires Claude Code v2.1.210 or later.
- The split-pane teammate sessions that [agent teams](../09-Agents-Patterns/agent-teams.md) start in tmux or iTerm2. Teammate panes are interactive rather than background processes, but Claude Code starts them from its own binary, so the launcher covers them. Requires Claude Code v2.1.210 or later.

On Windows, the variable is ignored: the launcher contract depends on `exec`, which Windows doesn’t support. A Windows machine with the variable set runs every process unwrapped and keeps working, and the only signal is a warning in the [debug log](../02-Claude-Code-CLI/troubleshooting.md). If your launcher policy covers Windows, the variable doesn’t satisfy it there: count Windows machines as unwrapped when you plan the rollout.


[​](#processes-that-start-outside-the-launcher)

Processes that start outside the launcher

The following processes don’t start through the launcher:

- A session you start yourself in a terminal, which runs however you invoked it. To cover these sessions, put a script named `claude` in a directory earlier on `PATH` that runs your launcher with the real binary; don’t replace the managed symlink. The background service and its sessions start without a `PATH` lookup, so the two launchers don’t stack there.
- The first process of a `claude-cli://` deep link, which the operating system’s protocol handler starts directly. Everything that session starts in the background afterward runs through the launcher. To close this path entirely, [prevent handler registration](../02-Claude-Code-CLI/deep-links.md#registration-and-supported-platforms) with the `disableDeepLinkRegistration` setting.
- The relaunch that `--worktree` combined with `--tmux` performs: the terminal multiplexer starts that pane, not Claude Code’s binary.
- The native-messaging host that [Claude in Chrome](../03-IDE-Integrations/chrome.md) registers: the browser starts it, not Claude Code’s binary.


[​](#helper-process-names-in-process-monitors)

Helper process names in process monitors

With a launcher configured, `ps` and Activity Monitor no longer show Claude Code’s `claude bg-pty-host` and `claude bg-spare` labels for the background helper processes, because the launcher’s `exec` rebuilds the argument list. Losing the labels is a side effect, not concealment: the processes are otherwise unchanged, and Claude Code identifies its own processes by binary path, never by display name.


[​](#set-up-the-launcher)

Set up the launcher

1

Write the launcher script

Create an executable script at an absolute path, such as `/opt/corp/launcher`. Claude Code runs it with the full Claude Code command as its arguments, and the script must end by calling `exec "$@"` so it replaces itself with Claude Code:

```python
#!/bin/sh
# Your organization's setup: enter the sandbox, apply
# network controls, or inject credentials.
exec "$@"
```

Make it executable with `chmod +x`. The setup portion is whatever your launcher must do before Claude Code runs; [the launcher contract](#the-launcher-contract) below lists the rules the script has to follow.

If you previously replaced the `~/.local/bin/claude` symlink with your launcher, restore the original symlink in the same change. A replaced symlink makes the first wrapped session start the background service through both launchers at once, and it puts the installation into an externally managed state: `/doctor` reports it, auto-update leaves the file in place, and cleanup of old versions stays disabled until the installer manages that path again.

2

Set CLAUDE_CODE_PROCESS_WRAPPER in settings

Set the variable in the `env` block of a settings file so the detached background service inherits it. A shell `export` isn’t enough: the background service starts on demand, outlives your shell, and never re-reads shell profiles.For one machine, add it to `~/.claude/settings.json`. To deploy it to every machine in your organization, put the same block in [managed settings](managed-settings.md):

```python
{
  "env": {
    "CLAUDE_CODE_PROCESS_WRAPPER": "/opt/corp/launcher"
  }
}
```

When more than one source sets the variable, the managed settings value overrides both `~/.claude/settings.json` and a value exported in the shell, so users can’t point self-spawns at a different launcher.The [`processWrapper` setting](../02-Claude-Code-CLI/settings-reference.md#processwrapper) carries the same value as a named, top-level settings key. Set it when your organization pushes settings as individual keys rather than an `env` block. The `processWrapper` setting requires Claude Code v2.1.210 or later. The following settings file sets the same launcher through the key:

```python
{
  "processWrapper": "/opt/corp/launcher"
}
```

`CLAUDE_CODE_PROCESS_WRAPPER` takes precedence when both are set.Because `processWrapper` is a named setting, an organization that delivers it through [remote managed settings](managed-settings.md#delivery-mechanisms) sees it listed on the [security approval dialog](server-managed-settings.md#security-approval-dialogs) alongside the other settings that run administrator-supplied executables.Project and local settings can’t configure the launcher. A file committed to a repository must not be able to put a binary in front of every Claude Code process on the machine, so Claude Code ignores `CLAUDE_CODE_PROCESS_WRAPPER` in `.claude/settings.json` or `.claude/settings.local.json` with a warning in the [debug log](../02-Claude-Code-CLI/troubleshooting.md), and never reads the `processWrapper` key from those files.

3

Restart the background service and your sessions

A running background service and any open `claude` sessions read the variable once at startup, so they keep launching unwrapped processes until restarted. Run `claude daemon stop --any` to stop the on-demand service. The next command that needs it, such as `claude agents`, starts a wrapped one. Then restart your open `claude` sessions.On machines you can’t restart yourself, the first session started after the settings push retires a leftover unwrapped on-demand service automatically. A machine where no new session starts keeps its unwrapped background service until one does.

4

Verify

Run `/status` in a session: the Self-exec entry shows the resolved launch command and warns when the running background service doesn’t match it. `claude daemon status` prints the same information from the shell, including after you unset the variable, when `/status` no longer shows the entry.


[​](#the-launcher-contract)

The launcher contract

When the launcher can’t run, Claude Code refuses to start the process instead of starting it unwrapped. On Windows, [the variable is ignored](#what-the-launcher-covers) and processes start unwrapped. Claude Code holds the script to these rules:

- **End with `exec "$@"`.** A launcher that forks a child and exits leaves an orphaned Claude Code process the background service can’t track. Agent view marks such a session failed with a message naming the launcher, and the service reaps what the launcher left behind.
- **Don’t reorder, absorb, or add to Claude Code’s arguments.** Remove the launcher’s [own arguments](#pass-arguments-to-the-launcher), which come first, and pass the rest to `exec` as it arrived.
- **Pass every inherited environment variable through to `exec`.** Adding variables, such as injected credentials, is fine; dropping inherited ones is not.
  - The per-session authentication tokens, the model and provider selection, and `CLAUDE_CODE_PROCESS_WRAPPER` itself all travel on the inherited environment, so a launcher that rebuilds it from an allow list breaks the sessions it starts, and `/status` reports a launcher mismatch.
  - If the launcher must enter a namespace or sandbox that resets the environment, re-export the inherited environment inside it verbatim.
- **Reach `exec` within about three seconds each time the launcher runs.** A background session can run the launcher three times in series before its first output. Do slow work such as a single sign-on exchange lazily or from a cache, and give any step that waits on the network its own time limit.
- **Tolerate being invoked from inside itself.** Claude Code applies the launcher to every nested self-spawn, so a launcher that acquires an exclusive resource must detect that it already holds it.
- **Don’t write to the terminal or standard output before Claude Code starts.** Anything printed before the `exec` is reported as the crash cause if the session dies before initializing. Text on standard output also reaches a program that reads Claude Code’s output, such as a script that parses `claude -p` results.
- **Don’t depend on how arguments are spelled.** A flag’s value can arrive as its own argument, `--flag value`, or joined to the flag, `--flag=value`. Which form a flag uses can change between versions.
- **Run `exec` from the directory the launcher started in.** Claude Code works in the directory it starts in, so a launcher that changes directory can start a session in the wrong project. If the launcher must enter a namespace or sandbox that resets the working directory, change back to the starting directory inside it, before the `exec`.
- **Don’t read standard input.** A command that reads it before the `exec`, such as one that asks for a password, can wait for input that doesn’t come and leave the session stuck. It can also take input meant for Claude Code, and the session can then run without that input and report no error. Redirect such a command’s input from `/dev/null`.
- **Call other programs by absolute path.** The launcher can start with a `PATH` that lacks the directories your shell profile adds.


[​](#format-of-the-launcher-value)

Format of the launcher value

`CLAUDE_CODE_PROCESS_WRAPPER` and the `processWrapper` setting take the same format. For most launchers, the value is the script’s absolute path, like `/opt/corp/launcher`. To pass your launcher arguments of its own, write them after the path. Claude Code parses the value as an argument list, not a shell command:

- Whitespace separates tokens, and double quotes group a token that contains spaces. A single quote is an ordinary character, so `'a b'` is two tokens.
- A value that starts with `[` is read as a JSON string array, such as `["/opt/corp/launcher", "--profile", "cc"]`.
- Shell syntax doesn’t work: there is no variable expansion or globbing, and an unquoted operator such as `;`, `|`, `&`, or `$(` is rejected as a configuration error rather than reinterpreted.

When the value can’t be used, Claude Code refuses to start the affected process and [reports the reason](../02-Claude-Code-CLI/errors.md#claude_code_process_wrapper-launcher-errors). In a settings file, write the array form inside a JSON string and escape its quotes: `"[\"/opt/corp/launcher\", \"--profile\", \"cc\"]"`.


[​](#pass-arguments-to-the-launcher)

Pass arguments to the launcher

Write the launcher’s own arguments after its path in [the value](#format-of-the-launcher-value), and remove them in the script before the `exec` call. They reach the script first, ahead of the Claude Code command. The following script, written for the value `/opt/corp/launcher --profile cc`, saves the profile name and then removes both arguments with `shift`:

```python
#!/bin/sh
if [ "$1" = "--profile" ]; then
  profile="$2"
  shift 2
fi
# Your organization's setup for "$profile".
exec "$@"
```

Use the [Verify step](#set-up-the-launcher) to see the launch command Claude Code resolved from the value.


[​](#relationship-to-claude_code_shell_prefix)

Relationship to `CLAUDE_CODE_SHELL_PREFIX`

`CLAUDE_CODE_PROCESS_WRAPPER` wraps Claude Code’s own processes and passes the command through as separate argv tokens for the launcher to `exec`. [`CLAUDE_CODE_SHELL_PREFIX`](../02-Claude-Code-CLI/env-vars.md) wraps the shell commands Claude runs on your behalf, such as Bash tool calls, hooks, and the commands that start stdio MCP servers, and passes each one as a single shell-quoted string in `$1` for the wrapper to re-evaluate. A launcher written for one doesn’t work as the other.


[​](#related-resources)

Related resources

- [Agent view](../09-Agents-Patterns/agent-view.md): the background sessions and supervisor process the launcher covers
- [Environment variables](../02-Claude-Code-CLI/env-vars.md): the `CLAUDE_CODE_PROCESS_WRAPPER` reference entry
- [Managed settings](../02-Claude-Code-CLI/permissions.md#managed-settings): deliver the `env` block across a fleet
- [Launcher error reference](../02-Claude-Code-CLI/errors.md#claude_code_process_wrapper-launcher-errors): the refusal messages and how to recover

---

## Create surveys for your organization | Claude Help Center

Source: https://support.claude.com/en/articles/16764057-create-surveys-for-your-organization
Path: 13-Enterprise-Admin/create-surveys-for-your-organization.md

# Create surveys for your organization

September 2, 2026


Surveys let admins ask users short, in-product questions about how they're using Claude and review the aggregated answers in the Admin console. This article explains how to create a survey, choose who sees it, and review the results.

Surveys are available in beta for Enterprise plans. Primary Owners, Owners, and Admins can create and view surveys, along with users on **[custom roles that grant Analytics view access](../17-Billing-Plans/manage-custom-roles-on-enterprise-plans.md#h_536123d968)**. While the feature is in beta, organizations using **[customer-managed encryption keys (CMEK)](https://support.claude.com/en/articles/15505325)** can't use surveys.

## How surveys work

Surveys appear in the **Analytics** section. Usage analytics show how much your team uses Claude; surveys tell you what people are doing with it and what's getting in their way.

Users see a survey as a small card in Cowork or chat at a natural break in their work. The card shows your organization's name, and each user sees a given survey once. A survey takes about 90 seconds to complete.

## Create a survey

To create and schedule a survey:

1.  Navigate to **[Analytics \> Surveys](https://claude.ai/analytics/surveys)**.

2.  Click the “New survey” button in the upper right corner.

3.  Name your survey. The name is internal only, so users never see it.

4.  Set a start and end date. The survey runs for that window and closes automatically.

5.  Choose your audience: all members, or one or more groups. Groups are how you narrow a survey to a specific department or team: select the group that matches the department you want to hear from. You can select more than one. Click "Manage groups" to open your organization's group settings.

6.  Choose where the survey appears: Cowork, Chat, or both.

7.  Review the questions. You can add or remove questions before the survey goes live.

8.  Check the preview and the estimated reach panel, which shows how many users are in your selected audience, how many are active in Cowork or chat, and the expected number of responses.

9.  Schedule the survey, or save it as a draft.

The following questions are included by default:

1.  “What’s the most valuable thing you’ve done with Claude recently? How could you tell the outcome was better than it would have been without Claude?” (text)

2.  “For the work you described, what (if anything) did Claude change about the time it took?” (multi-select)

3.  “What’s one way you use Claude that a teammate should copy? Describe it so they could start tomorrow: the setup, habit, or way of prompting they wouldn’t know to do.” (text)

4.  “If your team lost access to Claude tomorrow, what would break or slow down first?” (text)

## What users see

When a survey is live, users in the audience see a card at a natural break in their work: between tasks in Cowork, or after a reply in chat. A survey never interrupts a running task.

- The card shows your organization's name and explains that answers go to your organization's admins, not to Anthropic.

- Users answer one question at a time.

- Dismissing the card declines the survey.

## Review results

The **Surveys** table lists every survey by status (Live, Scheduled, or Closed), along with its audience, window, response count, and response rate.

Open a survey to see results, which aggregate while the survey is live:

- Total responses and response rate

- The split between Cowork and chat responses

- Bar charts for multi-select questions

- A feed of written responses tagged by group, surface, and date. Filter the feed by group to read the responses from a single department or team.

You can also close a survey early from the results page.

## Export responses

Each survey can be exported as a CSV file from **[Analytics \> Surveys](https://claude.ai/analytics/surveys)**. The export includes one row per response, with the submission time, the user's email, their group, the surface where they responded, and one column per question. Multi-select answers are separated by semicolons.

## Privacy and data handling

Surveys run under your organization's name, and responses go to your organization's admins. Your surveys are treated as your data so Anthropic accesses them only as necessary to operate the service. CMEK protections are coming soon, but while this feature is in beta, CMEK customers cannot use it. Regulated customers, including HIPAA customers, should not put regulated data, such as PHI, into survey responses.

Learn more about **[viewing usage analytics for Team and Enterprise plans](https://support.claude.com/en/articles/12883420)**.

---

## Create and manage Workspaces in the Claude Console | Claude Help Center

Source: https://support.claude.com/en/articles/9796807-creating-and-managing-workspaces-in-the-claude-console
Path: 13-Enterprise-Admin/creating-and-managing-workspaces-in-the-claude-console.md

# Create and manage Workspaces in the Claude Console


This guide will walk you through the process of creating, editing, and managing Workspaces in your Claude Console organization.

## What are Workspaces?

Workspaces are collaborative spaces within Console organizations where teams can separate API resources by use case.

If you need to create a fully separate organization with its own members, billing, and Workspaces, see **[Creating a separate Console organization](#h_816b6c2559)** below.

## How to create a new Workspace

**Note:** Only Organization Admins can create new Workspaces.

1.  Log in to your **[Claude Console account](https://platform.claude.com/login)**.

2.  Go to the **Workspaces** page (platform.claude.com/settings/workspaces).

3.  Click "Create workspace." The **Create workspace** page opens (platform.claude.com/settings/workspaces/new).

4.  Enter a name for your new Workspace, and select a color. The color helps you identify your Workspace in the Claude Console.

5.  Click "Create workspace" to finalize the new Workspace.

Your new Workspace will now appear in the list of Workspaces.

**Note:** You are limited to 100 workspaces per organization.

## Edit Workspace settings

1.  From the **Workspaces** list, click on the ellipsis next to the Workspace you want to edit.

2.  Select “Edit details."

3.  You can modify the following settings:

    - Workspace Name

    - Color

4.  After making your changes, click "Save" to apply them.

**Note:** You can’t rename the default Workspace or change its color, and you can’t archive or delete it.

## Add members to a Workspace

**Note:** You must add members to your Console organization by following **[these instructions](../04-API-Reference/Other/inviting-members-to-the-claude-console.md)** before you can add them to a Workspace.

1.  Go to the **Members** page (platform.claude.com/settings/members).

2.  Click "Add to workspace."

3.  Select the Workspace, the organization member to add, and their Workspace role.

4.  Click "Add to workspace" to confirm.

**Note:** Organization Admins are automatically added as Workspace Admin to every Workspace. Organization Billing role holders are automatically granted ability to see cost, usage, and limit values for all Workspaces, but can be upgraded to the Organization Admin role.

## Delete members from a Workspace

1.  Go to the **Members** page (platform.claude.com/settings/members) and select the Workspace from the **Workspace** dropdown menu. If the menu shows "All," removing a member removes them from your organization instead.

2.  At the end of the member's row, open the "More actions" menu (three dots) and select "Remove member."

3.  Click "Remove" to confirm.

**Note:** Organization members with Admin and Billing roles are automatically granted permissions on all Workspaces and cannot be removed from Workspaces.

## Manage API Keys in a Workspace

1.  Go to the **API keys** page (platform.claude.com/settings/keys).

2.  Select the Workspace from the **Workspace** dropdown menu to list only its keys.

3.  To create a new API key for this Workspace:

    - Click "Create key"

    - Give the key a descriptive name

    - Click "Create key"

4.  To revoke an existing API key:

    - Find the key in the list

    - Click the ellipsis next to it

    - Select "Disable API key" or “Delete API key”

      - **Note:** Deleting an API key is a permanent action and cannot be undone.

    - Confirm the action

**Note:** API keys are tied to the Workspace they're created in and cannot be moved between Workspaces.

## Set Workspace rate limits

1.  Go to the **Rate limits** page (platform.claude.com/usage/limits) and select the Workspace from the **Workspace** dropdown menu.

2.  Click "Edit" next to a model to set its limits.

3.  Set a limit for each model tier and limit type by clicking "Edit" next to each option.

4.  Workspace limits can be reset to the organization rate limit by selecting a model, leaving all the fields blank in the **Edit workspace rate limits** modal, and clicking "Save."

**Note:** Anthropic always evaluates all applicable limiters—at the Workspace and Organization level—for every request. In other words, if the Workspace limits are unset, Organization limits still apply.

## Set Workspace spend limits or notifications

1.  Go to the **Workspaces** page (platform.claude.com/settings/workspaces) and select the Workspace.

2.  In the panel that opens, click "Spend limits."

3.  Choose one of the following:

    - Select "Change limit" under **Monthly limit** to set a specific spend limit for this Workspace.

      - **Note:** You can only set a spend limit that is lower than your organization’s limit. If unset, your spend limit defaults to the organization’s limit. You can’t set a spend limit for the default Workspace.

    - Select "+ Add notification" under **Email notification** to set up an email notification when the Workspace spend reaches a specific amount.


## View Workspace usage and costs

Go to the **Usage** page (platform.claude.com/usage) or the **Cost** page (platform.claude.com/cost).

## Archive a Workspace

If you no longer need a Workspace but want to retain its historical data:

1.  Navigate to the **Workspaces** page.

2.  Click the ellipsis next to the Workspace you would like to archive.

3.  Confirm that you want to archive the Workspace.

**Note:** Archiving a Workspace will archive all API keys in the Workspace. This action cannot be undone.

## Manage the Default Workspace

Go to the **API keys** page (platform.claude.com/settings/keys) and select "Default" from the **Workspace** dropdown menu.

------------------------------------------------------------------------

## Create a separate Console organization

A single email address can create only one Console organization. If you've already created an organization with your email, signing up again routes you back to that organization instead of starting a new one. You can still be invited to other organizations and switch between them on the same email, but you can only create one yourself.

### When you need a separate organization

You might need a fully separate organization for a different legal entity or for separate billing. A separate organization is independent of your existing one, with its own members, billing, and Workspaces.

To create a new organization, sign up with a work email address that isn't already registered with Anthropic.

### How to create a new organization

The person who will own the new organization should complete these steps. Whoever signs up becomes the organization's Primary Owner.

1.  Go to **[Claude Console](../04-API-Reference/Other/playground.md)**.

2.  Sign up with a work email address using Google or Microsoft single sign-on (SSO), or an email and password.

3.  Verify the email address using the link sent to that inbox.

4.  Complete the organization details form, including organization name, entity type, country, and intended use, then choose "Complete setup."

The new organization is created automatically with a default Workspace, and the person who signed up is taken to the Console for that organization.

### After setup

The Primary Owner can:

- Invite members and assign roles (such as Admin, Billing, and Developer) on the **Members** page (platform.claude.com/settings/members).

- Add a payment method on the **Billing** page (platform.claude.com/settings/billing).

---

## Configure custom data retention controls for Enterprise plans | Claude Help Center

Source: https://support.claude.com/en/articles/10440198-custom-data-retention-controls-for-enterprise-plans
Path: 13-Enterprise-Admin/custom-data-retention-controls-for-enterprise-plans.md

# Configure custom data retention controls for Enterprise plans

Updated today


*This article is about our commercial products, specifically Claude Enterprise. For our consumer products such as Claude Free, Pro, Max and when accounts from those plans use Claude Code, see **[here](https://privacy.claude.com/en/collections/10663362-consumers)**.*

Custom data retention controls allow organizations to manage how long Claude stores conversation, project, and artifact data. This article explains how to set up and manage data retention periods for your organization.

This feature is available to Enterprise plan customers. A retention period set on an Enterprise plan still applies to artifacts if your organization later changes to a Team plan. To set custom retention periods for your organization, you must have either a Primary Owner or Owner role.

## How data retention works

Data retention is based on the last observed activity:

- **For chats:** Retention period starts from the time of the last message in the conversation.

- **For projects:** Retention period starts from the time of the most recent action that resets it.

  - These actions reset the retention period:

    - Sending a message in an existing chat in the project

    - Creating a new chat in the project

    - Editing the project's knowledge files, instructions, or sharing settings

    - Starting a Claude Cowork session in the cloud from the project, or moving a cloud session into the project (this resets the period once, when the session starts or is moved)

    - Connecting or disconnecting a local folder for Cowork

    - Editing the project's knowledge files from a Cowork session

  - These actions don't reset the retention period:

    - Viewing a project or opening a chat in it

    - Starting a *local* Cowork session

    - Other work in a Cowork session, whether it runs in the cloud or on your computer

  - Project retention always takes precedence over chat retention for chats inside a project. This applies even if you haven’t set a custom project retention period: by default, projects are retained indefinitely, so chats inside projects are not deleted by your chat retention period.

- **For artifacts:** Retention period starts from the last time someone opened or edited the artifact.

The minimum retention period is 30 days, and each month is counted as 30 days. For example, a three-month retention period equals 90 days.

## What gets deleted

When data reaches the end of its retention period:

- **For chats:** All standalone chats (chats not inside a project) will be deleted. Chats inside a project follow the project’s retention period. Moving a chat into a project places it under the project’s retention period; moving a chat out of a project makes it standalone again, and it becomes eligible for deletion under the chat retention period based on its last activity.

- **For projects:** All projects will be deleted, including any chats within those projects.

- **For artifacts:** Artifacts are deleted under a retention period that covers artifacts, not with the chat or project they were made in. This includes artifacts Claude creates in chats, with some exceptions. For example, Research reports and artifacts in some older chats are deleted with their chat, along with any link published from them. A retention period that covers artifacts doesn't delete artifacts published with a public link.

## Important considerations

- Deletion occurs at midnight UTC on the scheduled day.

- By default, data is retained indefinitely unless a custom retention period is set.

- When you shorten a retention period, any data that falls outside the new period is scheduled for permanent deletion as soon as you save. A daily background process removes it, which can take several days for large amounts of data. Don’t rely on this delay to reverse the change.

- Data past its retention period will be permanently deleted and cannot be recovered.

- If your chat is flagged by our automated trust and safety systems as violating our Usage Policy, we retain inputs and outputs as described here: **[How long do you store my organization’s data?](https://privacy.claude.com/en/articles/7996866)**

## Set up data retention

To set custom retention periods:

1.  Log in to your Owner Enterprise plan account.

2.  Navigate to **[Organization settings \> Data and Privacy](https://claude.ai/admin-settings/data-privacy-controls)**.

3.  Set your desired retention period (minimum 30 days).

4.  Save your changes.

**Important:** Custom retention periods apply to content in chats, projects, and artifacts. They don't apply to standalone **[Claude Design](../15-Claude-AI-Features/get-started-with-claude-design.md)** at claude.ai/design, [**Claude Tag**](../15-Claude-AI-Features/what-is-claude-tag.md), **[Claude Managed Agents](../04-API-Reference/Other/managed-agents-overview.md)**, or any other feature that runs **[Claude Code in the cloud](https://support.claude.com/en/articles/12618689)**. A Claude Design project or design system that moves, or has already moved, to Claude becomes an artifact. If your organization has a retention period that covers artifacts (a single period for all data, or the private or shared artifact period if you use separate periods), that artifact can be permanently deleted if nobody opens it for that period. The period starts on the day of the move and restarts when someone opens or edits the artifact.

## Example retention calculation

If a conversation’s last message is at 3PM UTC on March 1 with a 30-day retention period, the deletion will occur at midnight UTC on March 31.

## Monitor retention-related activities

All retention-related actions and changes are automatically tracked in **[audit logs](how-to-access-audit-logs.md)**. You can access these logs to monitor changes to retention settings and data deletion events.

---

## Data usage - Claude Code Docs

Source: https://code.claude.com/docs/en/data-usage
Path: 13-Enterprise-Admin/data-usage.md

## On this page

- [Data policies](#data-policies)
  - [Data training policy](#data-training-policy)
  - [Development Partner Program](#development-partner-program)
  - [Feedback using the /feedback command](#feedback-using-the-%2Ffeedback-command)
  - [Session quality surveys](#session-quality-surveys)
  - [Data retention](#data-retention)
- [Data access](#data-access)
- [Local Claude Code: Data flow and dependencies](#local-claude-code-data-flow-and-dependencies)
  - [Cloud execution: Data flow and dependencies](#cloud-execution-data-flow-and-dependencies)
- [Telemetry services](#telemetry-services)
- [Default behaviors by API provider](#default-behaviors-by-api-provider)
  - [WebFetch domain safety check](#webfetch-domain-safety-check)

Security and data

# Data usage

Copy pageCopy page

Learn about Anthropic’s data usage policies for Claude

Copy pageCopy page


[​](#data-policies)

Data policies


[​](#data-training-policy)

Data training policy

**Consumer users (Free, Pro, and Max plans)**: We give you the choice to allow your data to be used to improve future Claude models. We will train new models using data from Free, Pro, and Max accounts when this setting is on (including when you use Claude Code from these accounts). **Commercial users**: (Team and Enterprise plans, API, 3rd-party platforms, and Claude Gov) maintain existing policies: Anthropic does not train generative models using code or prompts sent to Claude Code under commercial terms, unless the customer has chosen to provide their data to us for model improvement (for example, the [Developer Partner Program](../15-Claude-AI-Features/about-the-development-partner-program.md)).


[​](#development-partner-program)

Development Partner Program

If you explicitly opt in to methods to provide us with materials to train on, such as via the [Development Partner Program](../15-Claude-AI-Features/about-the-development-partner-program.md), we may use those materials provided to train our models. An organization admin can expressly opt-in to the Development Partner Program for their organization. Note that this program is available only for Anthropic first-party API, and not for Amazon Bedrock or Google Cloud’s Agent Platform users.


[​](#feedback-using-the-/feedback-command)

Feedback using the `/feedback` command

If you choose to send us feedback about Claude Code using the `/feedback` command, we may use your feedback to improve our products and services. Transcripts shared via `/feedback`, or via `/bug` and `/share`, which report through the same path, are retained for 5 years. With [Claude-drafted feedback](../02-Claude-Code-CLI/tools-reference.md#sendfeedback-tool-behavior), Claude can also draft a feedback report and queue it on your machine for you to review. Claude Code sends nothing until you choose to send the draft, and a sent draft goes through the same submission path and retention as other `/feedback` reports.


[​](#session-quality-surveys)

Session quality surveys

When you see the “How is Claude doing this session?” prompt in Claude Code, responding to this survey, including selecting “Dismiss”, records only your rating. We do not collect or store any conversation transcripts, inputs, outputs, or other session data as part of the rating prompt itself. Unlike thumbs up/down feedback or `/feedback` reports, this session quality survey is a simple product satisfaction metric. After the rating prompt, you may see a separate follow-up asking “Can Anthropic look at your session transcript to help us improve Claude Code?”. This is an optional second step distinct from the rating:

- **Yes**: uploads your conversation transcript, any subagent transcripts, and the raw session log file from disk to Anthropic. Known API key and token patterns are redacted before upload. Source code, file contents, and other conversation content are uploaded as-is. Shared transcripts are retained for up to 6 months. On Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, and signed-in [Claude apps gateway](claude-apps-gateway.md) sessions, Yes writes the same payload to a local archive under `~/.claude/feedback-bundles/` instead of uploading; nothing leaves your machine until you forward that file.
- **No**: declines without sending anything
- **Don’t ask again**: declines and stops this follow-up from appearing in future sessions

Nothing is uploaded unless you explicitly select **Yes**. Organizations with [zero data retention](../02-Claude-Code-CLI/zero-data-retention.md), or where product feedback is disabled by organization policy, or where `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` is set, never see this follow-up. Your responses to this survey, including session transcripts submitted after the rating prompt, do not impact your data training preferences and cannot be used to train our AI models. To disable these surveys, set `CLAUDE_CODE_DISABLE_FEEDBACK_SURVEY=1`. The survey is also disabled when `DISABLE_TELEMETRY`, `DO_NOT_TRACK`, or `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` is set. Organizations that block nonessential traffic but capture survey responses through their own [OpenTelemetry collector](monitoring-usage.md) can opt the survey back in by setting `CLAUDE_CODE_ENABLE_FEEDBACK_SURVEY_FOR_OTEL=1`. The survey then logs ratings to the configured collector only. The transcript-share follow-up and all other Anthropic-bound feedback traffic stay disabled. To control frequency instead of disabling, set [`feedbackSurveyRate`](../02-Claude-Code-CLI/settings-reference.md#feedbacksurveyrate) in your settings file to a probability between `0` and `1`.


[​](#data-retention)

Data retention

Anthropic retains Claude Code data based on your account type and preferences. **Consumer users (Free, Pro, and Max plans)**:

- Users who allow data use for model improvement: 5-year retention period to support model development and safety improvements
- Users who don’t allow data use for model improvement: 30-day retention period
- Privacy settings can be changed at any time at [claude.ai/settings/data-privacy-controls](https://claude.ai/settings/data-privacy-controls).

**Commercial users (Team, Enterprise, and API)**:

- Standard: 30-day retention period
- [Zero data retention](../02-Claude-Code-CLI/zero-data-retention.md): available to qualified accounts for Claude Code on Claude for Enterprise. ZDR is not included in the standard Enterprise plan; it is enabled on a per-organization basis by your account team after confirming eligibility
- Local caching: Claude Code clients store session transcripts locally in plaintext under `~/.claude/projects/` for 30 days by default to enable session resumption. Adjust the period with `cleanupPeriodDays`. See [application data](../02-Claude-Code-CLI/claude-directory.md#application-data) for what’s stored and how to clear it. Transcripts of sessions started or most recently continued in Claude Desktop or Cowork are [exempt from that limit by default](../02-Claude-Code-CLI/claude-directory.md#cleaned-up-automatically).

You can delete individual cloud sessions at any time. Deleting a session permanently removes the session’s event data. For instructions on how to delete sessions, see [Delete sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md#delete-sessions). Learn more about data retention practices in our [Privacy Center](https://privacy.anthropic.com/). For full details, please review our [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms) (for Team, Enterprise, and API users) or [Consumer Terms](https://www.anthropic.com/legal/consumer-terms) (for Free, Pro, and Max users) and [Privacy Policy](https://www.anthropic.com/legal/privacy).


[​](#data-access)

Data access

For all first party users, you can learn more about what data is logged for [local Claude Code](#local-claude-code-data-flow-and-dependencies) and [remote Claude Code](#cloud-execution-data-flow-and-dependencies). [Remote Control](../02-Claude-Code-CLI/remote-control.md) sessions follow the local data flow since all execution happens on your machine; while connected, the session transcript is also stored on Anthropic servers to sync the conversation across devices, as described in [Connection and security](../02-Claude-Code-CLI/remote-control.md#connection-and-security). Note for remote Claude Code, Claude accesses the repository where you initiate your Claude Code session. Claude does not access repositories that you have connected but have not started a session in.


[​](#local-claude-code-data-flow-and-dependencies)

Local Claude Code: Data flow and dependencies

The diagram below shows how Claude Code connects to external services during installation and normal operation. Solid lines indicate required connections, while dashed lines represent optional or user-initiated data flows.

Claude Code runs locally. To interact with the LLM, Claude Code sends data over the network. This data includes all user prompts and model outputs, encrypted in transit via TLS 1.2+. Claude Code is compatible with most popular VPNs and LLM proxies. Encryption at rest depends on your model provider:

| Provider                      | Encryption at rest                                                                                                                                                                                                                                                                                                                                                                                                                      |
|-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Anthropic API                 | Infrastructure-level disk encryption (AES-256). Enable [Zero Data Retention](../02-Claude-Code-CLI/zero-data-retention.md) for no server-side persistence.                                                                                                                                                                                                                                                                                              |
| Amazon Bedrock                | AES-256 with AWS-managed keys. Customer-managed keys available via AWS KMS.                                                                                                                                                                                                                                                                                                                                                             |
| Google Cloud’s Agent Platform | Google-managed encryption keys. CMEK available.                                                                                                                                                                                                                                                                                                                                                                                         |
| Microsoft Foundry             | Depends on the deployment’s [hosting option](https://code.claude.com/docs/20-Models/build-with-claude-claude-in-microsoft-foundry-cac680095c.md#hosting-options). For Hosted on Azure deployments, prompts and completions remain within Azure; only usage metadata and content flagged by Anthropic’s safety systems egress to Anthropic. For Hosted on Anthropic deployments, requests route to Anthropic infrastructure with AES-256 disk encryption. |

Claude Code is built on Anthropic’s APIs. For details on API security controls, including API logging procedures, see the compliance artifacts in the [Anthropic Trust Center](https://trust.anthropic.com).


[​](#cloud-execution-data-flow-and-dependencies)

Cloud execution: Data flow and dependencies

[Cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) run in Anthropic-managed virtual machines by default instead of locally. Sessions your organization routes to a [self-hosted environment](self-hosted-environments.md) run on infrastructure you control; for what stays on your machines and what still goes to Anthropic, see [What stays on your infrastructure](self-hosted-environments.md#what-stays-on-your-infrastructure). In Anthropic-hosted cloud sessions:

- **Code and data storage:** Your repository is cloned into the session’s isolated VM. Anthropic stores the session transcript so you can return to the session later. Code and session data are subject to the [retention and usage policies](#data-retention) for your account type
- **Credentials:** GitHub credentials are stored encrypted on Anthropic’s servers and never enter the VM. GitHub traffic from the VM goes through an Anthropic proxy that attaches them on the server side
- **Network traffic:** All outbound traffic goes through a security proxy for audit logging and abuse prevention
- **Session data:** Prompts, code changes, and outputs follow the same data policies as local Claude Code usage

For security details about cloud execution, see [Security](security.md#cloud-execution-security).


[​](#telemetry-services)

Telemetry services

Claude Code sends two kinds of operational telemetry: usage metrics and error reports. You can turn each off individually with the environment variables below, or disable all non-essential traffic at once by setting `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`. Setting `DISABLE_TELEMETRY` or `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` also disables feature-flag evaluation, which can make [Remote Control](../02-Claude-Code-CLI/remote-control.md#requirements) unavailable; `DISABLE_ERROR_REPORTING` doesn’t. **Metrics**: latency, reliability, and usage patterns, sent to Anthropic and to third-party logging infrastructure over TLS. Metrics never include your code, prompts, or file paths. Set `DISABLE_TELEMETRY=1` to opt out. **Error reports**: error messages and stack traces from Claude Code’s own internals, sent to a third-party error tracking service over TLS. Claude Code redacts known patterns of secrets, file paths, email addresses, and other personal information before anything leaves your machine. Set `DISABLE_ERROR_REPORTING=1` to opt out. Error reporting is on only when all of these apply:

- you sign in with a Claude Pro or Max subscription
- you’re running Claude Code v2.1.198 or later
- you’re connecting directly to the Claude API
- your organization doesn’t have a zero data retention or HIPAA agreement

When you run the `/feedback` command, a copy of your conversation history including code is sent to Anthropic. The `/bug` and `/share` commands submit through the same path. Before submitting a report from the feedback dialog, you choose how much history to include: the current session only, which is the default, or also other sessions from the same project over the last 24 hours or 7 days. Claude Code submits [Claude-drafted feedback](../02-Claude-Code-CLI/tools-reference.md#sendfeedback-tool-behavior) through the same path, including the transcript when you chose to include it on the draft’s review screen. The data is encrypted in transit via TLS and stored in Google Cloud Storage, which encrypts stored data at rest by default. Optionally, a GitHub issue is created in the public repository. To opt out, set the `DISABLE_FEEDBACK_COMMAND` environment variable to `1`. When you use a third-party provider such as Amazon Bedrock or Google Cloud’s Agent Platform, or have no Anthropic credentials configured, `/feedback` writes the report to a local archive under `~/.claude/feedback-bundles/` instead of sending it to Anthropic. Known API key and token patterns are redacted before the archive is written. Nothing leaves your machine until you send that file to your Anthropic account representative or attach it to a support request.


[​](#default-behaviors-by-api-provider)

Default behaviors by API provider

By default, error reporting, telemetry, and bug reporting are disabled when using Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, or Claude Platform on AWS. Session quality surveys and the WebFetch domain safety check are exceptions and run regardless of provider. On a signed-in [Claude apps gateway](claude-apps-gateway.md) session, usage analytics, error reporting, and survey ratings to Anthropic are disabled by the gateway credential itself, with no setting to re-enable them. You can opt out of all non-essential traffic, including surveys, at once by setting `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`. This variable doesn’t affect the WebFetch check or official plugin marketplace auto-install; each has its own opt-out: `skipWebFetchPreflight` in [settings](../02-Claude-Code-CLI/settings.md) for WebFetch, and `CLAUDE_CODE_DISABLE_OFFICIAL_MARKETPLACE_AUTOINSTALL` for the marketplace. Here are the full default behaviors:

[TABLE]

All environment variables can be checked into `settings.json` (see [settings reference](../02-Claude-Code-CLI/settings-reference.md)). When a host platform sets `CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST`, metrics default to on for Google Cloud’s Agent Platform, Amazon Bedrock, Microsoft Foundry, and Claude Platform on AWS, and follow the standard `DISABLE_TELEMETRY` opt-out. Error reporting and `/feedback` reports remain off by default on those providers.


[​](#webfetch-domain-safety-check)

WebFetch domain safety check

Before fetching a URL, the WebFetch tool sends the requested hostname to `api.anthropic.com` to check it against a safety blocklist maintained by Anthropic. Only the hostname is sent, not the full URL, path, or page contents. Claude Code caches a hostname that passes the check for five minutes, and re-checks a blocked or failed hostname on the next request. This check runs regardless of which model provider you use and is not affected by `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`. If your network blocks `api.anthropic.com`, WebFetch requests fail until you either allowlist the domain or set `skipWebFetchPreflight: true` in [settings](../02-Claude-Code-CLI/settings.md). Disabling the check means WebFetch attempts to retrieve any URL without consulting the blocklist, so combine it with [`WebFetch` permission rules](../02-Claude-Code-CLI/permissions.md#webfetch) if you need to restrict which domains Claude can reach.

---

## Deploying enterprise-grade MCP servers with desktop extensions | Claude Help Center

Source: https://support.claude.com/en/articles/12702546-deploying-enterprise-grade-mcp-servers-with-desktop-extensions
Path: 13-Enterprise-Admin/deploying-enterprise-grade-mcp-servers-with-desktop-extensions.md

# Deploying enterprise-grade MCP servers with desktop extensions

August 5, 2026


Desktop extensions are installable packages that run Model Context Protocol (MCP) servers locally on your machine. They provide Claude Desktop with secure access to your local resources, internal systems, and personal tools without the complexity of remote infrastructure.

With desktop extensions, you can deploy enterprise-grade MCP implementations with fine-grained admin controls. This article explains why desktop extensions are valuable for organizations and how to use them effectively.

## Why use desktop extensions?

### Seamless authentication

Desktop extensions operate within your corporate network boundaries, using the user’s existing authenticated context without requiring additional firewall rules or VPN configurations. They leverage existing SSO and browser sessions automatically with no token management required – credentials stay securely on user devices.

### Access internal resources

Desktop extensions connect to internal wikis, JIRA, Confluence, and other systems behind your firewall. They can query private databases and APIs without VPN configuration, connect to on-premises systems like SAP, Oracle, and custom applications, and access internal resources without exposing them on public infrastructure.

### Read and manipulate local resources

Desktop extensions provide access to local resources that remote connectors cannot reach. This includes direct filesystem access for code editing and Git operations, integration with locally installed tools like Docker, IDEs, and databases, control over desktop applications via local APIs, and hardware integration for specialized workflows.

### Instant deployment with minimal infrastructure overhead

One-click installation through Claude Desktop comes with no dependencies to manage. The built-in Node.js runtime is included, there’s no cloud infrastructure to provision, and updates are distributed directly through the extension marketplace.

### Enterprise-grade controls

Organization owners can upload custom extensions to their organization and control which desktop extensions their users have access to through the desktop extension allowlist. To learn more about managing desktop extensions in your organization, see Enabling and using the desktop extension allowlist.

## Enterprise use cases

**Multiple enterprises are building desktop extensions to serve a variety of use cases:**

- As secure proxies to internal MCP servers, giving them complete control over access, authentication, and audit logs while maintaining their zero-trust architecture.

- To give Claude access to internal documentation and knowledge bases.

- To connect to development tools and databases securely, and maintain complete control over authentication, authorization, and audit logging.

Desktop extensions eliminate the traditional tradeoff between security and usability—providing Claude with powerful capabilities while maintaining your enterprise security posture.

---

## Does Anthropic Act as a Data Processor or Controller? | Claude Help Center

Source: https://support.claude.com/en/articles/9267385-does-anthropic-act-as-a-data-processor-or-controller
Path: 13-Enterprise-Admin/does-anthropic-act-as-a-data-processor-or-controller.md

# Does Anthropic Act as a Data Processor or Controller?


*This article is about our commercial products such as Claude for Work and the Anthropic API. For our consumer products such as Claude Free, Pro, Max and when accounts from those plans use Claude Code, see [here](https://privacy.claude.com/en/collections/10663362-consumers).*

When a commercial customer creates a Claude for Work account (Team or Enterprise plan), under our [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms) the customer is the "Controller" of the data submitted by its Users. This means:

- The customer has control over who can be a member of their team and provides us with instructions for how submitted data can be used.

- The customer may access and export data (such as conversation history) submitted by its Users.

- Anthropic acts as a "Processor" of the data on behalf of the customer. Anthropic only processes the data as instructed by the customer in order to provide the Claude service.

- Anthropic does not use the data you share when using our commercial products to train our models, unless you choose to participate in our [Development Partner Program](../15-Claude-AI-Features/about-the-development-partner-program.md).

In summary, for Claude for Work, the customer organization maintains control over their Users' data, and Anthropic only processes that data to provide the service on the customer's behalf, according to our agreement. You should consult with your organization's administrators regarding Claude usage policies.

## Resources

For more details on our data practices, please see the:

- [Anthropic Privacy Center](https://privacy.claude.com/en/collections/10663361-commercial-customers)

- [Anthropic Trust Center](https://trust.anthropic.com)

- [Anthropic Privacy Policy](https://www.anthropic.com/legal/privacy)

---

## Enable US-only inference for your organization | Claude Help Center

Source: https://support.claude.com/en/articles/15422948-enable-us-only-inference-for-your-organization
Path: 13-Enterprise-Admin/enable-us-only-inference-for-your-organization.md

# Enable US-only inference for your organization

June 15, 2026


This article explains what the **US-only inference** setting does, how to turn it on, and how it affects billing for your Enterprise organization.

US-only inference is available on usage-based Enterprise plans. Primary Owners, Owners, and custom roles with the Privacy permission set to "Can manage" can manage this setting.

## What is US-only inference?

US-only inference keeps your organization's inference, the model processing that happens when Claude generates a response, within the United States.

When the setting is on, all inference for your organization runs on servers located in the United States. This applies to usage across all the Claude apps, including Claude Code, Claude Desktop, etc., and includes background processing like conversation titles and memory. Using US-only inference comes with an increased cost. See **[How billing works](#h_8f4f5c2b9d)** below.

When the setting is off, inference may be routed to servers in other regions for speed and availability.

## Enable US-only inference

1.  Navigate to **[Organization settings \> Data and privacy](https://claude.ai/admin-settings/data-privacy-controls)**.

2.  Find **US-only inference**.

3.  Turn on the toggle.

The setting applies to your entire organization. Changes should take effect instantly, but may have some delay based on which of the Claude apps you’re using.

**Note:** If your organization previously opted out of global routing or has US-only inference requirements in its contract, the setting is already on and can't be changed. Your existing pricing terms continue to apply. Contact your account team with questions.

## How billing works

When US-only inference is on, your organization's usage is billed at 1.1x standard API rates for Claude Opus 4.6, Claude Sonnet 4.6, and later models. Your seat fees don't change and the 1.1x rate applies only to usage. Requests on Claude Opus 4.5, Claude Sonnet 4.5, Claude Haiku 4.5, or earlier models are billed at the standard rate.

Learn more about **[how you're billed for your Enterprise plan](../17-Billing-Plans/how-am-i-billed-for-my-enterprise-plan.md)**.

## What US-only inference doesn't cover

US-only inference applies to inference only, where Claude's models process your requests.

It doesn't control:

- **Connectors and third-party services.** When Claude works with a connected service like Slack or Google Drive, that service processes data on its own infrastructure, which may be located outside the US. The US-only inference setting doesn't change where third-party services operate.

- **Data storage.** Where your organization's data is stored is separate from where inference runs.

## Frequently asked questions

### Who can see and change this setting?

Primary Owners, Owners, and members with a custom role that has the Privacy permission set to "Can manage" can change the setting. Members with Privacy set to "Can view" can see it but can't change it.

### Does this setting apply to the Claude API?

This setting applies to your Claude Enterprise organization. For inference location options on the Claude API, see the **[data residency documentation on Claude API Docs](../04-API-Reference/Other/manage-claude-data-residency.md)**.

### Why don't I see this setting?

US-only inference is only available on usage-based Enterprise plans. If your organization is on a Team plan or a legacy seat-based Enterprise plan, the setting won't appear.

---

## Enforce network-level access control with Tenant Restrictions | Claude Help Center

Source: https://support.claude.com/en/articles/13198485-enforce-network-level-access-control-with-tenant-restrictions
Path: 13-Enterprise-Admin/enforce-network-level-access-control-with-tenant-restrictions.md

# Enforce network-level access control with Tenant Restrictions


Tenant Restrictions are available for members of Enterprise plans and Console organizations.

Tenant Restrictions enable IT administrators on Enterprise plans to enforce network-level access control for Claude. Tenant Restrictions also apply to artifacts, including Claude Docs. This feature ensures that users on corporate networks can only access approved organizational accounts, preventing unauthorized use of personal accounts.

## How it works

When enabled, your network proxy injects an HTTP header into requests to Claude. Anthropic validates this header and blocks access from any organization not in the allowed list.

**Supported authentication methods:**

- Web access ([claude.ai](https://claude.ai))

- Desktop / App Access

- API key authentication

- OAuth token authentication

## Header format

    anthropic-allowed-org-ids: <org-uuid-1>,<org-uuid-2>,...

- Comma-delimited list of organization UUIDs

- No spaces between values

**Example:**

    anthropic-allowed-org-ids: 550e8400-e29b-41d4-a716-446655440000,6ba7b810-
    9dad-11d1-80b4-00c04fd430c8

## Split large allowlists across multiple headers

If your allowlist is too long for a single header line on your proxy platform, split it across numbered continuation headers. Append `;n=K` to the base header to declare the total number of header lines, then add the remaining UUIDs in headers `anthropic-allowed-org-ids1` through `anthropic-allowed-org-ids{K-1}`.

    anthropic-allowed-org-ids: <org-uuid>,<org-uuid>,...;n=K
    anthropic-allowed-org-ids1: <org-uuid>,<org-uuid>,...
    ...
    anthropic-allowed-org-ids{K-1}: <org-uuid>,<org-uuid>,...

- Maximum of 10 header lines (`K` ≤ 10)

- Maximum of 500 organization UUIDs total across all lines

- Your proxy must send every declared slot. If you set `;n=3`, all three headers must be present on the request.

- Configure your proxy to overwrite these headers on every request rather than add them only if absent.

------------------------------------------------------------------------

## Configuration steps

### 1. Find your organization UUID

Members of Enterprise plans can find this in two different places:

1.  Navigate to **[Settings \> Account](https://claude.ai/settings/account)** and find **Organization ID**.

2.  Navigate to **[Organization settings \> Organization](https://claude.ai/admin-settings/organization)** and scroll down to the bottom of the page to locate **Organization ID**.

Members of Console organizations can find this in **[Settings \> Organization](../04-API-Reference/Other/playground.md)**.

### 2. Configure your network proxy

Configure your proxy to inject the header for Claude traffic:

    Rule: Claude Tenant Restriction
    Application: claude.ai, api.anthropic.com, claude.com, anthropic.com
    Action: Inject Header
    Header Name: anthropic-allowed-org-ids
    Header Value: 
    TLS Inspection: Required

### 3. Test your configuration

From restricted network, test with your org's API key:

    curl https://api.anthropic.com/v1/messages \
      -H "x-api-key: $CLAUDE_API_KEY" \
      -H "anthropic-version: 2023-06-01" \
      -H "content-type: application/json" \
      -d '{"model":"claude-sonnet-4-6","max_tokens":1024,"messages":
     [{"role":"user","content":"Hello"}]}'

------------------------------------------------------------------------

## Error responses

### Access blocked

When a user's organization isn't on the allowlist, they receive a 403 error:

    {
      "type": "error",
      "error": {
        "type": "permission_error",
        "message": "Access restricted by network policy. Contact IT Administrator.",
        "error_code": "tenant_restriction_violation"
      }
    }

### Header configuration errors

If your proxy sends the headers incorrectly, requests fail with a 400 status and one of these messages:

- Multiple `anthropic-allowed-org-ids` headers: A header name appeared more than once on the same request. Configure your proxy to overwrite each header rather than append a duplicate.

- Malformed `anthropic-allowed-org-ids` headers: The `;n=K` value is invalid, a declared continuation slot is missing or extra, or the total allowlist exceeds 500 UUIDs.

## Supported proxy platforms

- Cato Networks (Tenant Restriction policy)

- Cloudflare Zero Trust / Gateway (HTTP policy, add custom request headers)

- Netskope (Header Insertion rules)

- Palo Alto Prisma Access (SaaS App Management)

- Zscaler ZIA (Cloud App Control policies)

- Generic HTTPS proxies with header injection capability

## Use cases

[TABLE]

## Security benefits

- **Data Loss Prevention:** Block personal account usage from corporate networks.

- **Compliance:** Enforce data residency and access policies.

- **Shadow IT Control:** Prevent unauthorized Claude usage.

- **Audit Trail:** Complete visibility into access attempts.

## Backward compatibility

- No impact to networks without tenant restrictions configured.

- Standard authentication continues for unmanaged networks.

- Existing API key authentication remains unchanged.

---

## Enterprise configuration for Claude Desktop | Claude Help Center

Source: https://support.claude.com/en/articles/12622667-enterprise-configuration-for-claude-desktop
Path: 13-Enterprise-Admin/enterprise-configuration-for-claude-desktop.md

# Enterprise configuration for Claude Desktop


Administrators on Team or Enterprise plans can control Claude Desktop through system policies.

**Note:** Enterprise policy controls at the user-machine level will override the in-app **[allowlist](../16-Mobile-Desktop/enabling-and-using-the-desktop-extension-allowlist.md)**. If you want to use the allowlist, ensure `isDesktopExtensionEnabled` and `isDesktopExtensionDirectoryEnabled` are not set to "false" so the allowlist can populate the available registry.

------------------------------------------------------------------------

## macOS enterprise configuration

Deploy configuration settings through your MDM solution using configuration profiles. Claude Desktop reads preferences from the domain \`com.anthropic.claudefordesktop\`. Use your MDM tool (Jamf Pro, Kandji, Microsoft Intune) to deploy configuration profiles to target machines or user groups. Configuration profiles allow you to manage Claude Desktop settings centrally without user intervention.

**Configuration profile tools:**

- Built-in MDM profile editors (Jamf Pro, Kandji, Intune)

- **[ProfileCreator](https://github.com/profileCreator/ProfileCreator/)** - Profile management

- **[iMazing Profile Editor](https://imazing.com/profile-editor)** - Configuration profiles

------------------------------------------------------------------------

## Windows enterprise configuration

Deploy configuration settings through your enterprise management solution using **[Group Policy](https://learn.microsoft.com/en-us/previous-versions/windows/desktop/policy/group-policy-objects)** or Intune policies. Settings can be configured at machine-wide (HKLM) or per-user (HKCU) level. Machine-level settings take priority over user-level settings when both are configured.

    # Set machine-wide settings (recommended)
    New-Item -Path "HKLM:\SOFTWARE\Policies\Claude" -Force
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Claude" -Name "disableAutoUpdates" -Value 0 -Type DWord
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Claude" -Name "autoUpdaterEnforcementHours" -Value 72 -Type DWord
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Claude" -Name "isDesktopExtensionEnabled" -Value 1 -Type DWord
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Claude" -Name "isDesktopExtensionDirectoryEnabled" -Value 1 -Type DWord
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Claude" -Name "isLocalDevMcpEnabled" -Value 1 -Type DWord
    Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Claude" -Name "isClaudeCodeForDesktopEnabled" -Value 1 -Type DWord

------------------------------------------------------------------------

## Enterprise policy options

[TABLE]

------------------------------------------------------------------------

## Set `forceLoginOrgUUID` for multiple organizations

To accept more than one organization, set `forceLoginOrgUUID` to a JSON array of organization UUIDs, saved as a single string value.

**Windows**

Use a string value (REG_SZ) whose data is the JSON text. Don’t use a multi-string value (REG_MULTI_SZ).

    New-ItemProperty -Path 'HKLM:\SOFTWARE\Policies\Claude' -Name forceLoginOrgUUID 
    -PropertyType String -Value '["<uuid-1>","<uuid-2>"]' -Force

**macOS**

In your configuration profile for the `com.anthropic.claudefordesktop` domain, set the key as a string that contains the JSON text:

    <key>forceLoginOrgUUID</key> 
    <string>["<uuid-1>","<uuid-2>"]</string>

Claude Desktop reads this setting when it launches. After you change it, quit and reopen Claude Desktop. To check the value the app read, go to **Help \> Troubleshooting \> Generate Diagnostic Report**.

---

## Find and join a Console organization | Claude Help Center

Source: https://support.claude.com/en/articles/14793921-find-and-join-a-console-organization
Path: 13-Enterprise-Admin/find-and-join-a-console-organization.md

# Find and join a Console organization


Organization discovery lets you find and join your company’s existing Console organization when you start the sign-up flow with a business email address. Instead of creating a personal account, you can request to join—or be added automatically—depending on your organization’s configuration.

**Note:** Organization discovery only applies to organization accounts on the Console, not individual accounts, and must be enabled by an Admin before the capability is available. It’s unavailable for organizations that have single sign-on (SSO) enabled. If your organization uses SSO, your existing provisioning settings remain in effect.

## Admin setup

### Enable discoverability

Admins can manage organization discovery from identity and access settings.

**To enable discoverability:**

1.  Log in as an Admin.

2.  Go to the **Identity and access** page (platform.claude.com/settings/identity).

3.  Your organization’s domains are listed at the top of the page.

4.  Find the domain you want people to search for and click the toggle under **Discoverable**.

5.  Find **New member approval** under **Authentication** and choose either “Approve automatically” or “Require admin approval.”

### Configure allowed domains

Admins can specify which email domains are allowed to discover and join the organization by clicking “Add domain” under **Domains** on the identity and access settings page. The organization owner’s domain appears on the **Domains** list automatically. Admins can configure additional allowed domains by adding them to the list, verifying them, and toggling **Discoverable** on. Personal email domains (like Gmail) and .edu domains can’t be added to the list.

### Choose an approval mode

Admins select how join requests are handled:

- **Approve automatically:** People are added to the organization automatically when they ask to join. They can immediately access the workspaces and resources their role permits.

- **Require admin approval:** An admin reviews and approves each join request individually. People aren’t added to the organization until an admin approves the request.

------------------------------------------------------------------------

## How to find and join an organization

When someone signs up for the Console with a business email address that matches a discoverable organization, they’ll see the option to join during the signup flow. They can choose to join or continue with a personal account.

- If the organization uses “Approve automatically” to handle join requests, they’re added to the Console organization immediately.

- If the organization uses “Require admin approval” for join requests, a request is sent to the admin for review. The requester can choose to continue with a personal account until the request is approved or denied.

If multiple organizations share the same email domain and are all discoverable, the requester sees all of them and can choose which one to join.

------------------------------------------------------------------------

## Other ways to join an organization

In addition to organization discovery, there are a few other ways to join a Console organization:

- **Email invitation:** An Admin or existing member of the organization may send you an email invitation to join.

- **Admin invitation:** An Admin can add you directly from the **Members** page (platform.claude.com/settings/members).

------------------------------------------------------------------------

## SSO and organization discovery

Organization discovery isn’t available for Console organizations with single sign-on enabled. If your organization uses SSO, the feature doesn’t apply—your existing provisioning settings (including any just-in-time provisioning) remain unchanged. To enable organization discovery, SSO must be turned off first.

---

## Find and join a Team or Enterprise organization | Claude Help Center

Source: https://support.claude.com/en/articles/13566435-find-and-join-a-team-or-enterprise-organization
Path: 13-Enterprise-Admin/find-and-join-a-team-or-enterprise-organization.md

# Find and join a Team or Enterprise organization

July 21, 2026


Organization discovery allows you to find and join your company's existing Team or Enterprise plan organization when you start the sign-up flow with a work email address. Instead of creating a separate personal account, you can request to join—or be added automatically—depending on your organization's configuration.

Organization discovery must be enabled by an admin before the capability is available. It’s unavailable for organizations that have **[single sign-on (SSO) enabled](../21-Account-Support/setting-up-single-sign-on-sso.md)**. If your organization uses SSO, your existing provisioning settings remain in effect.

------------------------------------------------------------------------

## Admin setup

### Enable discoverability

Admins and above can manage organization discovery from **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)**.

- **New organizations:**

  - **Team plans:** Discoverability is on by default. Admins see the option during plan onboarding with it pre-selected.

  - **Enterprise plans:** Discoverability is off by default. Admins will see the option disabled on the Organization and access page.

- **Existing organizations:** Discoverability is off by default. Admins can turn it on from settings at any time.

To enable discoverability:

1.  Log in as an Admin, Owner, Primary Owner.

2.  Navigate to **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)**.

3.  Your organization’s domains are listed at the top of the page.

4.  Find the domain you want users to search for and click the toggle under **Discoverable**.

5.  Find **New member approval** under **User provisioning** and choose either “Approve automatically” or “Require admin approval.”

### Configure allowed domains

Admins can specify which email domains are allowed to discover and join the organization by clicking “+ Add domain” under **Domains** on the organization and access page. The organization owner’s domain will appear on the **Domains** list automatically, but admins can configure additional allowed domains by adding them here, verifying them, and toggling **Discoverable** on. Personal email domains (like Gmail) and .edu domains can't be added to the allowed list.

### Choose an approval mode

Admins select how join requests are handled:

**Approve automatically:** Users are added to the organization’s lowest available seat tier automatically when they ask to join. Billing begins as soon as a user joins—if the organization has no available seats, billing auto-expands and a new seat is purchased immediately.

**Require admin approval:** The admin reviews and approves each join request individually. Users aren't added to the organization until the admin approves. Billing begins when the request is approved—if no seats are available at that point, a new seat is purchased.

This approval mode also applies to invitations sent by existing members of your organization. For additional details, see **[Manage members on Team and Enterprise plans](../17-Billing-Plans/managing-members-on-team-and-enterprise-plans.md)**.

------------------------------------------------------------------------

## How to find and join an organization

When someone signs up for Claude with a work email address that matches a discoverable organization, they'll see the option to join during the signup flow. They can choose to join or continue with a personal account.

- If the organization uses **instant approval**, they're added right away.

- If the organization uses **request + approve**, a request is sent to the admin. The requester can choose to continue with a personal account (as long as "Restrict organization creation" is disabled) until the request is approved or denied.

If multiple organizations share the same email domain and are all discoverable, users will see all of them and can choose which one to join. You can be a member of multiple Team or Enterprise organizations at the same time.

------------------------------------------------------------------------

## Join when you already have a personal Claude account

If you have a Free, Pro, or Max account on the same email address as the organization you're joining, you can choose what to do with it:

- **Keep both accounts.** Your personal account stays active, and you switch between it and your organization account from your account menu.

- **Use your organization account only.** Your personal account closes. You choose whether to bring your data with you or delete it.

Some things don't move either way, including custom skills and your connected apps. Before you choose, review **[Move your personal Claude account to a Team or Enterprise organization](../21-Account-Support/can-individuals-with-pro-or-max-plan-accounts-migrate-them-to-team-or-enterprise-plan.md)**.

On Team plans, you're prompted when you accept the invite. On Team and Enterprise plans, you can start the migration from **[Settings \> Account](https://claude.ai/settings/account)** at any time after joining. Start from your organization account rather than your personal one—if you're signed in to the personal account, switch first by clicking your initials or name in the lower left corner.

------------------------------------------------------------------------

## Other ways to join an organization

In addition to organization discovery, there are a few other ways to join a Team or Enterprise organization:

- **Invite link:** Your admin may share an invite link that lets you join directly. See **[Join an organization via invite link](join-an-organization-via-invite-link.md#h_af9f6b7825)**.

  - **Note:** If the invite link has been disabled or regenerated by your admin, it will no longer work. Ask your admin to share a new link.

- **Email invitation:** An admin or existing member of the organization may send you an email invitation to join.

- **Admin invitation:** An admin can add you directly from admin settings. See **[Manage members on Team and Enterprise plans](../17-Billing-Plans/managing-members-on-team-and-enterprise-plans.md)**.

------------------------------------------------------------------------

## SSO and organization discovery

Organization discovery is not available for organizations with single sign-on enabled. If your organization uses SSO, the feature doesn't apply—your existing provisioning settings (including any just-in-time provisioning) remain unchanged.

If you'd like to enable organization discovery, SSO must be turned off first.

---

## Run Claude Code through a gateway - Claude Code Docs

Source: https://code.claude.com/docs/en/gateways
Path: 13-Enterprise-Admin/gateways.md

## On this page

- [How a gateway works](#how-a-gateway-works)
- [Choose a gateway](#choose-a-gateway)
  - [Claude apps gateway](#claude-apps-gateway)
  - [Other gateways](#other-gateways)
- [Subscriptions and gateways](#subscriptions-and-gateways)
- [Configure separately from the gateway](#configure-separately-from-the-gateway)
- [Next steps](#next-steps)

Gateways

# Run Claude Code through a gateway

Copy pageCopy page

Route Claude Code through a self-hosted gateway for centralized credentials, usage tracking, and cost controls. Covers the architecture, Anthropic’s Claude apps gateway, and using other gateway products.

Copy pageCopy page

A gateway is a proxy your organization runs between Claude Code and a model provider. Claude Code sends API traffic to the gateway instead of directly to the provider, and the gateway forwards it using a credential your organization holds. Developers authenticate to the gateway rather than holding provider credentials, so authentication, usage tracking, budgets, and audit logging happen in one place you control. Claude Code includes a self-hosted gateway, [Claude apps gateway](claude-apps-gateway.md), in the `claude` binary, so you don’t have to adopt a separate gateway product to run one. If your organization already runs an [LLM gateway](llm-gateway.md), Claude Code works with that too. This page covers:

- [How a gateway sits between Claude Code and your provider](#how-a-gateway-works)
- [Choosing between Claude apps gateway and a gateway you already run](#choose-a-gateway)
- [How gateways interact with claude.ai subscriptions](#subscriptions-and-gateways)
- [What’s configured separately from the gateway](#configure-separately-from-the-gateway)


[​](#how-a-gateway-works)

How a gateway works

Each developer’s Claude Code sends its requests to the gateway’s address and authenticates with a gateway-issued credential. The gateway authenticates the developer, applies whatever access and budget rules you configure, and forwards the request to your provider with the organization’s credential. The provider can be Anthropic’s API or a [cloud provider](../02-Claude-Code-CLI/third-party-integrations.md) such as Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry; the gateway’s configuration decides. With Claude apps gateway, or another gateway that exposes a single Anthropic-format endpoint, changing provider doesn’t require touching developer machines.

Two kinds of credential are involved:

- **Developer credential**: each developer holds their own, issued by the gateway. It authenticates them to the gateway and identifies them in usage tracking
- **Provider credential**: the gateway holds one credential for your provider account, shared by all forwarded traffic


[​](#choose-a-gateway)

Choose a gateway

Claude Code works with Anthropic’s own gateway or with a gateway your organization already runs.


[​](#claude-apps-gateway)

Claude apps gateway

Claude apps gateway is Anthropic’s self-hosted gateway, included in the `claude` binary. It routes to Amazon Bedrock, Claude Platform on AWS, Google Cloud, Microsoft Foundry, or the Anthropic API as the upstream. Developers sign in with your corporate identity provider through `/login`, the gateway enforces model access and [managed settings](managed-settings.md) by IdP group, and it emits [OpenTelemetry Protocol (OTLP)](monitoring-usage.md) usage metrics to your own observability stack. Because it is built and tested alongside each Claude Code release, it forwards the `anthropic-beta` values and request fields Claude Code sends. A gateway maintained separately needs its [forwarding rules updated](llm-gateway-protocol.md#forward-as-open-lists) as those change with each release; Claude apps gateway releases with the CLI, so there is no list to keep current. See [Availability and limitations](claude-apps-gateway.md#availability-and-limitations) for the small set of features that behave differently on a gateway session. The gateway sign-in is a browser SSO step, and there is no service-token flow, so a CI pipeline with no developer to approve the sign-in can’t authenticate through it; configure those against your provider directly. Agent SDK sessions and `claude -p` runs on a machine where a developer has signed in use that machine’s gateway session and are governed by its policies. See [CI pipelines and remote machines](claude-apps-gateway.md#ci-pipelines-and-remote-machines). See [Claude apps gateway](claude-apps-gateway.md) to deploy it.


[​](#other-gateways)

Other gateways

If your organization already runs an LLM gateway or API gateway, you can use it instead. Anthropic doesn’t endorse, maintain, or audit other gateway products, and doesn’t support routing Claude Code to non-Claude models through any gateway. See [Other LLM gateways](llm-gateway.md) for the admin rollout checklist, what a gateway must implement, and how to point Claude Code at it.


[​](#subscriptions-and-gateways)

Subscriptions and gateways

When developers connect through a gateway with a gateway credential, usage is billed to your organization’s provider account at API rates, and their claude.ai subscriptions aren’t used or charged. Setting [`ANTHROPIC_AUTH_TOKEN`](../02-Claude-Code-CLI/env-vars.md) for a gateway you run, or signing in to a Claude apps gateway with `/login`, turns off subscription login for that session. Every request forwarded under that credential is charged to the account behind the gateway’s provider credential. The exception is setting only `ANTHROPIC_BASE_URL`, with no gateway credential. Requests still route through the gateway, but a saved claude.ai login stays the active credential, so the subscription’s usage limits and billing apply. [Other LLM gateways](llm-gateway.md#subscriptions-and-gateways) covers that configuration and what the gateway has to forward for it to work.


[​](#configure-separately-from-the-gateway)

Configure separately from the gateway

A gateway routes model API requests. A few things you might expect it to handle are configured elsewhere:

- **Which model answers**: pick the model with the `/model` command or [model environment variables](../02-Claude-Code-CLI/model-config.md#setting-your-model). The gateway decides where requests go, not which model the developer selects. Claude apps gateway can bound the choice with a per-group `availableModels` allowlist, but the developer still picks within it.
- **Other network traffic**: Claude Code itself sends version checks and downloads directly to Anthropic, separate from the gateway path. Your network still needs egress to the [required domains](network-config.md), or set [`CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`](../02-Claude-Code-CLI/env-vars.md) to turn off the optional streams.
- **Client telemetry**: Claude Code disables its Anthropic-bound client analytics when a session signs in to a Claude apps gateway. To keep pre-sign-in startup analytics off as well, deliver [`DISABLE_TELEMETRY`](managed-settings.md#turn-telemetry-off-for-your-organization) in the [client-side managed settings](claude-apps-gateway-config.md#client-side-managed-settings) on each device.
- **Client telemetry on other gateways**: whether Claude Code sends the optional client telemetry stream depends on your provider, and the [telemetry defaults table](data-usage.md#default-behaviors-by-api-provider) covers each case.
- **Telemetry destinations**: where Claude Code sends a gateway session’s telemetry depends on how the session signed in, and [What’s enforced on developers](claude-apps-gateway.md#whats-enforced-on-developers) says where each kind of session’s exports go.
- **Corporate HTTP proxies**: an `HTTPS_PROXY` sits between Claude Code and every server it talks to, including the gateway. If your network requires one, [configure the proxy](network-config.md) in addition to the gateway. For a Claude apps gateway you host, [sign-in checks that the proxy host is also on a private network](claude-apps-gateway.md#prerequisites); if it isn’t, add the gateway host to `NO_PROXY` so the CLI connects to it directly.


[​](#next-steps)

Next steps

The next page depends on who runs the gateway. Anthropic’s gateway runs from the `claude` binary and has its own setup guide; a gateway your organization already runs has a compatibility guide to follow and an admin rollout checklist.

- [Claude apps gateway](claude-apps-gateway.md) to deploy Anthropic’s self-hosted gateway with SSO sign-in and OTLP telemetry
- [Other LLM gateways](llm-gateway.md) for what a gateway your organization already runs must implement, and how to point Claude Code at it
- [Set up Claude Code for your organization](admin-setup.md) for the wider rollout decisions a gateway is one part of

---

## Get started with Claude for Education at your university (for Owners/Admins) | Claude Help Center

Source: https://support.claude.com/en/articles/11139094-get-started-with-claude-for-education-at-your-university-for-owners-admins
Path: 13-Enterprise-Admin/get-started-with-claude-for-education-at-your-university-for-owners-admins.md

# Get started with Claude for Education at your university (for Owners/Admins)

August 6, 2026


This guide helps Admins, Owners, and Primary Owners set up and manage Claude for Education at universities. Account users can find more information in our **[FAQs for student](https://support.claude.com/en/articles/11139144-faqs-on-using-claude-for-education-at-your-university)**[, **faculty**, **and other users**](https://support.claude.com/en/articles/11139144-faqs-on-using-claude-for-education-at-your-university).

**K-12 educator?** Claude for Teachers is built for you. **[Get started](../18-Industry-UseCases/teachers.md)**.

------------------------------------------------------------------------

## Get started

Before setting up your Claude for Education account, review these essential resources:

- **[Important considerations before enabling single sign-on (SSO) and JIT/SCIM provisioning](https://support.claude.com/en/articles/10276682)**

- **[Set up single sign-on (SSO)](https://support.claude.com/en/articles/13132885-set-up-single-sign-on-sso)**

- **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195)**

Note the following information from **[Important considerations before enabling single sign-on (SSO) and JIT/SCIM provisioning](https://support.claude.com/en/articles/10276682)**:

When enforcing SSO on your Education account, there are two distinct categories to consider for existing users who are using their university email:

1.  Users with existing Free/Pro/Team accounts who ARE added to your SSO application

    - These users will maintain access to their existing Free/Pro/Team accounts

    - They will have the ability to toggle between the Education account and their previous accounts by clicking the profile icon with their initials in the bottom left corner  
      ​

2.  Users with existing Free/Pro/Team accounts who are NOT added to your SSO application

    - If "Enforce SSO for Claude.ai" is NOT enabled: These users can still access their existing accounts using the "Continue with email" option

    - If "Enforce SSO for Claude.ai" IS enabled: These users will be unable to access their existing Free/Pro/Team accounts

      - Please note, these accounts are not deleted, but can no longer be accessed as the user is unable to login via SSO.

## Enable and use Education features

After getting started with your plan, choose which Education features you’d like use and configure them in your account:

- **[Project visibility and sharing](../15-Claude-AI-Features/project-visibility-and-sharing.md)**

- **[Use connectors to extend Claude's capabilities](https://support.claude.com/en/articles/11176164)**

- **[Enable and use web search](https://support.claude.com/en/articles/10684626)**

Familiarize yourself with audit logs, data retention controls, and other administrative features:

- **[How to access audit logs](how-to-access-audit-logs.md)**

- **[Purchase and manage seats on Enterprise plans](https://support.claude.com/en/articles/13393991)**

- **[Configure custom data retention controls](https://support.claude.com/en/articles/10440198)**

- **[Manage user feedback settings](https://support.claude.com/en/articles/10504844)**

- **[How can I disable public projects?](../15-Claude-AI-Features/how-can-i-disable-public-projects.md)**

## Set up support paths

Anthropic Support assists Primary Owners and Owners directly. See **[How to get support](../15-Claude-AI-Features/how-to-get-support.md)** for more information on your support options.

Individual users should follow one of two paths to receive support:

1.  Self-serve with our **[Help Center](https://support.claude.com/en/)**.

2.  Escalate questions through university support channels.

We recommend setting up a documented support channel for any user escalations prior to going live at your university.

In addition, share the **[FAQs on using Claude for Education at your university](https://support.claude.com/en/articles/11139144-faqs-on-using-claude-for-education-at-your-university)** to help users get started successfully.

## Privacy and data retention

Visit our **[Privacy Center](https://privacy.claude.com/en/collections/10663361-commercial-customers)** for comprehensive information about your account's data retention and handling. The Privacy Center includes:

- FAQs on Anthropic's policies

- Terms of service details

- Privacy controls information

---

## Getting started with Claude Enterprise | Claude Help Center

Source: https://support.claude.com/en/articles/12166798-getting-started-with-claude-for-enterprise
Path: 13-Enterprise-Admin/getting-started-with-claude-for-enterprise.md

# Getting started with Claude Enterprise

Resources for new enterprise customers.

March 16, 2026


Welcome to Claude Enterprise! Our Customer Success team has put together the following guides to jumpstart your Claude for Enterprise journey with best practices, practical guidance, launch checklists, and more.

- [Getting started guide for admin setup](https://cdn.sanity.io/files/4zrzovbb/website/f445a7cc34957e7c145ffb2937af85916356e8da.pdf)

- [Getting started guide for end users](https://cdn.sanity.io/files/4zrzovbb/website/97c4b90b0af9ad8b8332980010157044d58b40ba.pdf)

If you're responsible for rolling out Claude to your organization, you may also like this [course](https://anthropic.skilljar.com/driving-enterprise-adoption-of-claude) in Anthropic Academy, designed for project owners like you!

## Additional support

Anthropic has services partners (advisory, training, change management, MCP development) available who can help provide additional support. If you'd like to discuss further, please submit [this interest form](https://forms.gle/ubnR5f7q5E71ECR79). For requests to connect with our Sales team, please contact [\[email protected\]](https://support.claude.com/cdn-cgi/l/email-protection#a0d3c1ccc5d3e0c1ced4c8d2cfd0c9c38ec3cfcd)

  
​

---

## Google Workspace SSO/SCIM email mismatch | Claude Help Center

Source: https://support.claude.com/en/articles/13917817-google-workspace-sso-scim-email-mismatch
Path: 13-Enterprise-Admin/google-workspace-sso-scim-email-mismatch.md

# Google Workspace SSO/SCIM email mismatch

March 24, 2026


Claude uses email as the primary identifier to match SSO logins to provisioned seats. In Google Workspace, SCIM auto-provisioning and SAML SSO can send different email values—especially when people have email aliases—causing a mismatch that blocks access.

**Applies to:** Enterprise plans and Console organizations using SCIM provisioning. Team plans don't have SCIM provisioning, so this mismatch scenario doesn't apply — see **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195)** for what's available on each plan.

------------------------------------------------------------------------

## Symptoms

People may experience one or more of the following when attempting to access your organization via SSO:

- **"Account creation is blocked"** — The person authenticates via SSO but Claude cannot find a matching provisioned account. If your organization has organization creation restricted (recommended), the person is blocked entirely.

- **Landing on a free personal account** — If organization creation is not restricted, the person bypasses your organization entirely and creates or lands on a free personal account instead of their provisioned seat.

- **"Please confirm your email" mismatch** — The SSO callback shows a different email than the one the person entered at login.

- **Claude Code authentication failure** — The Claude Code CLI shows an email mismatch error during the authentication flow, preventing the person from connecting to your organization.

------------------------------------------------------------------------

## How this happens

Google Workspace accounts have a primary email and may have multiple aliases. SCIM provisioning and SAML SSO are configured separately in the Google Admin console and can pull from different address fields:

[TABLE]

The most common mismatch: SCIM is configured to send an alias address while SAML sends the primary email (or vice versa). Since aliases and primary emails are different strings, Claude cannot match them. Claude requires an **exact string match**.

**Common confusion:** In Google Admin, SCIM auto-provisioning settings and SAML attribute mapping are on separate tabs within the same app. Admins sometimes update one and miss the other. Verify both locations.

------------------------------------------------------------------------

## Diagnostic steps

### Step 1 — Confirm the mismatch

1.  **Check the SCIM email:** In the **Google Admin console**, go to **Apps → Web and mobile apps → \[Claude app\] → Auto-provisioning**. Check which attribute is mapped to the email field being sent to Claude.

2.  **Check the SAML email:** In the same app, go to the **SAML attribute mapping** section. Find the attribute mapped to email and note its source.

3.  **Check a specific person:** In **Directory → Users → \[User\]**, compare the **Primary email** with any listed **Alias emails**.

### Step 2 — Identify the scope of the problem

- If **most or all** provisioned people share the same email format mismatch, this is a **systemic attribute mapping problem**. The fix is in your IdP's SCIM attribute mapping.

- If only **one or two people** are affected, the issue is likely specific to those accounts. Check their profile directly.

### Step 3 — Check Name ID in SAML configuration

1.  In the app settings, go to **SAML → Service provider details**.

2.  Confirm that the **Name ID** is set to **Basic Information → Primary email**.

3.  If Name ID is set to a custom schema field or alias, it may send a different value than SCIM.

------------------------------------------------------------------------

## Resolution

### Align both mappings to primaryEmail

Google Workspace's primaryEmail is the most reliable source for both SCIM and SAML.

1.  **Update SCIM provisioning:** In **Apps → Web and mobile apps → \[Claude app\] → Auto-provisioning → Attribute mapping**, ensure the email attribute maps to primaryEmail.

2.  **Update SAML Name ID:** In **SAML → Service provider details**, set **Name ID** to **Basic Information → Primary email**.

3.  **Update SAML attribute mapping:** In the **Attribute mapping** step, ensure the email attribute is mapped to **Basic Information → Primary email**.

4.  Save all changes.

### Trigger a full re-sync

**Critical — Full sync required:** An incremental sync will *not* update existing records after you change an attribute mapping. You must trigger a **full restart** of the provisioning cycle.

1.  In the Google Admin console, go to the app's **Auto-provisioning** settings.

2.  Temporarily suspend provisioning, then re-enable it. This triggers a full sync of everyone assigned.

3.  Alternatively, remove and re-add affected people from the app assignment to force their individual records to re-provision.

4.  Monitor the provisioning logs for errors and confirm emails updated to match the SAML format before asking people to retry.

------------------------------------------------------------------------

## Post-fix cleanup

After correcting the attribute mapping and completing the full sync:

- **Rogue free accounts:** If organization creation was not restricted before the fix, some people may have inadvertently created free personal Claude accounts. Contact **[our Support team](https://support.claude.com/en/articles/9015913)** to have these removed.

- **Ghost accounts (wrong-email seats):** The originally provisioned accounts with the incorrect email may still exist in your organization, occupying seats. Contact our Support team to deprovision these.

- **Seat availability:** If ghost accounts are occupying all contracted seats, new logins will fail with an out-of-seats error. Deprovisioning the ghost accounts frees those seats.

- **Re-adding affected people:** After ghost accounts are removed, people with the corrected email may need to be re-invited or re-provisioned.

- **Prevent future occurrences:** Enable "Restrict organization creation" in your organization's Identity and access settings. This prevents people who aren't yet provisioned from accidentally creating free personal accounts.

------------------------------------------------------------------------

## Verification

1.  Check a sample of provisioned people—confirm their email in the provisioning log matches the email format that SSO sends.

2.  Ask an affected person to clear browser cookies for claude.ai, then log in via SSO. They should land directly in your organization's workspace without any error.

3.  Confirm people aren't creating free accounts—with organization creation restricted, blocked people see a clear error rather than landing on a personal account.

4.  If Claude Code was affected, have the person re-run `claude auth login --enterprise` and confirm the email matches their provisioned seat.

------------------------------------------------------------------------

## Common issues

[TABLE]

------------------------------------------------------------------------

## When to contact Support

Contact **[our Support team](https://support.claude.com/en/articles/9015913)** with your organization's domain, the affected person's email, and screenshots of your attribute mappings when:

- SCIM and SSO attributes appear identical but people still cannot access their seats.

- You need confirmation of the email Claude recorded during SCIM provisioning for specific people.

- You need help cleaning up ghost accounts or rogue free orgs.

- People are hitting an out-of-seats error despite available contracted seats.

---

## Google Workspace SSO setup | Claude Help Center

Source: https://support.claude.com/en/articles/13917884-google-workspace-sso-setup
Path: 13-Enterprise-Admin/google-workspace-sso-setup.md

# Google Workspace SSO setup

August 6, 2026


This guide helps you configure Claude to use Google Workspace as your identity provider for SSO and automated user provisioning. It applies to Team plans, Enterprise plans, and Console organizations.

## Prerequisites

- A Claude Team plan, Enterprise plan, or Console organization with a parent organization (see **[Important considerations before enabling SSO](https://support.claude.com/en/articles/10276682)** for Console parent org requirements)

- Owner or Primary Owner role (Team and Enterprise) or Admin role (Console)

- Google Workspace Super Admin access

- Your domain verified in Claude's Identity and access settings—see **[Set up single sign-on](https://support.claude.com/en/articles/13132885)** for the full setup path including domain verification

## Where to find your configuration values

The ACS URL, Entity ID, and SCIM credentials referenced below are provided in the WorkOS setup flow within your Identity and access settings—not by contacting Support.

- **Team and Enterprise plans:** go to **[claude.ai/admin-settings/identity](https://claude.ai/admin-settings/identity)**

- **Console organizations:** go to **[platform.claude.com/settings/identity](https://platform.claude.com/settings/identity)**

Start the SSO setup flow there and keep it open alongside the Google Admin console as you work through the steps below.

------------------------------------------------------------------------

## Step 1 — Add a custom SAML app in Google Admin

1.  In the **Google Admin console**, go to **Apps → Web and mobile apps → Add app → Add custom SAML app**.

2.  Name it "Claude" and click "Next."

3.  Download the **IdP metadata** XML. You'll upload this in the WorkOS setup flow when prompted.

4.  Click "Next."

## Step 2 — Enter service provider details

1.  Enter the values from the WorkOS setup flow: **ACS URL** and **Entity ID**.

2.  Set **Name ID format** to **EMAIL** and **Name ID** to **Basic Information \> Primary email**.

3.  Click "Next."

## Step 3 — Configure attribute mapping

1.  In the **Attribute mapping** step, add: Google Directory attribute: **Primary email** → App attribute: email.

2.  Click "Finish."

## Step 4 — Enable auto-provisioning (SCIM)

**Note:** SCIM provisioning is available on Enterprise plans and eligible Console organizations only. If you're on a Team plan, skip this step—you can use JIT provisioning instead. See **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195)**.

1.  In the app's settings, go to the **Autoprovisioning** section and click "Configure autoprovisioning" (may require enabling in Google Workspace admin settings).

2.  Enter the **Endpoint URL** and **Access token** from the WorkOS setup flow.

3.  Map the primaryEmail attribute to the Claude email field.

4.  Enable provisioning and save.

**Critical:** Both SAML and SCIM must use primaryEmail. If someone has aliases, ensure their primary Google email matches what SCIM sends. For troubleshooting, see **[Google Workspace SSO/SCIM email mismatch](https://support.claude.com/en/articles/13917817)**.

## Step 5 — Assign the app to people or OUs

1.  In the app settings, click "User access."

2.  Enable the app for the relevant Organizational Units or groups.

3.  Click "Save."

## Step 6 — Verify

1.  If you enabled SCIM, check that provisioned people appear in your organization's member list.

2.  Have a test user log in via SSO and confirm they land in your organization's workspace.

------------------------------------------------------------------------

## Need help?

See **[Set up single sign-on](https://support.claude.com/en/articles/13132885)** for the full end-to-end flow including domain verification and choosing a provisioning approach. If you run into issues, contact **[our Support team](https://support.claude.com/en/articles/9015913)** with your organization's domain and a screenshot of your SAML configuration.

---

## Access the Compliance API | Claude Help Center

Source: https://support.claude.com/en/articles/13015708-how-can-i-access-the-compliance-api
Path: 13-Enterprise-Admin/how-can-i-access-the-compliance-api.md

# Access the Compliance API


The Compliance API lets your organization programmatically pull activity feed events, chat data, and file content across all your Claude deployments. Use it to monitor, audit, and meet compliance requirements with your own tools.

The Compliance API is available to Enterprise plan organizations and to Claude Platform customers in Claude chats. Organizations on Anthropic’s dedicated government deployments, such as Claude for Government, use **[a separate Compliance API](https://claude.com/docs/government/org-admin/compliance-api)**.

**Important:** For Claude Enterprise organizations, only your organization's Primary Owner can enable the Compliance API, from **[Organization settings \> API](https://claude.ai/admin-settings/api-access)**. You can create a key on the same page by clicking "+Create key" under **Keys**: the Primary Owner can create a key covering every linked organization, and Owners can create keys limited to their own organization. Owners see this page but not the **Compliance API** toggle—only the Primary Owner can turn the API on or off. Admins don't see the page at all. If your organization is linked to a parent organization, the parent organization's Primary Owner enables it and the setting applies to every linked organization.

## Compliance API technical documentation

For setup instructions, endpoints, and reference material, including how to enable the API and create access keys to start pulling data, see the **[Compliance API documentation](../04-API-Reference/Other/manage-claude-compliance-api.md)** on Claude Platform Docs.

## Compliance API security integrations

Security and compliance platforms have built integrations on top of the Claude Compliance API, so your team can monitor Claude activity within the tools you already use. Learn more about **[Compliance API integrations](../14-Connectors/get-started-with-claude-compliance-api-integrations.md)**.

## Audit log events in the Compliance API

The Compliance API now includes audit log events, giving you a full view across all your Claude deployments. To see which events are recorded via audit logs, see **[How to access audit logs](how-to-access-audit-logs.md#h_41cdad187a)**.

---

## Export your organization's data | Claude Help Center

Source: https://support.claude.com/en/articles/13346720-how-can-i-export-my-organization-s-data
Path: 13-Enterprise-Admin/how-can-i-export-my-organization-s-data.md

# Export your organization's data


Organization data exports are only available to Team and Enterprise plan Primary Owners. Data exports include conversation data and the user data for your organization. Primary Owners and Owners can also let users export their own data. Learn more in the **[Let users export their own data](#h_68f7c2a1e0)** section below.

If you're an individual Claude user on a Free, Pro, or Max plan looking to export your data, see **[Export your Claude data](https://support.claude.com/en/articles/9450526)**.

Primary Owners of Team and Enterprise plans can export organization data from **[Organization settings \> Data and privacy](https://claude.ai/admin-settings/data-privacy-controls)** on the web app or Claude Desktop.

Follow these steps to export your organization's data:

1.  Click on your initials in the lower left corner of your account.

2.  Select "Organization settings" from the menu.

3.  Click "Data and privacy" to open that section.

4.  Click on the "Export Data" button.

Once the export has been processed, you will receive a download link via email.

**Important:** Messages, files, and projects deleted from your account, either manually by individual users or via enterprise retention settings, will not be included in data exports initiated after the deletion.

Please note:

- There may be a small delay while the export is generated.

- The email will be sent to the address associated with your account.

- You must be signed in to your account to download your data using this link.

- The download link will expire 24 hours after delivery.

- If your link expires, you can always request a new one by repeating the export process.

If you're an Enterprise Primary Owner looking to export audit logs, see **[How to access audit logs](how-to-access-audit-logs.md)**.

------------------------------------------------------------------------

## Let users export their own data

Owners and Primary Owners can let users in your organization export their own chats without exporting the whole organization.

To turn this on:

1.  Navigate to **[Organization settings \> Data and privacy](https://claude.ai/admin-settings/data-privacy-controls)**.

2.  In the “Data management” section, turn on “Allow members to export their own data."

Once the setting is on, users export their data from **[Settings \> Privacy](https://claude.ai/settings/data-privacy-controls)** in Claude on the web or Claude Desktop. Exports aren’t available in Claude for iOS or Claude for Android. Each export covers the user’s own chats and doesn’t include the contents of uploaded files. Learn more about **[exporting your Claude data](https://support.claude.com/en/articles/9450526)**.

**Note:** Primary Owners don’t see the personal "Export data" button, even when this setting is on. These roles export the whole organization’s data using the steps above.

---

## How long do you store my organization’s data? | Claude Help Center

Source: https://support.claude.com/en/articles/7996866-how-long-do-you-store-personal-data
Path: 13-Enterprise-Admin/how-long-do-you-store-personal-data.md

# How long do you store my organization’s data?

July 1, 2026


*This article is about our commercial products such as Claude for Work and the Anthropic API. For our consumer products such as Claude Free, Pro, Max and when accounts from those plans use Claude Code, see* *[here](https://privacy.anthropic.com/en/articles/10023548-how-long-do-you-store-personal-data).*

## Standard Retention Timeframe

For Anthropic API users, we automatically delete inputs and outputs on our backend within 30 days of receipt or generation, except:

- When you use a service with longer retention under your control (e.g. Files API)

- When you and we have agreed otherwise (e.g. [zero data retention agreement](https://privacy.anthropic.com/en/articles/8956058-i-have-a-zero-retention-agreement-with-anthropic-what-products-does-it-apply-to))

- If we need to retain them for longer to enforce our [Usage Policy (UP)](https://www.anthropic.com/legal/aup)

- In compliance with the law

If you use our other commercial products (e.g. Claude for Work, Claude for Enterprise, Anthropic Console, etc.) that allow you to save and continue conversations with Claude, we retain your chats and coding sessions in the product to provide you with a consistent product experience.

You control your conversations and can [delete your chats](https://privacy.anthropic.com/en/articles/8230524-how-can-i-delete-or-rename-a-conversation) anytime from your dashboard. When you delete a conversation it’s:

- Removed from your chat history immediately

- Deleted from our back-end storage systems within 30 days

Incognito chats are automatically deleted within 30 days unless flagged as a Usage Policy violation. [Learn more](../15-Claude-AI-Features/using-incognito-chats.md) about Incognito chats for your organization.

## **Covered Models**

Additionally, for Covered Models, we require limited data retention and review as a part of our safety work. To learn more about Covered Models and these requirements, see [here](../20-Models/data-retention-practices-for-covered-models-claude-help-center.md).

## Usage Policy Violations

We retain inputs and outputs for up to 2 years and trust and safety classification scores for up to 7 years if your chat is flagged by our automated trust and safety systems as violating our [Usage Policy](https://www.anthropic.com/legal/aup).

In all cases, we may retain your chats or sessions as required by law or as necessary to combat violations of our [Usage Policy](https://www.anthropic.com/legal/aup). If permitted in your contract with us, we may anonymize your organization’s data for research or statistical purposes, in which case we may retain this information for longer.

## Feedback Data

Where you have provided feedback to us (e.g. by submitting feedback through our thumbs up/down button or sent bug reports), we retain data associated with that submission for 5 years. [Learn more](https://privacy.anthropic.com/en/articles/7996868-is-my-data-used-for-model-training).

In all cases, we may retain your chats or sessions as required by law or as necessary to combat violations of our [Usage Policy](https://www.anthropic.com/legal/aup). If permitted in your contract with us, we may anonymize your organization’s data for research or statistical purposes, in which case we may retain this information for longer.

For more information about our privacy practices, please see our [Trust Center](https://trust.anthropic.com/) and [Data Processing Addendum](https://anthropic.com/legal/data-processing-addendum).

---

## How SCIM sync works for Enterprise organizations | Claude Help Center

Source: https://support.claude.com/en/articles/14499648-how-scim-sync-works-for-enterprise-organizations
Path: 13-Enterprise-Admin/how-scim-sync-works-for-enterprise-organizations.md

# How SCIM sync works for Enterprise organizations

September 2, 2026


SCIM provisioning keeps your Enterprise organization's membership and groups in sync with your identity provider. This article covers what gets synced, how syncs are triggered, how to preview a sync before it applies, and what to watch for when resyncing.

Available on the Enterprise plan. For setup instructions, see **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195-set-up-jit-or-scim-provisioning)**.

## What gets synced

When you connect your identity provider (IdP) to your Enterprise organization through the WorkOS integration, two things sync from your IdP:

- **Members** from your SCIM directory

- **SCIM groups** that you've pushed from your IdP to WorkOS

Group membership in your organization determines which capabilities members with custom roles can access, along with group spend limits.

## Automatic syncing

Your Enterprise organization receives changes from your IdP automatically whenever your IdP pushes member or group updates (adds, removes, or edits) to WorkOS.

Behind the scenes, your organization polls WorkOS for update events every minute and processes them in a queue. This method is eventually consistent—syncs typically complete within minutes, but can take several hours during periods of high system traffic.

## Manual syncing

Some actions trigger a manual sync immediately, and you can also run one on demand.

### Actions that trigger a manual sync

- **Changing the provisioning mode to SCIM.** Any member not in your IdP directory is removed, and any member in your IdP directory is added. You'll need to wait for this initial resync before configuring group mappings.

- **Enabling group mappings.** The full list of members and groups updates. New and existing members' roles and seat tiers are enforced by the group mapping and can't be overridden manually. After saving a new mapping, click "Sync."

- **Sync now** to recompute roles and seat tiers for existing members.

  - Using the "Check for updates" button at **[Organization settings \> Groups](https://claude.ai/admin-settings/groups)**.

  - Using the "Sync" button at **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)** under **User provisioning**.

**Note:** When you update seat provisioning or seat roles through group mappings, existing members aren't resynced automatically. Trigger a manual sync to apply the changes.

When you disable group mappings, you regain the ability to manually assign member roles and seat tiers. Existing members keep their current roles. New members are assigned the User role—change their role to "Custom" if you want to enable custom role permissions.

## How to manually trigger a sync

You can trigger a manual sync from two places in your admin settings.

**From the Groups page**

1.  Go to **[Organization settings \> Groups](https://claude.ai/admin-settings/groups)**.

2.  Click "Check for updates" under **SCIM sync**:


3.  Select whether to sync members, groups, or both.

**From the Manage SCIM page**

1.  Go to **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)** and scroll to the **User provisioning** section.

2.  Click "Sync."

3.  Select whether to sync members, groups, or both:


**Note:** If you trigger a manual sync while background changes are processing, your organization takes the most recent change for each member or group. If multiple changes are queued for the same member or group, you may need to resync again to make sure everything applies correctly.

## Preview a sync before it applies

When you run a sync—by resyncing manually or saving a mapping change—the admin console shows a preview of what the sync will do before anything applies. The preview appears automatically in the sync flow and shows how many members the sync will add, remove, or modify, along with how recent the directory data is.

Review the preview before you confirm. If the numbers don't match what you expect (for example, far more removals than people who actually left) cancel the sync and check your IdP directory and group mappings before continuing.

If a sync would remove an unusually large share of your members, the preview shows a warning. This helps you catch a misconfigured mapping before it removes people who should keep access. When you see this warning, stop and confirm the change is intentional before continuing.

## Member sync vs. group sync

When you trigger a manual sync, you can choose to sync members, groups, or both. Here's what each does:

- **Member sync** updates your organization's record of which members are mapped to seats, seat tiers, and seat roles (Custom, User, Admin, Owner). This can affect members' login access to Claude.

- **Group sync** updates your organization's record of which SCIM groups exist and who belongs to them. Group membership determines which capabilities members with custom roles can use, along with group spend limits.

## How long manual syncs take

Manual syncs rescan WorkOS for the full list of members and groups to establish an up-to-date baseline. Expect roughly one minute per 100 members in your organization—so a 1,000-member organization takes about 10 minutes to fully resync.

## Verifying your sync status

To check whether your organization's membership and groups are current, you have two options:

- **Export your member list.** Go to **[Organization settings \> Members](https://claude.ai/admin-settings/members)** and click "Export CSV" to download the current view of your membership.

- **View the WorkOS integration's record.** Go to **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)** and click "Manage SCIM" to see what WorkOS currently holds for your organization.

## Risks to watch for when resyncing

Before you trigger a manual resync, keep these in mind:

- **Changing the provisioning mode to SCIM removes members not in your IdP directory.** Confirm that all existing members are in your IdP directory before changing provisioning mode.

- **Updating a member's email creates a new member record.** The member with the old email is removed, and a new member with the new email is created.

- **Resyncing cascades to child organizations.** If you have multiple organizations with SCIM provisioning under the same **parent organization**, resyncing one triggers resyncing in the others. This includes sandbox organizations sharing the same parent.

- **Incomplete role mappings remove members from the organization.** When enabling group mapping for SCIM, finish assigning all groups before saving. Any member not included in a role group mapping is removed from the organization. Seat tier mappings work differently: a member who isn’t in any seat tier group isn’t removed. Existing members keep their current seat type, and newly provisioned members receive the highest seat type that still has an unassigned purchased seat, then the next seat type down, and are added with no seat if none is available.

---

## Access audit logs | Claude Help Center

Source: https://support.claude.com/en/articles/9970975-how-to-access-audit-logs
Path: 13-Enterprise-Admin/how-to-access-audit-logs.md

# Access audit logs

June 15, 2026


Audit logs are available for Enterprise organizations only.

Organization Owners and Primary Owners can export data within **[Organization settings \> Data and Privacy](https://claude.ai/admin-settings/data-privacy-controls)** and clicking the "Export logs" button. Upon triggering this export, all audit logs for the organization within the past 180 days will be aggregated. The Owner who requested the export will receive an email containing a download link, which is active for 24 hours. Note that there may be a delay between triggering the export and receiving the email as logs are aggregated.

Please note that title and content of chats and projects are not available to be exported in audit logs (only their unique identifiers will be exported). However, chat inputs/outputs will be exportable by Primary Owners via **[data exports](../22-Safety-Policy/how-can-i-export-my-claude-data.md)**.

**Note:** You can’t export data using the "Export logs" button if your organization uses customer-managed encryption keys on an Enterprise plan. Audit log events are available in the Compliance API. For more information, refer to **[Access the Compliance API](https://support.claude.com/en/articles/13015708-access-the-compliance-api)**.

## Log Structure

Audit logs consist of the following columns:

[TABLE]

## Entities

The entities that can be affected by events. Entity info follows this structure:

[TABLE]

[TABLE]

## Recorded Events

[TABLE]

---

## Authentication - Claude Code Docs

Source: https://code.claude.com/docs/en/iam
Path: 13-Enterprise-Admin/iam.md

## On this page

- [Log in to Claude Code](#log-in-to-claude-code)
  - [Log in with multiple accounts](#log-in-with-multiple-accounts)
- [Set up team authentication](#set-up-team-authentication)
  - [Claude for Teams or Enterprise](#claude-for-teams-or-enterprise)
  - [Claude Console authentication](#claude-console-authentication)
  - [Sign in without an API key](#sign-in-without-an-api-key)
  - [Cloud provider authentication](#cloud-provider-authentication)
  - [Restrict login to your organization](#restrict-login-to-your-organization)
  - [Restrict which API providers a machine may use](#restrict-which-api-providers-a-machine-may-use)
- [Credential management](#credential-management)
  - [Renew an expiring login](#renew-an-expiring-login)
  - [Authentication precedence](#authentication-precedence)
  - [Anthropic profiles and federation credentials](#anthropic-profiles-and-federation-credentials)
  - [Generate a long-lived token](#generate-a-long-lived-token)

Setup and access

# Authentication

Copy pageCopy page

Log in to Claude Code and configure authentication for individuals, teams, and organizations.

Copy pageCopy page

Claude Code supports multiple authentication methods depending on your setup. Individual users can log in with a claude.ai account, while teams can use Claude for Teams or Enterprise, the Claude Console, or a cloud provider like Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry.


[​](#log-in-to-claude-code)

Log in to Claude Code

After [installing Claude Code](../01-Getting-Started/setup.md#install-claude-code), run `claude` in your terminal. On first launch, Claude Code opens a browser window for you to log in. If you’ve set the `ANTHROPIC_API_KEY` environment variable, Claude Code skips the login prompt and asks you to approve the key instead. If the browser doesn’t open automatically, press `c` to copy the login URL to your clipboard, then paste it into your browser. If your browser shows a login code instead of redirecting back after you sign in, paste it into the terminal at the `Paste code here if prompted` prompt. This happens when the browser can’t reach Claude Code’s local callback server, which is common in WSL2, SSH sessions, and containers. When login completes, the terminal shows `Login successful` and prompts you to press `Enter` to continue. You can authenticate with any of these account types:

- **Claude Pro or Max subscription**: log in with your claude.ai account. Subscribe at [claude.com/pricing](../17-Billing-Plans/pricing.md).
- **Claude for Teams or Enterprise**: log in with the claude.ai account your team admin invited you to.
- **Claude Console**: log in with your Console credentials. Your admin must have [invited you](#claude-console-authentication) first. You can sign in with or without [creating an API key](#sign-in-without-an-api-key).
- **Cloud providers**: if your organization uses [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md), [Google Cloud’s Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md), or [Microsoft Foundry](../02-Claude-Code-CLI/microsoft-foundry.md), set the required environment variables before running `claude`, or select **3rd-party platform** at the login prompt, which launches an interactive setup wizard for Bedrock and Vertex AI. No browser login is needed.
- **Cloud gateway**: if your organization runs a self-hosted [Claude apps gateway](claude-apps-gateway.md), sign in with corporate SSO through `/login`. The gateway-issued token is the session’s only credential.

Admins can direct which login method developers use and require claude.ai logins to belong to a specific organization; see [Restrict login to your organization](#restrict-login-to-your-organization). To log out and re-authenticate, type `/logout` at the Claude Code prompt. Logging out also resets your first-launch setup state, so the next time you run `claude` it walks you through login and setup again. If you’re having trouble logging in, see [authentication troubleshooting](../02-Claude-Code-CLI/troubleshoot-install.md#login-and-authentication).


[​](#log-in-with-multiple-accounts)

Log in with multiple accounts

To stay signed in to multiple accounts at once, such as work and personal accounts, give each account its own configuration directory. When you start `claude`, set the [`CLAUDE_CONFIG_DIR`](../02-Claude-Code-CLI/env-vars.md#variables) environment variable to the directory for the account you want to use. Each directory has its own settings, session history, and claude.ai login or API key. For example, in Bash or Zsh, add this alias to `~/.bashrc` or `~/.zshrc` so that `claude-work` uses your work account while `claude` keeps your personal one:

```python
alias claude-work='CLAUDE_CONFIG_DIR=~/.claude-work claude'
```

After you open a new terminal and run `claude-work` for the first time, Claude Code walks you through login and setup for the new directory. Separate directories don’t keep two Claude Console sign-ins [without an API key](#sign-in-without-an-api-key) apart, because Claude Code stores that kind of sign-in outside the configuration directory.


[​](#set-up-team-authentication)

Set up team authentication

For teams and organizations, you can configure Claude Code access in one of these ways:

- [Claude for Teams or Enterprise](#claude-for-teams-or-enterprise), recommended for most teams
- [Claude Console](#claude-console-authentication)
- [Claude apps gateway](claude-apps-gateway.md), a self-hosted gateway that signs developers in with your IdP and routes inference to the cloud provider you configure
- [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md)
- [Google Cloud’s Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md)
- [Microsoft Foundry](../02-Claude-Code-CLI/microsoft-foundry.md)


[​](#claude-for-teams-or-enterprise)

Claude for Teams or Enterprise

[Claude for Teams](../17-Billing-Plans/pricing.md#team-&-enterprise) and [Claude for Enterprise](https://anthropic.com/contact-sales?utm_source=claude_code&utm_medium=docs&utm_content=authentication_enterprise) provide the best experience for organizations using Claude Code. Team members get access to both Claude Code and Claude on the web with centralized billing and team management.

- **Claude for Teams**: self-service plan with collaboration features, admin tools, SSO, billing management, and [server-managed settings](server-managed-settings.md) for organization-wide Claude Code configuration. Best for smaller teams.
- **Claude for Enterprise**: adds domain capture, role-based permissions, and the compliance API. Best for larger organizations with security and compliance requirements.

1

Subscribe

Subscribe to [Claude for Teams](../17-Billing-Plans/pricing.md#team-&-enterprise) or contact sales for [Claude for Enterprise](https://anthropic.com/contact-sales?utm_source=claude_code&utm_medium=docs&utm_content=authentication_enterprise_step).

2

Invite team members

Invite team members from the admin dashboard.

3

Install and log in

Team members install Claude Code and log in with their claude.ai accounts.


[​](#claude-console-authentication)

Claude Console authentication

For organizations that prefer API-based billing, you can set up access through the Claude Console.

1

Create or use a Console account

Use your existing Claude Console account or create a new one.

2

Add users

You can add users through either method:

- Bulk invite users from within the Console: Settings -\> Members -\> Invite
- [Set up SSO](../21-Account-Support/setting-up-single-sign-on-sso.md)

3

Assign roles

When inviting users, assign one of:

- **Claude Code** role: users can only create Claude Code API keys
- **Developer** role: users can create any kind of API key

4

Users complete setup

Each invited user needs to:

- Accept the Console invite
- [Check system requirements](../01-Getting-Started/setup.md#system-requirements)
- [Install Claude Code](../01-Getting-Started/setup.md#install-claude-code)
- Log in with Console account credentials


[​](#sign-in-without-an-api-key)

Sign in without an API key

You can sign in to your Console account without creating an API key, even when your organization doesn’t let developers create them. Choose the Anthropic Console account at the `/login` prompt and Claude Code asks how you want to sign in. Requires Claude Code v2.1.242 or later. Both routes sign you in to Console in the browser and differ in what Claude Code stores afterwards:

- **Sign in with your Console account**, labeled `(recommended)`: Claude Code keeps the OAuth token from that sign-in and stores it as an [Anthropic profile](#anthropic-profiles-and-federation-credentials). It creates no API key
- **Create an API key**, labeled `(legacy)`: Claude Code creates a Console API key for you and stores it with your other credentials

In practice, the profile stores an OAuth login while an API key is a static credential: Claude Code refreshes the profile’s login automatically, and when refresh fails, requests fail with [Anthropic profile login expired](../02-Claude-Code-CLI/errors.md#anthropic-profile-login-expired) until you sign in again. You don’t get the choice on every machine. Claude Code creates an API key without asking in these cases:

- You run against a cloud provider, such as [Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry](../02-Claude-Code-CLI/third-party-integrations.md) or [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md)
- Any settings file sets [`forceLoginOrgUUID`](#restrict-login-to-your-organization), or sets `forceLoginMethod` to `"claudeai"` or `"console"`
- A managed settings source on your machine, such as the managed settings file, an MDM profile, or the cached server-managed settings, exists but Claude Code [can’t read it](managed-settings.md#invalid-entries-in-managed-settings) and no other managed source supplies a policy

Unset `ANTHROPIC_API_KEY` before you sign in without a key. After you sign in without a key, you have a profile instead of a stored API key:

- **Which profile it writes**: Claude Code writes the profile named by `ANTHROPIC_PROFILE`, or your active profile, or `default`. If that profile is a federation profile, Claude Code refuses the sign-in instead of overwriting it
- **What it signs you out of**: Claude Code signs you out of any claude.ai login stored on the machine
- **How to undo it**: run `/logout`, which removes and revokes the credential this sign-in wrote

If your organization uses [server-managed settings](server-managed-settings.md), they apply to this sign-in on Claude Code v2.1.257 or later. Everything else about profiles applies to this sign-in, including where it ranks against your other credentials, the `Profile` row you get in `/status`, and the features that need a claude.ai login. See [Anthropic profiles and federation credentials](#anthropic-profiles-and-federation-credentials).


[​](#cloud-provider-authentication)

Cloud provider authentication

For teams using Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry:

1

Follow provider setup

Follow the [Amazon Bedrock docs](../02-Claude-Code-CLI/amazon-bedrock.md), [Google Cloud’s Agent Platform docs](../02-Claude-Code-CLI/google-vertex-ai.md), or [Microsoft Foundry docs](../02-Claude-Code-CLI/microsoft-foundry.md).

2

Distribute configuration

Distribute the environment variables and instructions for generating cloud credentials to your users. Read more about how to [manage configuration here](../02-Claude-Code-CLI/settings.md).

3

Install Claude Code

Users can [install Claude Code](../01-Getting-Started/setup.md#install-claude-code).


[​](#restrict-login-to-your-organization)

Restrict login to your organization

To require that developers’ claude.ai logins belong to a specific Anthropic organization, set [`forceLoginMethod`](../02-Claude-Code-CLI/settings-reference.md#forceloginmethod) and [`forceLoginOrgUUID`](../02-Claude-Code-CLI/settings-reference.md#forceloginorguuid) in [managed settings](managed-settings.md). Set `forceLoginOrgUUID` to your organization ID, shown in [claude.ai admin settings](https://claude.ai/admin-settings/organization) for Claude for Teams or Enterprise organizations. Claude Code reports an error for a claude.ai login to any other organization and exits at startup if the claude.ai credential in use belongs to an organization that isn’t listed. For Claude Console logins, Claude Code uses `forceLoginOrgUUID` to pre-select the organization on the Console sign-in page when you set it to a single Console organization ID, shown at [platform.claude.com/settings/organization](../04-API-Reference/Other/playground.md). It doesn’t check which organization the resulting Console credential belongs to, at login or at startup. A developer who logged in with a Console account before you deployed the keys stays logged in, and that saved key is blocked on a machine that also requires the [gateway](claude-apps-gateway.md) sign-in or in a session that selects a cloud provider. If you set `forceLoginOrgUUID` in any settings file, Claude Code stops offering the [keyless Console sign-in](#sign-in-without-an-api-key) in the sessions that file applies to and creates an API key instead. To direct developers to claude.ai sign-in instead, set `forceLoginMethod` to `"claudeai"`. On Claude Code v2.1.212 or later, every login path listed here applies `forceLoginMethod`. On the terminal’s interactive login screen, reached by `/login` or first-run onboarding, Claude Code pre-selects a `claudeai` or `console` method without enforcing it, so even with `forceLoginMethod` set to `"claudeai"`, a developer can still complete a Console login there. The paths differ on `forceLoginOrgUUID`:

- **Terminal, [VS Code extension](../03-IDE-Integrations/vs-code.md), and Agent SDK logins**: verify `forceLoginOrgUUID` for claude.ai account logins
- **`claude setup-token` and `/install-github-app`**: enforce only `forceLoginMethod`, so they can mint a token in a different organization
- **[Gateway](claude-apps-gateway.md) sign-in**: selected by `forceLoginMethod: "gateway"` rather than restricted by it, and doesn’t authenticate against an Anthropic organization, so `forceLoginOrgUUID` doesn’t apply; use your gateway identity provider to restrict access

Deploy the keys through your device management tooling. [Server-managed settings](server-managed-settings.md) reach only accounts that are already authenticated into your organization, so they can’t redirect a developer’s first login. If your organization distributes server-managed settings as well, set the keys in both places: managed-settings sources [don’t merge](server-managed-settings.md#settings-precedence), and cached server-managed settings replace the device-managed file apart from a few [per-key exceptions](server-managed-settings.md#per-key-exceptions-across-managed-sources). `forceLoginOrgUUID` and the `"claudeai"` and `"console"` values of `forceLoginMethod` aren’t among those exceptions, so keep them in both places. In a [gateway](claude-apps-gateway.md) deployment, also keep `forceLoginMethod` and `forceLoginOrgUUID` out of the [settings the gateway serves](claude-apps-gateway-config.md#managed). The keys also decide whether a session that doesn’t use a login credential can start. See [`forceLoginOrgUUID`](../02-Claude-Code-CLI/settings-reference.md#forceloginorguuid) in the settings reference for the full behavior.

- **`ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `apiKeyHelper`**: blocked at startup. Under `forceLoginOrgUUID`, organization membership can’t be verified for an environment credential, and under `forceLoginMethod` the credential would stand in for the required sign-in. When the managed settings also require the [gateway](claude-apps-gateway.md) sign-in, Claude Code blocks an API key saved by an earlier Claude Console login the same way. See [Administrator policy requires a Cloud gateway sign-in](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in)
- **Cloud provider sessions such as Amazon Bedrock**: blocked only while an `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `apiKeyHelper` credential, or an API key saved by an earlier Claude Console login, is still present on the machine. Remove it and the session starts. These sessions authenticate against your cloud provider, whose access policies govern them
- **[Anthropic profile or federation credentials](#anthropic-profiles-and-federation-credentials)**: not blocked unless an `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `apiKeyHelper` credential, or an API key saved by an earlier Claude Console login, is also present on the machine. The keys don’t check which organization the profile belongs to


[​](#restrict-which-api-providers-a-machine-may-use)

Restrict which API providers a machine may use

[`allowedProviders`](../02-Claude-Code-CLI/settings-reference.md#allowedproviders) in [managed settings](managed-settings.md) lists which services a managed machine may reach Claude through, such as the Anthropic API, Amazon Bedrock, or an LLM gateway. It complements `forceLoginMethod` and `forceLoginOrgUUID`, which govern which account a session uses when it talks to Anthropic. Requires Claude Code v2.1.285 or later.

managed-settings.json

```python
{
  "forceLoginMethod": "claudeai",
  "forceLoginOrgUUID": ["xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"],
  "allowedProviders": ["anthropic", "bedrock"]
}
```

With this file, a developer signed in to your claude.ai organization or configured for Amazon Bedrock starts normally. A session set up for any other provider is refused at startup, and a running session that switches to one is refused on its next request. [Managed settings don’t allow this API provider](../02-Claude-Code-CLI/errors.md#managed-settings-dont-allow-this-api-provider) shows each message.

- **Allow an LLM gateway or proxy**: list `"customEndpoint"` and set the gateway’s URL in the managed `env` block of the same source. The [settings reference](../02-Claude-Code-CLI/settings-reference.md#allowedproviders) lists every value and says which endpoint variables need a managed `env` pin.
- **Deploy on managed machines**: put the list in the managed source that carries the rest of your policy. The entry’s [Scope note](../02-Claude-Code-CLI/settings-reference.md#allowedproviders) says how a server-managed list combines with it.
- **Server-managed settings only**: a list you set only in [server-managed settings](server-managed-settings.md) reaches only sessions that fetch your organization’s settings, so treat it as a convenience for machines you can’t reach with device management, not as enforcement. [Platform availability](server-managed-settings.md#platform-availability) lists which sessions fetch them.


[​](#credential-management)

Credential management

Claude Code securely manages your authentication credentials:

- **Storage location**:
  - On macOS, credentials are stored in the encrypted macOS Keychain. When the Keychain rejects the write, such as when it’s locked in an SSH session, Claude Code stores your login in `~/.claude/.credentials.json` with file mode `0600` instead, the same storage it uses on Linux. A Console login that creates an API key fails until the Keychain is writable. To move your login back into the Keychain, follow [the recovery steps](../02-Claude-Code-CLI/troubleshoot-install.md#not-logged-in-or-token-expired).
  - On Linux, credentials are stored in `~/.claude/.credentials.json` with file mode `0600`.
  - On Windows, credentials are stored in `%USERPROFILE%\.claude\.credentials.json` and inherit the access controls of your user profile directory, which restricts the file to your user account by default.
  - If you’ve set the `CLAUDE_CONFIG_DIR` environment variable, Claude Code keeps the `.credentials.json` file under that directory instead, including the file the macOS fallback writes, and keys the macOS Keychain entry to that directory too, so a session with a different `CLAUDE_CONFIG_DIR` reads a different entry.
  - Claude Code manages `.credentials.json` through `/login` and `/logout`. To route requests through a custom API endpoint, set the [`ANTHROPIC_BASE_URL`](../02-Claude-Code-CLI/env-vars.md) environment variable instead.
- **Supported authentication types**: claude.ai credentials, Claude API credentials, Microsoft Foundry Auth, Bedrock Auth, Vertex Auth, Anthropic profile and [Workload Identity Federation](../04-API-Reference/Other/manage-claude-workload-identity-federation.md) credentials, and [Claude apps gateway](claude-apps-gateway.md) session tokens.
- **Custom credential scripts**: configure the [`apiKeyHelper`](../02-Claude-Code-CLI/settings-reference.md#apikeyhelper) setting to run a shell script that returns an API key.
- **Refresh intervals**: see [`apiKeyHelper`](../02-Claude-Code-CLI/settings-reference.md#apikeyhelper) for the cases in which Claude Code re-runs the helper.
- **Slow helper notice**: if `apiKeyHelper` takes longer than 10 seconds to return a key, Claude Code displays a warning notice in the prompt bar showing the elapsed time. If you see this notice regularly, check whether your credential script can be optimized.
- **Helper failures**: when the script exits with an error, times out, or prints nothing, requests fail with [`Your apiKeyHelper script is failing`](../02-Claude-Code-CLI/errors.md#your-apikeyhelper-script-is-failing) within three attempts.

`apiKeyHelper`, `ANTHROPIC_API_KEY`, and `ANTHROPIC_AUTH_TOKEN` apply to the CLI and the surfaces that wrap it, including the VS Code extension, the Agent SDK, and GitHub Actions. Claude Desktop and cloud sessions do not call `apiKeyHelper` or read these environment variables: they use OAuth, except desktop sessions running a [third-party inference configuration](llm-gateway-connect.md#desktop-app), which authenticate with that configuration’s credential.


[​](#renew-an-expiring-login)

Renew an expiring login

When the login you created with `/login` is within three days of expiring, Claude Code shows a warning at startup: `Your login expires in 3 days · run /login to renew`. Run `/login` to renew. The warning is informational and never blocks a request: authentication keeps working until the login actually expires. Once the stored login expires and can’t be refreshed, each model request fails with [`Login expired · Please run /login`](../02-Claude-Code-CLI/errors.md#login-expired) until you sign in again. You can check for this state before a request fails: [`/status`](../02-Claude-Code-CLI/commands.md) shows a `Login` row reading `Expired — log in again`, plus the organization and email it has saved for the expired login. The row appears only when the saved claude.ai login is the active credential. The row requires Claude Code v2.1.210 or later. The warning appears only when a claude.ai login is the active credential, and not when a cloud provider, `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `apiKeyHelper` supplies the credential. Renewing early matters most for sessions that run unattended. A [background session in agent view](../09-Agents-Patterns/agent-view.md) or a [Remote Control](../02-Claude-Code-CLI/remote-control.md) session that outlives the login stops making progress once the credential expires and can’t recover until you sign in again.


[​](#authentication-precedence)

Authentication precedence

When multiple credentials are present, Claude Code chooses one in this order:

1.  Cloud provider credentials, when `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, or `CLAUDE_CODE_USE_FOUNDRY` is set. See [third-party integrations](../02-Claude-Code-CLI/third-party-integrations.md) for setup.
2.  `ANTHROPIC_AUTH_TOKEN` environment variable. Sent as the `Authorization: Bearer` header. Use this when routing through an [LLM gateway or proxy](llm-gateway.md) that authenticates with bearer tokens rather than Anthropic API keys.
3.  `ANTHROPIC_API_KEY` environment variable. Sent as the `X-Api-Key` header. Use this for direct Anthropic API access with a key from the [Claude Console](../04-API-Reference/Other/playground.md). In interactive mode, you are prompted once to approve or decline the key, and your choice is remembered. To change it later, use the “Use custom API key” toggle in `/config`. The toggle only appears while `ANTHROPIC_API_KEY` is set in your environment. In non-interactive mode (`-p`), the key is always used when present.
4.  [`apiKeyHelper`](../02-Claude-Code-CLI/settings-reference.md#apikeyhelper) script output. Use this for dynamic or rotating credentials, such as short-lived tokens fetched from a vault.
5.  `CLAUDE_CODE_OAUTH_TOKEN` environment variable. A long-lived OAuth token generated by [`claude setup-token`](#generate-a-long-lived-token). Use this for CI pipelines and scripts where browser login isn’t available. If you run `/login` while the variable is set, Claude Code switches the current session to the new login, but reads the variable again in every new session until you remove it from your shell profile or the `env` block of a [settings file](../02-Claude-Code-CLI/settings.md).
6.  Anthropic profile and federation credentials, the credentials that the `ant` CLI and Workload Identity Federation use. A profile that `ant auth login` wrote ranks here only when you name it in `ANTHROPIC_PROFILE`; otherwise it ranks below `/login`. See [Anthropic profiles and federation credentials](#anthropic-profiles-and-federation-credentials).
7.  Subscription OAuth credentials from `/login`. This is the default for Claude Pro, Max, Team, and Enterprise users.

A signed-in [Claude apps gateway](claude-apps-gateway.md) session sits outside this list: it is a provider selection like Amazon Bedrock or Google Cloud’s Agent Platform, and it outranks them. When a gateway session exists, the CLI authenticates with the gateway token even if `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, or `CLAUDE_CODE_USE_FOUNDRY` is set, and credential sources above such as the bearer token, API key, `apiKeyHelper`, and profiles are not used. If your machine’s [managed settings](managed-settings.md) set [`forceLoginMethod`](../02-Claude-Code-CLI/settings-reference.md#forceloginmethod) to `"gateway"` or set [`forceLoginGatewayUrl`](../02-Claude-Code-CLI/settings-reference.md#forcelogingatewayurl), and you don’t select a cloud provider through a variable such as `CLAUDE_CODE_USE_BEDROCK` or `CLAUDE_CODE_USE_VERTEX`, your session uses only the gateway sign-in. Claude Code skips the other credential sources and asks you to sign in with `/login`. See [Administrator policy requires a Cloud gateway sign-in](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in) for what you see with each leftover credential. Requires Claude Code v2.1.261 or later, or v2.1.265 or later on a machine that sets only `forceLoginGatewayUrl`. If you have an active Claude subscription but also have `ANTHROPIC_API_KEY` set in your environment, Claude Code uses the API key once you approve it. This can cause authentication failures if the key belongs to a disabled or expired organization. Run `unset ANTHROPIC_API_KEY` to fall back to your subscription, and check `/status` to confirm which method is active. When a login and an API key are both configured, `/status` marks the credential that isn’t in use. [Cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) always use your subscription credentials. If you set `ANTHROPIC_API_KEY` or `ANTHROPIC_AUTH_TOKEN` in the cloud environment, it doesn’t override your subscription credentials.


[​](#anthropic-profiles-and-federation-credentials)

Anthropic profiles and federation credentials

A profile is a named credential configuration file in your [Anthropic configuration directory](../04-API-Reference/Other/manage-claude-wif-reference.md#configuration-directory), by default `~/.config/anthropic` on macOS and Linux or `%APPDATA%\Anthropic` on Windows. A profile’s auth mode is `oidc_federation` when you set it up for [Workload Identity Federation (WIF)](../04-API-Reference/Other/manage-claude-workload-identity-federation.md) or `user_oauth` when [`ant auth login`](../04-API-Reference/Other/cli-sdks-libraries-cli-authentication.md) wrote it or you [signed in to a Console account without an API key](#sign-in-without-an-api-key). Claude Code doesn’t read profiles or federation variables in [bare mode](../02-Claude-Code-CLI/headless.md#start-faster-with-bare-mode), in Claude Desktop, or in cloud sessions. In those sessions, `/status` shows no `Profile` row. Claude Code checks three sources in this order and stops at the first one that is set. The table shows what sets each source and where it ranks against your `/login` credential.

| Source               | Set by                                                                                                                                                                   | Rank against `/login`                                                                                                 |
|:---------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------|
| Named profile        | `ANTHROPIC_PROFILE`                                                                                                                                                      | Above, whichever auth mode the profile has                                                                            |
| Federation variables | `ANTHROPIC_FEDERATION_RULE_ID` and `ANTHROPIC_ORGANIZATION_ID`, both set                                                                                                 | Above                                                                                                                 |
| Active profile       | The [`active_config` file](../04-API-Reference/Other/manage-claude-wif-reference.md#active-profile) in your configuration directory, or a profile named `default` | Above when its auth mode is `oidc_federation`; below a working `/login` credential when its auth mode is `user_oauth` |

For the federation variables, Claude Code also reads the other variables in the [WIF reference](../04-API-Reference/Other/manage-claude-wif-reference.md#environment-variables), such as `ANTHROPIC_IDENTITY_TOKEN_FILE`, when it exchanges your identity token. For the profile file format, see the [WIF reference](../04-API-Reference/Other/manage-claude-wif-reference.md#profile-configuration-file). To confirm which source Claude Code chose, run `/status`. A `Profile` row names the source in place of the `Login method` row. When the profile is the credential in use, `Organization` and `Email` rows show its account. When a `user_oauth` profile’s login has expired and Claude Code can’t renew it, requests fail with [Anthropic profile login expired](../02-Claude-Code-CLI/errors.md#anthropic-profile-login-expired). Features that need your claude.ai login, such as [claude.ai connectors](../06-MCP-Tools/General/mcp.md#use-mcp-servers-from-claude-ai) and [`/schedule`](../02-Claude-Code-CLI/web-scheduled-tasks.md), aren’t available while one of these sources is selected. To stop Claude Code from selecting a source:

- **Named profile or federation variables**: unset `ANTHROPIC_PROFILE`, or unset either federation variable
- **Active profile**: run `/logout` for a `user_oauth` profile whose current credential you wrote by [signing in to a Console account without an API key](#sign-in-without-an-api-key), run `ant auth logout` for one whose current credential `ant auth login` wrote, or delete the profile’s file from `configs/` in your configuration directory for either auth mode


[​](#generate-a-long-lived-token)

Generate a long-lived token

For CI pipelines, scripts, or other environments where interactive browser login isn’t available, generate a one-year OAuth token with `claude setup-token`:

```python
claude setup-token
```

The command opens the same browser authorization flow as `/login`, and the token prints to the terminal after you approve access in the browser. It does not save the token anywhere; copy it and set it as the `CLAUDE_CODE_OAUTH_TOKEN` environment variable wherever you want to authenticate:

- macOS, Linux, WSL

- Windows PowerShell

- Windows CMD

```python
export CLAUDE_CODE_OAUTH_TOKEN=your-token
```

```python
$env:CLAUDE_CODE_OAUTH_TOKEN = "your-token"
```

```python
set CLAUDE_CODE_OAUTH_TOKEN=your-token
```

This token authenticates with your Claude subscription and requires a Pro, Max, Team, or Enterprise plan. It can only make model requests, so it can’t establish [Remote Control](../02-Claude-Code-CLI/remote-control.md) sessions or fetch [claude.ai connectors](../06-MCP-Tools/General/mcp.md#use-mcp-servers-from-claude-ai). MCP servers you configure locally still work. [Bare mode](../02-Claude-Code-CLI/headless.md#start-faster-with-bare-mode) does not read `CLAUDE_CODE_OAUTH_TOKEN`. If your script passes `--bare`, authenticate with `ANTHROPIC_API_KEY` or an `apiKeyHelper` instead.

---

## Join an organization via invite link | Claude Help Center

Source: https://support.claude.com/en/articles/13776697-join-an-organization-via-invite-link
Path: 13-Enterprise-Admin/join-an-organization-via-invite-link.md

# Join an organization via invite link

August 6, 2026


Admins on Team and Enterprise plans can share an invite link that lets teammates join the organization directly—without requiring individual email invitations.

## For admins: Set up and share an invite link

### Who can manage invite links

Admins and above can generate, copy, disable, and regenerate the invite link for their organization.

### Availability

- **Team plans:** Invite links are enabled by default for new organizations.

- **Enterprise plans (non-SSO):** Invite links are disabled by default. Admins can enable them from **[Organization settings \> Identity and access](https://claude.ai/admin-settings/identity)**.

- **SSO organizations:** Invite links are not available. Member provisioning is managed through your Identity Provider.

### Find and share your invite link

1.  Navigate to **[Organization settings \> Identity and access](https://claude.ai/admin-settings/identity)**.

2.  Find **Invite link** in the **Global access settings** section.

3.  Click the button to the right of the link to copy it.

4.  Share the link with the teammates you'd like to add—for example, in a Slack channel, email thread, or team wiki.

**Important:** Invite links expire automatically 90 days after creation, so you’ll need to regenerate the link when this happens so prospective members can use it.

### Manage your invite link

[TABLE]

### How seat assignment works

Members who join via link are assigned to the lowest available seat tier. If no seats are available, a standard seat is created automatically.

### Security

Invite link joins respect your organization's existing security controls:

- **Allowed domains:** Only users with an email address on your organization's **[allowed domains list](../21-Account-Support/account-management-faqs.md#h_b54c41c86c)** can join. Anyone else will see an error.

- **Seat limits:** If your organization has reached its seat limit, link joiners will see an error prompting them to contact their admin.

**Note:** Members who join via invite link are always added automatically, regardless of your organization's new member approval setting. If you need to review members before they join, share individual email invitations instead of the invite link.

------------------------------------------------------------------------

## For members: Join via an invite link

1.  Click the invite link your admin shared.

2.  If you're not already logged in, you'll be prompted to sign up or log in.

3.  After authenticating, your email domain will be checked against your organization's allowed domains.

    - If your domain matches, you can proceed to join.

    - If your domain doesn't match, you'll see an error. Contact your admin if you believe this is incorrect.

4.  After your domain is verified, you'll be added to the organization automatically and assigned to the lowest available seat tier.

If you're already a member of the organization, you'll be redirected to the chat interface automatically.

**Note:** If the link has been disabled or regenerated by your admin, it will no longer work. Ask your admin to share a new link.

---

## Legal and compliance - Claude Code Docs

Source: https://code.claude.com/docs/en/legal-and-compliance
Path: 13-Enterprise-Admin/legal-and-compliance.md

## On this page

- [Legal agreements](#legal-agreements)
  - [License](#license)
  - [Commercial agreements](#commercial-agreements)
  - [Can customers offer Claude Code in their products?](#can-customers-offer-claude-code-in-their-products)
- [Compliance](#compliance)
  - [Healthcare compliance (BAA)](#healthcare-compliance-baa)
- [Usage policy](#usage-policy)
  - [Acceptable use](#acceptable-use)
  - [Authentication and credential use](#authentication-and-credential-use)
- [Security and trust](#security-and-trust)
  - [Trust and safety](#trust-and-safety)
  - [Security vulnerability reporting](#security-vulnerability-reporting)

Resources

# Legal and compliance

Copy pageCopy page

Legal agreements, compliance certifications, and security information for Claude Code.

Copy pageCopy page


[​](#legal-agreements)

Legal agreements


[​](#license)

License

Your use of Claude Code is subject to:

- [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms) - for Team, Enterprise, and Claude API users
- [Consumer Terms of Service](https://www.anthropic.com/legal/consumer-terms) - for Free, Pro, and Max users


[​](#commercial-agreements)

Commercial agreements

Whether you’re using the Claude API directly (1P) or accessing it through Amazon Bedrock or Google Cloud’s Agent Platform (3P), your existing commercial agreement will apply to Claude Code usage, unless we’ve mutually agreed otherwise.


[​](#can-customers-offer-claude-code-in-their-products)

Can customers offer Claude Code in their products?

Unless we’ve mutually agreed otherwise, preinstalling or running Claude Code in your products or services (e.g. in hosted sandboxes or other agent infrastructure) requires agreeing to our [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms) and complying with the conditions below:

- **The Claude Code binary must not be modified.** Claude Code must be installed and run as published by Anthropic, and customers may not remove, disable, or restrict any authentication method built into it (including methods that permit signing in with a Claude account or the user’s own API key).
- **Customers may not pay for, resell, or intermediate Claude usage on their end users’ behalf.** Each end user must authenticate with their own Anthropic API key, Claude subscription plan credentials, or 3P inference provider credential (Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry). That usage is billed directly to the end user under their own agreement with Anthropic or, for third-party inference providers, with the applicable provider.

**Using the Claude Code name and logo.** You can accurately say, in plain text, that your product has Claude Code preinstalled or that it runs Claude Code. But you can’t use the Claude Code or Anthropic names or logos as part of your own product, feature, or company name, in your own logo, or in a way that suggests Anthropic built, endorses, or is partnered with your product. Any other use of Anthropic’s names or logos is governed by our [Trademark Guidelines](https://www.anthropic.com/legal/trademark-guidelines) and requires our written permission. Claude Code remains governed by Anthropic’s standard terms (see the License and Commercial agreements sections above) regardless of the platform through which it is accessed.


[​](#compliance)

Compliance


[​](#healthcare-compliance-baa)

Healthcare compliance (BAA)

If a customer has executed a Business Associate Agreement (BAA) with Anthropic, that BAA extends to Claude Code in two configurations, depending on what is available for your organization:

- **HIPAA configuration**: the Claude Code CLI or the Code tab in Claude Desktop, used with a Claude for Enterprise account. The organization must have HIPAA enabled and the HIPAA configuration applied to Claude Code (local mode) and Cowork (local mode). See [Set up Claude Code (local mode) for a HIPAA-ready organization](../02-Claude-Code-CLI/hipaa-setup.md).
- **Zero Data Retention**: Claude Code with [Zero Data Retention (ZDR)](../02-Claude-Code-CLI/zero-data-retention.md) enabled for the relevant organization. ZDR isn’t available for all organizations.

Under the HIPAA configuration, cloud sessions, Remote Control, Claude Code in the Claude mobile app, and Claude Code routed through a third-party cloud provider or a gateway aren’t covered. Review the [Implementation Guide](https://trust.anthropic.com/resources?s=l1wrssd9hsbi4gak0tp5a6&name=%5Banthropic%5D-hipaa-ready-offering-implementation-guide) for a full list of Eligible Services.


[​](#usage-policy)

Usage policy


[​](#acceptable-use)

Acceptable use

Claude Code usage is subject to the [Anthropic Usage Policy](https://www.anthropic.com/legal/aup). Advertised usage limits for Pro and Max plans assume ordinary, individual usage of Claude Code and the Agent SDK.


[​](#authentication-and-credential-use)

Authentication and credential use

Claude Code authenticates with Anthropic’s servers using OAuth tokens or API keys. These authentication methods serve different purposes:

- **OAuth authentication** is intended exclusively for purchasers of Claude Free, Pro, Max, Team, and Enterprise subscription plans and is designed to support ordinary use of Claude Code and other native Anthropic applications. For the sign-in steps, see [Logging in to your Claude account](../21-Account-Support/logging-in-to-your-claude-account.md); for how Claude Code performs OAuth authentication, see [Authentication](../02-Claude-Code-CLI/authentication.md).
- **Developers** building products or services that interact with Claude’s capabilities, including those using the [Agent SDK](../05-Agent-SDK/agent-sdk-overview.md), should use API key authentication through [Claude Console](../04-API-Reference/Other/playground.md) or a supported cloud provider. Anthropic does not permit third-party developers to offer Claude.ai login into their own applications, or to route requests through Free, Pro, or Max plan credentials on behalf of their users. Moreover, developers may not collect, store, or intermediate Claude.ai credentials or session tokens — sign-in to a Claude account must complete through Anthropic’s own flow.

This does not restrict how customers provision and manage their own API keys or third-party inference provider credentials — for example, configuring an API key in a development environment, secrets manager, or machine image for use by the customer’s own authorized users — provided the resulting usage is billed to the key owner under their agreement with Anthropic (or the applicable provider) and is not resold or intermediated as described above. Nor does it prevent an end user from signing in to the unmodified Claude Code binary with their own Claude subscription, including where a platform hosts Claude Code as described under *Can customers offer Claude Code in their products?* above. Anthropic reserves the right to take measures to enforce these restrictions and may do so without prior notice. For questions about permitted authentication methods for your use case, please [contact sales](https://www.anthropic.com/contact-sales?utm_source=claude_code&utm_medium=docs&utm_content=legal_compliance_contact_sales).


[​](#security-and-trust)

Security and trust


[​](#trust-and-safety)

Trust and safety

You can find more information in the [Anthropic Trust Center](https://trust.anthropic.com) and [Transparency Hub](https://www.anthropic.com/transparency).


[​](#security-vulnerability-reporting)

Security vulnerability reporting

Anthropic manages our security program through HackerOne. [Use this form to report vulnerabilities](https://hackerone.com/4f1f16ba-10d3-4d09-9ecc-c721aad90f24/embedded_submissions/new).

------------------------------------------------------------------------

© Anthropic PBC. All rights reserved. Use is subject to applicable Anthropic Terms of Service.

---

## Let team members run smart reports for specific groups | Claude Help Center

Source: https://support.claude.com/en/articles/16948886-let-team-members-run-smart-reports-for-specific-groups
Path: 13-Enterprise-Admin/let-team-members-run-smart-reports-for-specific-groups.md

# Let team members run smart reports for specific groups


Smart reports show how your organization uses Claude. By default, only Primary Owners, Owners, Admins, and custom roles with analytics view access can create and view smart reports. With delegated access, you can let team leads or department heads run smart reports without making them admins. They can only report on the groups, departments, or cost centers you choose, and they see only the reports they ran or that others share with them.

Smart reports are available in beta on Claude Enterprise plans and aren’t available for organizations using customer-managed encryption keys (CMEK) or HIPAA configurations. Smart reports are also unavailable for Claude Code for Claude Enterprise organizations that use zero data retention.

**Important:** Smart reports help you understand adoption and plan your investment in Claude. They aren't designed and should not be used for evaluating individual performance or making employment decisions.

## Who can manage access for smart reports

To manage access to smart reports, you must be an Owner or Primary Owner, or in a custom role with both the **Analytics** permission and the **Identity & Access** permission. If you don’t have one of these roles, you won’t see the **Manage access** button.

## Before you begin

- Your organization must be on a Claude Enterprise plan with "Smart Reports (beta)" turned on in **Organization settings \> Capabilities \> Analytics**.

- The people you add must be on the **Custom roles** access level, which is set per member in **Organization settings \> Members**.

## Step 1: Open Manage access

Go to **[Analytics \> Smart reports (beta)](https://claude.ai/analytics/insights)** and click "Manage access." The **Who can run reports** screen opens.

The line at the top tells you which roles can run reports. Admins and owners can always run reports.

## Step 2: Add a person

Under **Add people**, type a name or email and pick the person.

Some users appear greyed out with the label **Not on Custom roles**. Change their access level in **[Organization settings \> Members](https://claude.ai/admin-settings/members)**, then close and reopen **Manage access** and add them.

## Step 3: Choose what they can report on

The person appears under **People with access**, with the columns **Person** and **Can run reports on**. Nothing is saved for them until you choose a scope.

1.  If your organization has more than one kind of scope, select **Group**, **Department**, or **Cost center** in the first dropdown menu.

    1.  Groups come from **[Organization settings \> Groups](https://claude.ai/admin-settings/groups)**.

    2.  Departments and cost centers are offered only if your identity provider provisions members through SCIM with those attributes.

2.  Select the group, department, or cost center values in the second dropdown menu.

Each change is saved as soon as you make it and will say "Saved."  
​

If you click "Close" while someone still has no scope, or their last change couldn't be saved, the screen asks "Close without saving?" and explains that their access isn't saved.

Keep in mind:

- You can select up to 50 groups, departments, or cost centers of each kind per person.

- A delegate can only run a report on what you assign here. They never get a whole-organization option.

**Note:** People you give access to aren’t notified. They can view smart reports by clicking on their name in the lower left corner in Claude, then going to **Analytics \> Smart reports (beta)**.

## What happens automatically

The first time you save a delegate, smart reports creates two things in your organization:

- A group named **Smart Reports delegates**

- A custom role named **Smart Reports delegates**, assigned to that group, with the **Smart Reports delegated admin** permission

Every person you add is placed in this group, which is how they get permission to run reports. We recommend that you don’t edit these roles manually.

The following badges can appear on a person's row:

- **Not on Custom roles**: the person's access level changed after you added them. Change it back in **[Organization settings \> Members](https://claude.ai/admin-settings/members)**.

- **Needs the permission**: this person doesn’t have access to any roles that grant permission to run smart reports (for example, they were removed from the smart reports delegates group). Click **Add to the Smart Reports delegates role** on their row to add them back. If the automatic role itself lost the permission, restore it in **[Organization settings \> Roles](https://claude.ai/admin-settings/roles)** first.

## What a delegate sees and can do

- In **Analytics**, delegates see only **Smart Reports**. Their list is titled **Your reports** and contains only the reports they ran.

- When they click "New report," the **People in scope** menu offers only the groups, departments, or cost centers you assigned. All of them are selected to start, and the delegate can narrow the selection.

- Delegates can share their reports with other members of your organization and download them, the same way admins can.

- The attributed view, which shows user emails and session IDs, only appears when your organization's **Allow attribution to individual users** setting is on.

- The same privacy guardrails apply to reports delegates run. Learn more about **[privacy guardrails in smart reports](../15-Claude-AI-Features/get-started-with-smart-reports.md#h_7b22bb48c1)**.

- Reports run by delegates count toward your organization's monthly smart reports limit and share the same limit on reports running at once.

Delegates cannot:

- Run a report on the whole organization, or on anything outside their assigned scope

- See reports other people ran, unless someone shares the report with them

- Rename, archive, or delete reports

- Open **Manage access** or change who can run reports

## Change or remove access

- **To change what someone can report on**, open **Manage access** and change their selections. The new selection replaces their previous scope. Reports they already ran stay available to them.

- **To remove someone**, click the "✕" (**Remove**) button on their row. Their access is removed right away, and you'll see a confirmation that they can no longer run reports. If the smart reports delegates group is used only by the automatic role, they are also taken out of it. Otherwise, remove them from the group in **Organization settings \> Groups**. Once they no longer hold the permission, they can't open smart reports, including the reports they ran. Reports they already downloaded aren't affected.

- **When a user is removed from your organization**, they can no longer run reports. Their saved scope is usually removed at the same time. If your identity provider removes users through SCIM, remove them in **Manage access** first so their old scope isn't kept if they're added back later.

- **When a group is deleted, or a department or cost center value no longer exists**, it disappears from the person's row and from their selections. If a delegate tries to run a report with a selection that no longer exists, the report is refused and they can pick again. You can also open their row and choose new values.

## FAQ

### Can I grant the permission through my own roles instead of the automatic one?

Yes. In **Organization settings \> Roles**, the **Smart Reports delegated admin** permission appears under **Product controls**. Adding it to a role isn't enough on its own: each person still needs a scope. Open **Manage access** on the smart reports page, add each person, and choose what they can report on.

### Why is someone greyed out when I try to add them?

They aren't on the **Custom roles** access level, which custom roles (including the automatic one) require. Change it in **Organization settings \> Members**, or in your identity provider if it sets access levels.

### Can a delegate see individual users' names or emails?

Only if your organization's **Allow attribution to individual users** setting is on. It is off by default.

---

## Connect Claude Code to an LLM gateway - Claude Code Docs

Source: https://code.claude.com/docs/en/llm-gateway-connect
Path: 13-Enterprise-Admin/llm-gateway-connect.md

## On this page

- [Check for an existing configuration](#check-for-an-existing-configuration)
- [Configure Claude Code yourself](#configure-claude-code-yourself)
  - [Set the credential variable](#set-the-credential-variable)
  - [Set the base URL and credential](#set-the-base-url-and-credential)
  - [Set as shell environment variables](#set-as-shell-environment-variables)
  - [Set in a settings file](#set-in-a-settings-file)
  - [Verify the connection](#verify-the-connection)
  - [Confirm in Claude Code](#confirm-in-claude-code)
  - [How the credential variable maps to a header](#how-the-credential-variable-maps-to-a-header)
  - [Conflicts with an existing login](#conflicts-with-an-existing-login)
- [Configure each surface](#configure-each-surface)
  - [VS Code extension](#vs-code-extension)
  - [Desktop app](#desktop-app)
  - [GitHub Actions](#github-actions)
  - [Agent SDK](#agent-sdk)
  - [Slack, cloud sessions, and Remote Control](#slack-cloud-sessions-and-remote-control)
- [Additional configuration](#additional-configuration)
  - [Send additional headers](#send-additional-headers)
  - [Add gateway models to the model picker](#add-gateway-models-to-the-model-picker)
  - [Rotate credentials with apiKeyHelper](#rotate-credentials-with-apikeyhelper)
  - [Turn off traffic outside the gateway path](#turn-off-traffic-outside-the-gateway-path)
  - [Route to a cloud provider through a gateway](#route-to-a-cloud-provider-through-a-gateway)
  - [Amazon Bedrock](#amazon-bedrock)
  - [Google Cloud’s Agent Platform](#google-cloud%E2%80%99s-agent-platform)
  - [Microsoft Foundry](#microsoft-foundry)
  - [Claude Platform on AWS](#claude-platform-on-aws)
  - [Confirm the provider route](#confirm-the-provider-route)
- [Troubleshoot gateway errors](#troubleshoot-gateway-errors)
- [Related resources](#related-resources)

Other gateways

# Connect Claude Code to an LLM gateway

Copy pageCopy page

Point Claude Code at your organization’s LLM gateway. Check whether your admin already configured it, or set the base URL and credential yourself, then verify the connection and fix gateway errors.

Copy pageCopy page

An [LLM gateway](llm-gateway.md) is a proxy your organization runs between Claude Code and the model provider. When your organization uses one, Claude Code authenticates to the gateway with a credential your organization issues instead of your personal claude.ai login. This page is for developers running Claude Code through a gateway their organization operates. It covers two paths: [checking whether your administrator already configured it for you](#check-for-an-existing-configuration), and [configuring it yourself](#configure-claude-code-yourself) when they haven’t.

- To deploy a gateway for your organization, see [Roll out an LLM gateway](llm-gateway-rollout.md)
- For what Claude Code sends to a gateway, see the [gateway compatibility guide](llm-gateway-protocol.md)


[​](#check-for-an-existing-configuration)

Check for an existing configuration

Administrators can distribute the gateway address and credential through [managed settings](managed-settings.md), device management, or an [`apiKeyHelper`](#rotate-credentials-with-apikeyhelper), so Claude Code picks them up at startup with nothing for you to set. To check whether your organization already did this:

1

Start Claude Code

Run `claude`. If it opens to the login screen instead of a session, no gateway credential was distributed; [configure it yourself](#configure-claude-code-yourself) below.

2

Check the Status tab

If Claude Code started a session without showing the login screen, run `/status`, which opens on the **Status** tab, and check two lines:

- `Anthropic base URL`: this line only appears when a gateway address is set. If it isn’t there, Claude Code isn’t pointed at the gateway; [configure it yourself](#configure-claude-code-yourself) below.
- `Auth token` or `API key`: a line naming `ANTHROPIC_AUTH_TOKEN`, `ANTHROPIC_API_KEY`, or an `apiKeyHelper` confirms a gateway credential is active. A `Login method` line naming a claude.ai account instead means the credential wasn’t distributed; [set it yourself](#set-the-credential-variable).

3

Send a test message

Close the `/status` menu and send any prompt in Claude Code. A normal response from Claude, with no error, confirms the gateway connection works.

If both lines in the `/status` menu look right but the message to Claude fails, see the [troubleshooting table](#troubleshoot-gateway-errors).


[​](#configure-claude-code-yourself)

Configure Claude Code yourself

To configure Claude Code for the gateway yourself, you need from your gateway team:

- The gateway’s base URL
- A credential: a key or token string, or a command that fetches one
  - If your gateway team didn’t say which kind of credential it is, the [credential variable section](#set-the-credential-variable) below covers what to try

The sections below cover the configuration in order:

- [Set the credential variable](#set-the-credential-variable) and [set the base URL](#set-the-base-url-and-credential): the two variables every gateway connection needs
- [Verify the connection](#verify-the-connection): confirm it works before persisting anything
- [Configure each surface](#configure-each-surface): if you are using a surface besides the Claude Code CLI, such as VS Code, see how to configure it with your gateway credentials
- [Additional configuration](#additional-configuration): variables some gateways need beyond the base URL and credential, such as a custom header, a credential helper, model discovery, a provider-format base URL, or turning off traffic outside the gateway path. Set these only if your administrator named them or your network restricts egress


[​](#set-the-credential-variable)

Set the credential variable

To authenticate Claude Code to the gateway, set your credential in an environment variable. Which variable depends on what your gateway team told you:

| Set the credential in                                   | Use when                                                        |
|:--------------------------------------------------------|:----------------------------------------------------------------|
| `ANTHROPIC_AUTH_TOKEN`                                  | Your gateway team said “bearer token” or “Authorization header” |
| `ANTHROPIC_API_KEY`                                     | Your gateway team said “API key” or “x-api-key”                 |
| [`apiKeyHelper`](#rotate-credentials-with-apikeyhelper) | The credential rotates or comes from a vault                    |

If you weren’t told which kind, use `ANTHROPIC_AUTH_TOKEN`; the [verification request](#verify-the-connection) below shows how to tell if you need to switch.


[​](#set-the-base-url-and-credential)

Set the base URL and credential

Set the gateway’s base URL and the credential variable you picked above as environment variables. The examples use `ANTHROPIC_AUTH_TOKEN`; swap it for `ANTHROPIC_API_KEY` if that’s [the variable you picked](#set-the-credential-variable). You can set them [in your shell](#set-as-shell-environment-variables), which lasts for one terminal session, or [in a Claude Code settings file](#set-in-a-settings-file), which persists everywhere Claude Code runs. For your first connection, start with shell exports and run the [verification request](#verify-the-connection) before moving the values to a settings file.


[​](#set-as-shell-environment-variables)

Set as shell environment variables

Replace the values with the ones your gateway team gave you:

- Bash or Zsh

- PowerShell

```python
export ANTHROPIC_BASE_URL=https://llm-gateway.example.com
export ANTHROPIC_AUTH_TOKEN=sk-gateway-key
```

```python
$env:ANTHROPIC_BASE_URL = "https://llm-gateway.example.com"
$env:ANTHROPIC_AUTH_TOKEN = "sk-gateway-key"
```

Shell exports apply only to that terminal session and programs started from it. An editor launched from the dock or Start menu won’t see them. To make the values persist across new terminals, add the same lines to your shell profile, such as `~/.zshrc`, `~/.bashrc`, or your PowerShell `$PROFILE`. If you export the gateway only in your shell, it doesn’t reliably reach background agents hosted by the [supervisor](../09-Agents-Patterns/agent-view.md#how-background-sessions-are-hosted); see [how each background session sources its gateway](../09-Agents-Patterns/agent-view.md#llm-gateway). Use a settings file for any gateway that background agents must always route through.


[​](#set-in-a-settings-file)

Set in a settings file

To make the configuration apply everywhere Claude Code runs, including [background agents](../09-Agents-Patterns/agent-view.md#how-background-sessions-are-hosted), set the variables in the `env` block of a [settings file](../02-Claude-Code-CLI/settings.md) instead of relying on your shell. Settings files have different scopes:

- `~/.claude/settings.json` applies to all your projects. On Windows the path is `%USERPROFILE%\.claude\settings.json`
- `.claude/settings.local.json` applies to one project. Claude Code adds it to your global gitignore when it saves a setting there; if you create it by hand or have Claude write it, add it to your gitignore yourself first so you don’t accidentally commit your credential

Don’t put the credential in a project’s `.claude/settings.json`. That file is committed and shared with everyone who clones the repository.

The `env` block looks the same in either file:

```python
{
  "env": {
    "ANTHROPIC_BASE_URL": "https://llm-gateway.example.com",
    "ANTHROPIC_AUTH_TOKEN": "sk-gateway-key"
  }
}
```

When both a shell export and a settings-file `env` block set the same variable, the settings-file value applies. Run `/status` to see which base URL and credential source Claude Code is using.


[​](#verify-the-connection)

Verify the connection

With the variables exported in your shell, send a one-token request to the gateway directly. This confirms the URL and credential work before you open Claude Code, so a failure points at the gateway rather than your configuration. The commands below read the shell variables, so they need the [shell exports](#set-as-shell-environment-variables) even if you also put the values in a settings file.

- Bash or Zsh

- PowerShell

```python
curl -sS -w '\n%{http_code}\n' -X POST "$ANTHROPIC_BASE_URL/v1/messages" \
  -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model": "claude-sonnet-4-6", "max_tokens": 1, "messages": [{"role": "user", "content": "."}]}'
```

```python
Invoke-RestMethod -Method Post -Uri "$env:ANTHROPIC_BASE_URL/v1/messages" `
  -Headers @{ "Authorization" = "Bearer $env:ANTHROPIC_AUTH_TOKEN"; "anthropic-version" = "2023-06-01" } `
  -ContentType "application/json" `
  -Body '{"model": "claude-sonnet-4-6", "max_tokens": 1, "messages": [{"role": "user", "content": "."}]}'
```

If your gateway expects keys in the `x-api-key` header, replace the `Authorization` header with `x-api-key: $ANTHROPIC_API_KEY` in the Bash command, or the `"Authorization"` hashtable entry with `"x-api-key" = "$env:ANTHROPIC_API_KEY"` in the PowerShell command. A JSON response that starts with `{"id":"msg_` and includes a `"content":[...]` field means the gateway is reachable and the credential works. An error naming an unknown model still proves the URL and credential work, since the gateway authenticated the request before rejecting the model name; you don’t need to find a model your gateway serves for this test. A `401` means the credential was rejected: if you guessed the variable, switch to the other one and re-export.


[​](#confirm-in-claude-code)

Confirm in Claude Code

Start `claude` from the same shell so it inherits the exports, send a message, and run `/status`. On the **Status** tab, the `Anthropic base URL` line should show your gateway address, which confirms requests are routing there; if the line isn’t there, the variable didn’t reach the session. An `Auth token` or `API key` line naming the variable you set confirms the gateway credential is active rather than a saved claude.ai login. If the message fails, or `/status` doesn’t show the gateway URL, see the [troubleshooting table](#troubleshoot-gateway-errors) below.


[​](#how-the-credential-variable-maps-to-a-header)

How the credential variable maps to a header

Each variable sends the credential in a different HTTP header: `ANTHROPIC_AUTH_TOKEN` in `Authorization: Bearer`, `ANTHROPIC_API_KEY` in `x-api-key`, and `apiKeyHelper` in both. A credential in the wrong variable reaches the gateway in a header it doesn’t read, and the request fails with `401`. If the verification request returned `401`, switch to the other variable and try again.


[​](#conflicts-with-an-existing-login)

Conflicts with an existing login

A gateway credential variable takes precedence over a saved claude.ai login or Console key. Your claude.ai login stays saved and unused while the variable is set; unset the variable and Claude Code goes back to it. With `ANTHROPIC_AUTH_TOKEN`, the variable takes precedence immediately. With `ANTHROPIC_API_KEY`, you are prompted once in interactive mode to approve the key before it takes over. Run `/status` to confirm which credential source is active. If startup shows an auth-conflict warning naming two sources, see the first row of the [troubleshooting table](#troubleshoot-gateway-errors) for which one to drop. To clear a saved login so only the gateway credential remains, run `/logout`.


[​](#configure-each-surface)

Configure each surface

The CLI reads the environment variables and settings files above. The other surfaces are the VS Code extension, the desktop app, GitHub Actions, the Agent SDK, and the cloud surfaces such as Slack and the web; the sections below cover whether those settings reach each one.


[​](#vs-code-extension)

VS Code extension

Set the gateway variables for the [VS Code extension](../03-IDE-Integrations/vs-code.md) in `claudeCode.environmentVariables`, in VS Code’s own user settings opened with the **Preferences: Open User Settings (JSON)** command. The extension checks credentials from this setting before launching, so it’s the reliable place for the gateway credential; values in `~/.claude/settings.json` reach the spawned process but not the extension’s own login check.

```python
{
  "claudeCode.environmentVariables": [
    { "name": "ANTHROPIC_BASE_URL", "value": "https://llm-gateway.example.com" },
    { "name": "ANTHROPIC_AUTH_TOKEN", "value": "sk-gateway-key" }
  ]
}
```


[​](#desktop-app)

Desktop app

The desktop app reads gateway routing from its [third-party inference configuration](https://claude.com/docs/third-party/claude-desktop/gateway), not from `ANTHROPIC_BASE_URL` or `settings.json`. That configuration can come from your organization or from a form in the app itself:

- **Distributed by an administrator**: if your organization has [deployed the configuration](llm-gateway-rollout.md#distribute-through-managed-settings), the desktop app routes through the gateway with no setup on your part
- **Configured locally**: for devices without an administrator-distributed configuration, open Help → Troubleshooting → Enable Developer Mode, which restarts the app with a Developer menu. Then open Developer → Configure Third-Party Inference and enter your gateway base URL. An administrator-distributed configuration takes precedence and makes this form read-only

With the gateway configuration active, the environment selector doesn’t offer Anthropic-hosted cloud environments, and [Remote Control](../02-Claude-Code-CLI/remote-control.md) is unavailable. SSH sessions are in beta with a gateway configuration and require Claude Desktop v1.40609.0 or later. Before you connect, check the allowlist and the gateway’s address:

- **Allowed hosts**: SSH sessions are off by default. To turn them on, you or your administrator lists the allowed hosts in the [`sshHostAllowlist`](https://claude.com/docs/third-party/claude-desktop/configuration#sshhostallowlist) key of the third-party inference configuration
- **Gateway address**: the remote machine connects to the gateway itself, so a gateway at `localhost` on your computer doesn’t work for SSH sessions

See [SSH remote sessions in Claude Desktop on 3P](https://claude.com/docs/third-party/claude-desktop/ssh-remote-sessions). You can also run the CLI on the remote host with [`ANTHROPIC_BASE_URL` and the gateway credential](#set-the-base-url-and-credential) set there. If the desktop app shows `Gateway was unreachable`, the app couldn’t reach the configured base URL at startup; check the URL and network path with the [curl test above](#verify-the-connection).


[​](#github-actions)


[Claude Code GitHub Actions](../02-Claude-Code-CLI/github-actions.md) reads `ANTHROPIC_BASE_URL` and `ANTHROPIC_CUSTOM_HEADERS` from the workflow’s `env` block. Pass the credential as the action’s `anthropic_api_key` input; the action sets it as `ANTHROPIC_API_KEY`, so it reaches the gateway in the `x-api-key` header. For an `x-api-key` gateway, set the base URL in `env` and pass the gateway key as the input:

```python
env:
  ANTHROPIC_BASE_URL: https://llm-gateway.example.com

steps:
  - uses: anthropics/claude-code-action@v1
    with:
      anthropic_api_key: ${{ secrets.GATEWAY_API_KEY }}
```

For a bearer-token gateway, pass the same secret twice: as the `anthropic_api_key` input and as `ANTHROPIC_AUTH_TOKEN` in the workflow `env` block. The action requires `anthropic_api_key`, `CLAUDE_CODE_OAUTH_TOKEN`, or workload identity federation before it launches Claude Code, and it doesn’t read `ANTHROPIC_AUTH_TOKEN`, so the input is there only to satisfy that launch check. The env variable is what puts the key in the `Authorization` header the gateway reads; the copy in `x-api-key` is ignored:

```python
env:
  ANTHROPIC_BASE_URL: https://llm-gateway.example.com
  ANTHROPIC_AUTH_TOKEN: ${{ secrets.GATEWAY_API_KEY }}

steps:
  - uses: anthropics/claude-code-action@v1
    with:
      anthropic_api_key: ${{ secrets.GATEWAY_API_KEY }}
```

For the action’s other authentication options, including `CLAUDE_CODE_OAUTH_TOKEN` and workload identity federation, see [Claude Code GitHub Actions](../02-Claude-Code-CLI/github-actions.md) and the action’s [README](https://github.com/anthropics/claude-code-action#readme).


[​](#agent-sdk)

Agent SDK

The [Agent SDK](../05-Agent-SDK/agent-sdk-overview.md) has no gateway-specific options; it passes environment variables to the Claude Code process it spawns. Each SDK accepts an `env` option that sets the spawned process’s environment, and the TypeScript and Python SDKs treat it differently:

- TypeScript: the spawned process inherits the parent environment by default, but setting `options.env` replaces the environment entirely. Spread `process.env` into it to keep your gateway variables.
- Python: `ClaudeAgentOptions(env=...)` merges on top of the inherited environment, so gateway variables set in the parent process carry through without spreading.

TypeScript

Python

```python
const result = query({
  prompt: "...",
  options: {
    env: {
      ...process.env,
      ANTHROPIC_BASE_URL: "https://llm-gateway.example.com",
      ANTHROPIC_AUTH_TOKEN: process.env.GATEWAY_KEY,
    },
  },
})
```

```python
options = ClaudeAgentOptions(
    env={
        "ANTHROPIC_BASE_URL": "https://llm-gateway.example.com",
        "ANTHROPIC_AUTH_TOKEN": os.environ["GATEWAY_KEY"],
    }
)
```


[​](#slack-cloud-sessions-and-remote-control)

Slack, cloud sessions, and Remote Control

[Claude Code in Slack](../14-Connectors/slack.md) and [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) aren’t part of a gateway deployment. Gateway variables set in a cloud session’s environment configuration are not applied. If your traffic must stay on the gateway, don’t enable these surfaces for those users. [Remote Control](../02-Claude-Code-CLI/remote-control.md) and [voice dictation](../02-Claude-Code-CLI/voice-dictation.md) both rely on a claude.ai identity: Remote Control to pair a live session with your account, and voice dictation to reach the claude.ai transcription endpoint. They are unavailable while `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or an `apiKeyHelper` is active. Remote Control is also disabled while `ANTHROPIC_BASE_URL` points at a non-Anthropic host, so signing in with claude.ai isn’t enough on its own. To restore either feature, log in with claude.ai and unset the gateway variables that feature checks. The Remote Control section of `claude doctor` names what is currently blocking Remote Control.

- Voice dictation: unset the gateway credential
- Remote Control: unset the gateway credential and `ANTHROPIC_BASE_URL`


[​](#additional-configuration)

Additional configuration

These settings cover cases beyond the base URL and credential. Set them only if your administrator’s instructions, your network’s egress rules, or the [troubleshooting table](#troubleshoot-gateway-errors) call for one.


[​](#send-additional-headers)

Send additional headers

Some gateways route or tag requests using a custom header in addition to the credential, for example a tenant identifier or a routing key. To send one, set [`ANTHROPIC_CUSTOM_HEADERS`](../02-Claude-Code-CLI/env-vars.md) with one `Name: Value` pair per line. The example below adds a routing header named `X-Org-Route`:

- Bash or Zsh

- PowerShell

```python
export ANTHROPIC_CUSTOM_HEADERS="X-Org-Route: prod"
```

```python
$env:ANTHROPIC_CUSTOM_HEADERS = "X-Org-Route: prod"
```

You can also set `ANTHROPIC_CUSTOM_HEADERS` in the `env` block of a settings file. Use `\n` between pairs there, since JSON strings can’t span multiple lines:

```python
{
  "env": {
    "ANTHROPIC_CUSTOM_HEADERS": "X-Org-Route: prod\nX-Tenant: example"
  }
}
```

Routing and tenant header names like these count as [headers that need approval](server-managed-settings.md#environment-variables-and-the-approval-dialog). When the headers come from a project settings file, Claude Code applies them under the [rules for when it applies `env` values](../02-Claude-Code-CLI/settings-reference.md#when-claude-code-applies-env-values).


[​](#add-gateway-models-to-the-model-picker)

Add gateway models to the model picker

With model discovery enabled, Claude Code queries the gateway for its model list at startup and adds those names to the `/model` picker alongside the built-in entries. If you or your administrator set `replaceBuiltInOptions` in a [`modelPicker`](../02-Claude-Code-CLI/settings-reference.md#modelpicker) lineup, Claude Code hides the discovered names too. It keeps a row for the model the session is already using. Enable it if your gateway serves model names that aren’t in Claude Code’s built-in list and you want to select them from the picker. If the built-in models are what you use, you don’t need discovery; your administrator may also have already enabled it through managed settings. To enable it, set `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1` in your shell or in the `env` block of `~/.claude/settings.json`. Discovered models appear as additional `/model` entries. Each entry shows the description your gateway supplies for the model, or `From gateway` when it doesn’t supply one. To confirm discovery ran, start `claude --debug` and look for the `[gatewayDiscovery]` lines in the debug log at `~/.claude/debug/<session-id>.txt`. The first time discovery succeeds, Claude Code logs how many models it cached, and it logs again only when the gateway’s list changes. A `404`, timeout, or redirect appears there too. For when discovery runs, what it filters, and the response format gateways serve, see the [model discovery reference](llm-gateway-protocol.md#model-discovery).


[​](#rotate-credentials-with-apikeyhelper)

Rotate credentials with apiKeyHelper

An `apiKeyHelper` is a command Claude Code runs to fetch your gateway credential, instead of reading it from a static environment variable. Use a helper when the credential expires on a schedule, comes from a vault or SSO command, or your administrator told you to configure one. If your credential is a fixed string you set once, the [credential variable](#set-the-credential-variable) is all you need and you can skip this section. The helper is any shell command that prints the current credential to stdout. Claude Code runs it through your system shell, so on Windows it can be an executable or a PowerShell invocation. Make the command print nothing but the credential. On Claude Code v2.1.227 or later, a banner or a log line printed alongside the key makes the [helper fail](../02-Claude-Code-CLI/errors.md#your-apikeyhelper-script-is-failing). Write the script, make it executable, and reference it from `apiKeyHelper` in your [settings file](../02-Claude-Code-CLI/settings.md):

- Bash or Zsh

- PowerShell

For example, a script that reads from a vault:

```python
#!/bin/bash
vault kv get -field=api_key secret/llm-gateway/claude-code
```

Reference its path in `~/.claude/settings.json`:

```python
{
  "apiKeyHelper": "~/bin/get-gateway-key.sh"
}
```

For example, a script that reads from a vault:

```python
vault kv get -field=api_key secret/llm-gateway/claude-code
```

Reference the PowerShell invocation in `%USERPROFILE%\.claude\settings.json`, escaping the backslashes in the JSON string:

```python
{
  "apiKeyHelper": "powershell -NoProfile -File C:\\scripts\\get-gateway-key.ps1"
}
```

Claude Code caches the helper’s output for five minutes by default and re-runs the helper after the cache lifetime elapses. To change the lifetime, set `CLAUDE_CODE_API_KEY_HELPER_TTL_MS` in milliseconds, for example `CLAUDE_CODE_API_KEY_HELPER_TTL_MS=900000` for 15 minutes. See [`apiKeyHelper`](../02-Claude-Code-CLI/settings-reference.md#apikeyhelper) for the other cases in which Claude Code re-runs the helper. The helper’s value is sent in both the `Authorization` and `x-api-key` headers, so it works whichever header your gateway reads.


[​](#turn-off-traffic-outside-the-gateway-path)

Turn off traffic outside the gateway path

The gateway carries model requests, but Claude Code also sends nonessential background traffic outside the gateway path, to Anthropic and to third-party services such as GitHub: version checks, telemetry, release notes, and similar requests. On a network that only allows egress to the gateway, these requests fail and can appear as blocked connections in your egress monitoring. Claude Code attaches a credential to a telemetry or usage-metrics request only when the request goes to the host the credential belongs to. While `ANTHROPIC_BASE_URL` points at the gateway, Claude Code sends its telemetry events to Anthropic without your gateway credential. With a [credential variable](#set-the-credential-variable) or `apiKeyHelper` also active, Claude Code doesn’t report usage metrics to the Console [analytics dashboard](analytics.md#access-analytics-for-api-customers). Before v2.1.246, Claude Code could attach the gateway credential to telemetry and usage-metrics requests bound for Anthropic hosts; model requests always went to the gateway with the credential the gateway expects. To turn that traffic off, set `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1` alongside the gateway variables, in the same shell exports or settings-file `env` block:

- Bash or Zsh

- PowerShell

```python
export CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
```

```python
$env:CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = "1"
```

Setting the variable has these effects and limits:

- It disables auto-updates, so plan for another update path, such as your package manager or managed distribution.
- It suppresses the [fast mode](../02-Claude-Code-CLI/fast-mode.md) availability check. Unless a previous check already enabled fast mode on the machine, `/fast` reports that fast mode is unavailable.
- It doesn’t affect [gateway model discovery](#add-gateway-models-to-the-model-picker), which queries only your gateway. Before v2.1.257, the variable also stopped discovery from refreshing, so the picker kept the previously cached list.
- The WebFetch tool’s [domain safety check](data-usage.md#webfetch-domain-safety-check) isn’t affected and still calls `api.anthropic.com`. Turn it off separately with `skipWebFetchPreflight: true` in [settings](../02-Claude-Code-CLI/settings.md) if your network blocks that host.
- For each telemetry stream and the variable that controls it, see [telemetry services](data-usage.md#telemetry-services).


[​](#route-to-a-cloud-provider-through-a-gateway)

Route to a cloud provider through a gateway

These configurations point Claude Code at a gateway through a provider-specific base URL variable in place of `ANTHROPIC_BASE_URL`. Amazon Bedrock and Google Cloud’s Agent Platform gateways accept those providers’ native request formats; Microsoft Foundry and Claude Platform on AWS gateways accept the Anthropic Messages format. On the Amazon Bedrock and Google Cloud’s Agent Platform routes, Claude Code also limits the beta headers and request fields it sends to the set that provider accepts. For what your gateway receives on each route, see the [gateway compatibility guide](llm-gateway-protocol.md). Use one only if your gateway team specifically named Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, or the Claude Platform on AWS. If the [verification request](#verify-the-connection) above returned JSON, you can skip this section. Set the block for the provider your gateway team named. The skip-auth variables in the Amazon Bedrock, Google Cloud’s Agent Platform, and Claude Platform on AWS blocks tell Claude Code not to sign requests with the cloud provider’s credentials, since the gateway holds those. If the gateway also needs its own token, where you put it depends on the provider:

- **Amazon Bedrock, Google Cloud’s Agent Platform, or Claude Platform on AWS**: add `ANTHROPIC_AUTH_TOKEN` after the block. Claude Code sends it to the gateway as an `Authorization: Bearer` header. For a credential in a different scheme or header, use [`ANTHROPIC_CUSTOM_HEADERS`](#send-additional-headers) instead. Keep the skip-auth variable set either way, since without it Claude Code removes any `Authorization` header that `ANTHROPIC_AUTH_TOKEN`, an [`apiKeyHelper`](#rotate-credentials-with-apikeyhelper), or `ANTHROPIC_CUSTOM_HEADERS` would add.
- **Microsoft Foundry**: use `ANTHROPIC_FOUNDRY_API_KEY` as [its block](#microsoft-foundry) shows


[​](#amazon-bedrock)

Amazon Bedrock

Leave `AWS_BEARER_TOKEN_BEDROCK` unset when the gateway issues its own credential. If you set it, Claude Code sends that [Amazon Bedrock API key](../02-Claude-Code-CLI/amazon-bedrock.md#2-configure-aws-credentials) as the `Authorization` header instead of your gateway token, even with `CLAUDE_CODE_SKIP_BEDROCK_AUTH` set.

- Bash or Zsh

- PowerShell

```python
export ANTHROPIC_BEDROCK_BASE_URL=https://llm-gateway.example.com/bedrock
export CLAUDE_CODE_SKIP_BEDROCK_AUTH=1
export CLAUDE_CODE_USE_BEDROCK=1
```

```python
$env:ANTHROPIC_BEDROCK_BASE_URL = "https://llm-gateway.example.com/bedrock"
$env:CLAUDE_CODE_SKIP_BEDROCK_AUTH = "1"
$env:CLAUDE_CODE_USE_BEDROCK = "1"
```


[​](#google-cloud’s-agent-platform)

Google Cloud’s Agent Platform

Replace the project ID and region with your own values. Claude Code includes both in the path of each request it sends to the gateway:

- Bash or Zsh

- PowerShell

```python
export ANTHROPIC_VERTEX_BASE_URL=https://llm-gateway.example.com/vertex
export ANTHROPIC_VERTEX_PROJECT_ID=your-gcp-project-id
export CLAUDE_CODE_SKIP_VERTEX_AUTH=1
export CLAUDE_CODE_USE_VERTEX=1
export CLOUD_ML_REGION=us-east5
```

```python
$env:ANTHROPIC_VERTEX_BASE_URL = "https://llm-gateway.example.com/vertex"
$env:ANTHROPIC_VERTEX_PROJECT_ID = "your-gcp-project-id"
$env:CLAUDE_CODE_SKIP_VERTEX_AUTH = "1"
$env:CLAUDE_CODE_USE_VERTEX = "1"
$env:CLOUD_ML_REGION = "us-east5"
```

The block covers routing and authentication. The region overrides and model pins from the [Agent Platform setup](../02-Claude-Code-CLI/google-vertex-ai.md#4-configure-claude-code) apply through a gateway as well:

- **Per-model regions**: if your gateway serves some models from a region other than `CLOUD_ML_REGION`, set the matching `VERTEX_REGION_CLAUDE_*` variable for each, for example `VERTEX_REGION_CLAUDE_4_6_SONNET=europe-west1`. The [environment variables reference](../02-Claude-Code-CLI/env-vars.md) lists the exact names.
- **Model versions**: pin `ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL`, and `ANTHROPIC_DEFAULT_HAIKU_MODEL` as in [Pin model versions](../02-Claude-Code-CLI/google-vertex-ai.md#5-pin-model-versions). Setting `ANTHROPIC_DEFAULT_HAIKU_MODEL` also moves background tasks such as session titles to that model, and that section explains which model runs them otherwise.
- **Model capabilities**: if you pin a model ID that your Claude Code version doesn’t recognize, features such as effort levels or extended thinking can stay disabled on it. Declare what the model supports with [`ANTHROPIC_DEFAULT_OPUS_MODEL_SUPPORTED_CAPABILITIES`](../02-Claude-Code-CLI/model-config.md#customize-pinned-model-display-and-capabilities) and its Sonnet and Haiku counterparts.


[​](#microsoft-foundry)

Microsoft Foundry

Put the gateway’s credential in `ANTHROPIC_FOUNDRY_API_KEY`; it is sent to the gateway as the `x-api-key` header. A gateway that expects a bearer token can take [`ANTHROPIC_FOUNDRY_AUTH_TOKEN`](../02-Claude-Code-CLI/env-vars.md) instead. Claude Code sends that value as the `Authorization: Bearer` header, and it takes precedence over `ANTHROPIC_FOUNDRY_API_KEY` when both are set. Requires Claude Code v2.1.203 or later. For a gateway that injects its own `Authorization` header, set `CLAUDE_CODE_SKIP_FOUNDRY_AUTH=1` and leave both credential variables unset. Claude Code then sends requests without an Azure credential and preserves the `Authorization` header you supply, for example through `ANTHROPIC_CUSTOM_HEADERS`.

- Bash or Zsh

- PowerShell

```python
export ANTHROPIC_FOUNDRY_BASE_URL=https://llm-gateway.example.com/foundry
export ANTHROPIC_FOUNDRY_API_KEY=sk-gateway-key
export CLAUDE_CODE_USE_FOUNDRY=1
```

```python
$env:ANTHROPIC_FOUNDRY_BASE_URL = "https://llm-gateway.example.com/foundry"
$env:ANTHROPIC_FOUNDRY_API_KEY = "sk-gateway-key"
$env:CLAUDE_CODE_USE_FOUNDRY = "1"
```


[​](#claude-platform-on-aws)

Claude Platform on AWS

See [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md) for the workspace ID.

- Bash or Zsh

- PowerShell

```python
export ANTHROPIC_AWS_BASE_URL=https://llm-gateway.example.com/anthropic-aws
export ANTHROPIC_AWS_WORKSPACE_ID=wrkspc_01ABCDEFGHIJKLMN
export CLAUDE_CODE_SKIP_ANTHROPIC_AWS_AUTH=1
export CLAUDE_CODE_USE_ANTHROPIC_AWS=1
```

```python
$env:ANTHROPIC_AWS_BASE_URL = "https://llm-gateway.example.com/anthropic-aws"
$env:ANTHROPIC_AWS_WORKSPACE_ID = "wrkspc_01ABCDEFGHIJKLMN"
$env:CLAUDE_CODE_SKIP_ANTHROPIC_AWS_AUTH = "1"
$env:CLAUDE_CODE_USE_ANTHROPIC_AWS = "1"
```


[​](#confirm-the-provider-route)

Confirm the provider route

Start `claude` from the shell where you set the block and run `/status`. With the Amazon Bedrock block, the **Status** tab shows rows like these:

```python
API provider: Amazon Bedrock
Bedrock base URL: https://llm-gateway.example.com/bedrock
AWS auth skipped
```

The other blocks produce the same rows under their provider’s names, for example `Vertex base URL` and `GCP auth skipped` for Google Cloud’s Agent Platform; the Microsoft Foundry block shows an auth skipped row only if you set `CLAUDE_CODE_SKIP_FOUNDRY_AUTH`. If you also route through a corporate proxy, a `Proxy` row shows the proxy URL. If the base URL row is missing, the variable didn’t reach the session.


[​](#troubleshoot-gateway-errors)

Troubleshoot gateway errors

These are the most common errors when running Claude Code through a gateway, with the gateway-side cause and the fix:

| Error                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | Cause                                                                                                                                                                                                                                                                                                                                                                                                                                       | Fix                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| A startup warning naming two credential sources and ending in `auth may not work as expected`. Older versions show `Auth conflict: Both a token (SOURCE) and an API key (SOURCE) are set` instead.                                                                                                                                                                                                                                                                                                | A gateway credential and a saved login are both active; the variable is used for requests, but the stale login can cause unexpected auth behavior                                                                                                                                                                                                                                                                                           | Unset the variable to use the saved login, or run `/logout` to use the gateway credential                                                                                                                                                                                                                                                                                                                                                                                            |
| `401` errors naming an invalid or unrecognized token                                                                                                                                                                                                                                                                                                                                                                                                                                              | The credential isn’t one the gateway issued, or it’s in a header the gateway doesn’t read                                                                                                                                                                                                                                                                                                                                                   | Confirm the variable matches your credential kind in the [credential table](#set-the-credential-variable), and regenerate the key at the gateway if it was revoked                                                                                                                                                                                                                                                                                                                   |
| `Your apiKeyHelper script is failing`, or `apiKeyHelper failed:` on stderr in non-interactive mode                                                                                                                                                                                                                                                                                                                                                                                                | The command in the [`apiKeyHelper`](../02-Claude-Code-CLI/settings-reference.md#apikeyhelper) setting didn’t produce a usable key, so requests carry a placeholder key                                                                                                                                                                                                                                                                                      | Run the command directly to see why it fails, and re-authenticate with your credential provider if it reports an expired session; see [the error reference](../02-Claude-Code-CLI/errors.md#your-apikeyhelper-script-is-failing)                                                                                                                                                                                                                                                                     |
| `Connection refused — a firewall or proxy may be blocking it (ConnectionRefused)` when nothing answers at the address, or `Can't reach the API server — check your internet or DNS (ENOTFOUND)` when the hostname doesn’t resolve, often after a silent pause while Claude Code [retries with backoff](../02-Claude-Code-CLI/errors.md#automatic-retries). The code in parentheses varies; [Unable to connect to API](../02-Claude-Code-CLI/errors.md#unable-to-connect-to-api) covers the code spellings and the earlier wording | Nothing answered at the base URL: the address is wrong, or a VPN or firewall blocks the path to the gateway                                                                                                                                                                                                                                                                                                                                 | Run the [curl test above](#verify-the-connection), which fails immediately with the same cause, and confirm the URL and network path with your gateway team                                                                                                                                                                                                                                                                                                                          |
| `API returned an empty or malformed response (HTTP 200)`                                                                                                                                                                                                                                                                                                                                                                                                                                          | The gateway or an intermediate proxy returned a non-API response, often an HTML error or login page                                                                                                                                                                                                                                                                                                                                         | Test with the [curl request above](#verify-the-connection); fix the gateway route that answers with something other than a Claude API response. [The error reference](../02-Claude-Code-CLI/errors.md#api-returned-an-empty-or-malformed-response) explains the detail the message reports                                                                                                                                                                                                           |
| `400` errors naming `context_management`, `Extra inputs are not permitted`, or other unrecognized fields                                                                                                                                                                                                                                                                                                                                                                                          | The gateway forwards requests to an upstream that rejects fields Claude Code sends to Anthropic-format endpoints                                                                                                                                                                                                                                                                                                                            | Set `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`, which suppresses most pre-release fields; see [feature pass-through](llm-gateway-protocol.md#feature-pass-through). Some betas aren’t gated by this flag; for those, set the matching `CLAUDE_CODE_USE_*` provider variable so Claude Code sends only what that provider accepts. For steps that depend on which field the message names, see [Extra inputs are not permitted](../02-Claude-Code-CLI/errors.md#extra-inputs-are-not-permitted) |
| `400` errors naming `thinking` or `adaptive`, such as `Input tag 'adaptive' found`                                                                                                                                                                                                                                                                                                                                                                                                                | The upstream model build doesn’t accept adaptive reasoning, which Claude Code requests for Claude Opus 4.6, Claude Sonnet 4.6, Claude Haiku 5.5, Claude Fable 5, and later models                                                                                                                                                                                                                                                           | Upgrade the gateway’s upstream. On Opus 4.6 and Sonnet 4.6, `CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1` works instead. The [model configuration](../02-Claude-Code-CLI/model-config.md) capability variables apply only to the provider configurations, such as `CLAUDE_CODE_USE_BEDROCK` and `CLAUDE_CODE_USE_VERTEX`, not behind an `ANTHROPIC_BASE_URL` gateway                                                                                                                                     |
| `400` errors stating a context or token limit in the gateway’s own words, such as `ContextWindowExceededError` or `prompt token count of N exceeds the limit of M`                                                                                                                                                                                                                                                                                                                                | The gateway enforces a smaller context than the model’s native window and rewrites the upstream error, so Claude Code doesn’t recognize it as a [too-long error](../02-Claude-Code-CLI/errors.md#prompt-is-too-long) and doesn’t compact and retry automatically                                                                                                                                                                                            | Run `/compact` to recover the session. To prevent it, set `CLAUDE_CODE_AUTO_COMPACT_WINDOW` to the gateway’s limit; Claude Code clamps the value to at least 100,000 tokens and at most the model’s context window, so you can’t match a gateway limit below 100,000, and `/compact` remains the recovery there. Also set `CLAUDE_CODE_MAX_OUTPUT_TOKENS` below the gateway model’s output limit                                                                                     |
| `400` errors on every request, in the gateway’s own words rejecting a tool’s input schema or its `pattern`, on Claude Code v2.1.265 through v2.1.267                                                                                                                                                                                                                                                                                                                                              | In a gradual rollout on those versions, the [Artifact tool](../02-Claude-Code-CLI/artifacts.md#availability) schema carries a regular expression with `\p{...}` Unicode character classes. The Anthropic API accepts it, but a gateway or upstream that checks each tool schema’s `pattern` with its own regex engine rejects the whole request                                                                                                             | Update to v2.1.268 or later, which doesn’t send the regular expression. On an affected version, [turn artifacts off](../02-Claude-Code-CLI/artifacts.md#disable-artifacts), which removes the tool and its schema from requests                                                                                                                                                                                                                                                                      |
| `400` errors on every request, in the gateway’s own words rejecting an unrecognized tool type, such as `Input tag 'advisor_20260301'`, on Claude Code v2.1.275                                                                                                                                                                                                                                                                                                                                    | In a gradual rollout on that version, requests carry an [advisor tool](../02-Claude-Code-CLI/advisor.md) entry even with the advisor off. The Anthropic API accepts it, but a gateway or upstream that validates tool types rejects the whole request; one that [forwards request body fields unchanged](llm-gateway-protocol.md#forward-as-open-lists) passes it through unaffected. The entry is a declaration that carries no conversation content | Update to v2.1.276 or later, which doesn’t send the entry behind an `ANTHROPIC_BASE_URL` gateway unless you turn the advisor on. On v2.1.275, set [`CLAUDE_CODE_DISABLE_ADVISOR_TOOL=1`](../02-Claude-Code-CLI/env-vars.md), which removes the entry from requests                                                                                                                                                                                                                                   |
| Models missing from the `/model` picker                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Gateway model names aren’t in Claude Code’s built-in list, or Claude Code is showing a [`modelPicker`](../02-Claude-Code-CLI/settings-reference.md#modelpicker) lineup that replaces the built-in options                                                                                                                                                                                                                                                   | Enable [gateway model discovery](#add-gateway-models-to-the-model-picker) or add names with the [model configuration](../02-Claude-Code-CLI/model-config.md) variables. If Claude Code shows a replacing `modelPicker` lineup, add the gateway models to it, or ask your administrator to add them when managed settings supply it                                                                                                                                                                   |
| `/fast` reports `Fast mode unavailable due to network connectivity issues` while inference requests work                                                                                                                                                                                                                                                                                                                                                                                          | The [fast mode](../02-Claude-Code-CLI/fast-mode.md) availability check goes directly to `api.anthropic.com` and doesn’t follow `ANTHROPIC_BASE_URL`, so blocked direct egress fails the check. The same message appears on an open network when the check presents a gateway-issued key from `ANTHROPIC_API_KEY` or an `apiKeyHelper` and Anthropic rejects it                                                                                              | Allowlist `api.anthropic.com` if egress is blocked, or set a skip variable; for a rejected gateway key only the skip variables help. See [use fast mode behind proxies and LLM gateways](../02-Claude-Code-CLI/fast-mode.md#use-fast-mode-behind-proxies-and-llm-gateways)                                                                                                                                                                                                                           |
| `/fast` reports `Fast mode has been disabled by your organization` in a session authenticated with `ANTHROPIC_AUTH_TOKEN`, even though the organization has fast mode enabled                                                                                                                                                                                                                                                                                                                     | The availability check requires a claude.ai login or an Anthropic API key; with only a bearer token, Claude Code treats fast mode as disabled without sending the check                                                                                                                                                                                                                                                                     | Set `CLAUDE_CODE_SKIP_FAST_MODE_ORG_CHECK=1`; see [use fast mode behind proxies and LLM gateways](../02-Claude-Code-CLI/fast-mode.md#use-fast-mode-behind-proxies-and-llm-gateways)                                                                                                                                                                                                                                                                                                                  |
| Claude Code asks you to log in even though the [curl test](#verify-the-connection) succeeds                                                                                                                                                                                                                                                                                                                                                                                                       | The CLI has no credential of its own: a reachable base URL isn’t one, and in an interactive session an `env` block in a project’s `.claude/settings.json` or `.claude/settings.local.json` applies only after the first-run wizard and [trust prompt](../02-Claude-Code-CLI/permissions.md#what-runs-before-you-trust-a-folder)                                                                                                                             | Set `ANTHROPIC_AUTH_TOKEN` somewhere Claude Code reads before first-run setup: a shell export, the `env` block in `~/.claude/settings.json`, or managed settings                                                                                                                                                                                                                                                                                                                     |
| `ANTHROPIC_API_KEY` is set but ignored, with no prompt                                                                                                                                                                                                                                                                                                                                                                                                                                            | The key needs a one-time approval in interactive sessions, and a previously declined key is ignored without asking again                                                                                                                                                                                                                                                                                                                    | Enable it under `/config` with the `Use custom API key` option                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `This machine's managed settings require a first-party login`, or [`Administrator policy requires a Cloud gateway sign-in`](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in) when managed settings set `forceLoginMethod` to `"gateway"` or also set `forceLoginGatewayUrl`                                                                                                                                                                                                 | Managed settings include `forceLoginMethod` or `forceLoginOrgUUID`, which cannot coexist with `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, or `apiKeyHelper`                                                                                                                                                                                                                                                                                | Your administrator must remove `forceLoginMethod`, `forceLoginOrgUUID`, and `forceLoginGatewayUrl` from managed settings to use gateway credentials, or remove the gateway credential and use the sign-in the managed settings require. The two cannot be combined                                                                                                                                                                                                                   |
| `403` with an HTML body such as `403 Forbidden`, when the gateway’s own logs show no request received                                                                                                                                                                                                                                                                                                                                                                                             | A web application firewall or reverse proxy in front of the gateway blocked the request body before it reached the gateway. Claude Code prompts include XML-style tags and source code that match cross-site-scripting body rules, so a short curl test passes while a real session doesn’t                                                                                                                                                 | Exempt the gateway’s `/v1/messages` path from request-body inspection. On AWS WAF this is the `CrossSiteScripting_Body` managed rule; on nginx with ModSecurity it is the equivalent OWASP CRS body rules                                                                                                                                                                                                                                                                            |
| Certificate or TLS errors such as `SSL certificate verification failed` or `Self-signed certificate detected`, when the [curl test](#verify-the-connection) succeeds                                                                                                                                                                                                                                                                                                                              | Claude Code’s runtime isn’t trusting the same certificate authority that `curl` uses. Common behind corporate TLS-inspection proxies                                                                                                                                                                                                                                                                                                        | Set `NODE_EXTRA_CA_CERTS` to the CA bundle path; see [CA certificate store](network-config.md#ca-certificate-store)                                                                                                                                                                                                                                                                                                                                                            |

If Claude Code prompts you to log in repeatedly after removing gateway configuration, the cause is usually credential storage rather than the gateway; see [authentication errors](../02-Claude-Code-CLI/errors.md#authentication-errors).


[​](#related-resources)

Related resources

- [LLM gateways overview](llm-gateway.md): what a gateway is and how it interacts with claude.ai subscriptions
- [Roll out an LLM gateway for your organization](llm-gateway-rollout.md): the admin-facing checklist for deploying and distributing gateway configuration
- [Gateway compatibility guide](llm-gateway-protocol.md): what Claude Code sends to a gateway, including the headers and fields the gateway must forward
- [Settings](../02-Claude-Code-CLI/settings.md): where settings files live and how the `env` block is read
- [Authentication](../02-Claude-Code-CLI/authentication.md): how credential variables, `apiKeyHelper`, and OAuth login interact

---

## Claude Code gateway compatibility guide - Claude Code Docs

Source: https://code.claude.com/docs/en/llm-gateway-protocol
Path: 13-Enterprise-Admin/llm-gateway-protocol.md

## On this page

- [API formats](#api-formats)
  - [Foundry and Claude Platform on AWS](#foundry-and-claude-platform-on-aws)
  - [Optional endpoints and startup traffic](#optional-endpoints-and-startup-traffic)
  - [Streaming](#streaming)
  - [Format mismatch with the upstream](#format-mismatch-with-the-upstream)
- [How the connection method changes client behavior](#how-the-connection-method-changes-client-behavior)
  - [Requests and defaults by connection method](#requests-and-defaults-by-connection-method)
  - [Settings for unrecognized model IDs](#settings-for-unrecognized-model-ids)
- [Request headers](#request-headers)
  - [Gateway hint headers](#gateway-hint-headers)
  - [Forward as open lists](#forward-as-open-lists)
- [Response headers](#response-headers)
- [System prompt attribution block](#system-prompt-attribution-block)
- [Feature pass-through](#feature-pass-through)
  - [Beta header and body field pairs](#beta-header-and-body-field-pairs)
  - [Automatic retry and error forwarding](#automatic-retry-and-error-forwarding)
  - [Retry without tool search](#retry-without-tool-search)
  - [Disable pre-release capabilities](#disable-pre-release-capabilities)
- [Model discovery](#model-discovery)
  - [When discovery runs](#when-discovery-runs)
  - [Request and response](#request-and-response)
  - [Picker entries and caching](#picker-entries-and-caching)
- [Related resources](#related-resources)

Other gateways

# Claude Code gateway compatibility guide

Copy pageCopy page

Keep an LLM gateway compatible with Claude Code: the endpoints it calls, the headers and body fields to forward, and what breaks when they’re stripped.

Copy pageCopy page

This page documents the requests Claude Code sends to a gateway, including the endpoints it calls, the headers and body fields the gateway must forward, and which features stop working when it doesn’t. It is written for operators configuring a gateway product to work with Claude Code. The [Claude apps gateway](claude-apps-gateway.md), Anthropic’s self-hosted gateway, serves its own endpoint reference at `GET /protocol`, covering that gateway’s sign-in, inference, managed settings, model discovery, and telemetry endpoints. It is a separate document from this guide.

- To roll out an existing or third-party gateway for your organization, see [Roll out an LLM gateway](llm-gateway-rollout.md)
- If you’re an individual developer authenticating Claude Code to a gateway with a credential you were given, see [Connect Claude Code to an LLM gateway](llm-gateway-connect.md)

This page covers:

- [API formats](#api-formats) and the endpoints to serve for each
- [Client behavior by connection method](#how-the-connection-method-changes-client-behavior): how model IDs, `anthropic-beta` values, request fields, and defaults differ between the formats and a Claude apps gateway sign-in
- [Request headers](#request-headers): which must reach the upstream and which your gateway can consume
- [Response headers](#response-headers): what to return so stall detection, retries, and usage limit display work
- The [system prompt attribution block](#system-prompt-attribution-block) and how it interacts with prompt caching
- [Feature pass-through](#feature-pass-through): what breaks when headers or body fields are stripped
- [Model discovery](#model-discovery)

This page uses two terms for what your gateway does with each header and body field:

- **Forward unchanged**: pass it to the upstream byte-for-byte
- **Consume**: the gateway may read it for routing, attribution, or tracing and need not forward it

Anything not marked forward unchanged is yours to consume or ignore.


[​](#api-formats)

API formats

A gateway must expose at least one of the following API formats to Claude Code clients. A client picks a format and points Claude Code at your gateway with the variables in the Selected by column of the table below. Google Cloud’s Agent Platform is Google Cloud’s Claude endpoint, formerly Vertex AI; its variable names keep the `VERTEX` spelling.

| Format                                   | Selected by                                                   | Endpoints                                                                                                       | Forward unchanged                                                                                        |
|:-----------------------------------------|:--------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------|
| Anthropic Messages                       | `ANTHROPIC_BASE_URL`                                          | `/v1/messages`, `/v1/messages/count_tokens` (optional)                                                          | `anthropic-beta` and `anthropic-version` request headers                                                 |
| Amazon Bedrock InvokeModel               | `ANTHROPIC_BEDROCK_BASE_URL` with `CLAUDE_CODE_USE_BEDROCK=1` | `/model/{model}/invoke`, `/model/{model}/invoke-with-response-stream`, `/model/{model}/count-tokens` (optional) | `anthropic_beta` and `anthropic_version` request body fields                                             |
| Google Cloud’s Agent Platform rawPredict | `ANTHROPIC_VERTEX_BASE_URL` with `CLAUDE_CODE_USE_VERTEX=1`   | `:rawPredict`, `:streamRawPredict`, `count-tokens:rawPredict` (optional)                                        | `anthropic-beta` and `anthropic-version` request headers, and the `anthropic_version` request body field |


[​](#foundry-and-claude-platform-on-aws)

Foundry and Claude Platform on AWS

Microsoft Foundry and the [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md) implement the Anthropic Messages format. Claude Code routes to them through their own variables, `ANTHROPIC_FOUNDRY_BASE_URL` and `ANTHROPIC_AWS_BASE_URL`, but a gateway fronting either implements the Anthropic Messages row above. A gateway fronting the Claude Platform on AWS must also forward the `anthropic-workspace-id` header, which [that platform requires on every request](../02-Claude-Code-CLI/claude-platform-on-aws.md).


[​](#optional-endpoints-and-startup-traffic)

Optional endpoints and startup traffic

Token-counting endpoints are the only optional ones: when they’re absent, Claude Code falls back to a character-based estimate of context usage. Match on the path, not the full URL:

- Inference requests post to `/v1/messages?beta=true`
- The Google Cloud’s Agent Platform method suffixes attach to the publisher model path, as in `/projects/{project}/locations/{location}/publishers/anthropic/models/{model}:streamRawPredict`

A gateway also sees best-effort startup traffic it can reject without breaking anything. An Anthropic Messages-format gateway receives a `HEAD /api/hello` connection-warming probe, which Claude Code skips when an HTTP proxy or client certificate is configured. An Amazon Bedrock-format gateway receives a `GET /inference-profiles?type=SYSTEM_DEFINED` request and, when the configured model is an inference profile, `GET /inference-profiles/{profile}` lookups. Sessions that go through a gateway aren’t eligible for the HIPAA configuration. [Check how developers sign in and connect](../02-Claude-Code-CLI/hipaa-setup.md#check-how-developers-sign-in-and-connect) lists which sign-in and connection methods are. See [The HIPAA configuration behind a gateway](llm-gateway-rollout.md#the-hipaa-configuration-behind-a-gateway). The [fast mode](../02-Claude-Code-CLI/fast-mode.md) availability check never appears in gateway logs: it calls `api.anthropic.com` directly rather than following `ANTHROPIC_BASE_URL`, so on a network that blocks direct egress to `api.anthropic.com`, fast mode can report a connectivity error while inference through the gateway keeps working. The [WebFetch domain safety check](data-usage.md#webfetch-domain-safety-check) also calls `api.anthropic.com` directly. [Use fast mode behind proxies and LLM gateways](../02-Claude-Code-CLI/fast-mode.md#use-fast-mode-behind-proxies-and-llm-gateways) covers the variables that restore it.


[​](#streaming)

Streaming

Claude Code reads each streaming inference response event by event as it arrives, so the way your gateway relays the stream affects what the user sees:

- If your gateway buffers a response until it is complete, Claude Code stalls.
- Claude Code expects each response’s full event sequence, in order, through the final `message_delta` and `message_stop` events. If the body ends cleanly after a content block has started but before that final `message_delta`, Claude Code treats the response as a dropped connection. [The response above may be incomplete](../02-Claude-Code-CLI/errors.md#the-response-above-may-be-incomplete) describes what the user sees then, and [Automatic retries](../02-Claude-Code-CLI/errors.md#automatic-retries) says when Claude Code re-issues the request instead.
- When an Amazon Bedrock guardrail blocks a reply, the events Bedrock sends can reference a content block whose `content_block_stop` already arrived, and Claude Code relies on receiving them as sent. [AWS Guardrails](../02-Claude-Code-CLI/amazon-bedrock.md#aws-guardrails) describes how that reply ends.
- Claude Code aborts a streaming response once no bytes reach it for longer than its [streaming idle timeout](network-config.md#streaming-idle-watchdogs). During a long thinking pause, the upstream’s SSE `ping` events can be the only bytes on the stream, so a gateway that strips or buffers them can trip that timeout partway through a response. A gateway that translates from an upstream without pings, such as Amazon Bedrock’s binary event stream, has the same gap unless it emits its own `ping` events.
- On the [Amazon Bedrock InvokeModel format](#api-formats), Claude Code reads the `/model/{model}/invoke-with-response-stream` response as the binary `application/vnd.amazon.eventstream` body Bedrock returns, and can’t parse it once a gateway converts it to server-sent events or rewrites that `Content-Type` header. [Streaming errors behind a gateway or proxy](../02-Claude-Code-CLI/amazon-bedrock.md#streaming-errors-behind-a-gateway-or-proxy) describes what the user sees then.


[​](#format-mismatch-with-the-upstream)

Format mismatch with the upstream

Which format the client speaks determines what your gateway receives. The common failure mode is a mismatch between the format the client sends to your gateway and the format the upstream provider behind it accepts.

- When the client speaks the Amazon Bedrock or Google Cloud’s Agent Platform format, Claude Code sends only the subset of its full capability set that those providers accept
- When the client speaks the Anthropic Messages format, Claude Code sends the full set, even if your gateway forwards to an Amazon Bedrock or Google Cloud’s Agent Platform upstream

Bridging that difference is your gateway’s job. [Feature pass-through](#feature-pass-through) describes what breaks when it doesn’t. If your upstream is Amazon Bedrock or Google Cloud’s Agent Platform, you can avoid the bridging by exposing that provider’s format instead. [Route to a cloud provider through a gateway](llm-gateway-connect.md#route-to-a-cloud-provider-through-a-gateway) shows the client configuration for that format.


[​](#how-the-connection-method-changes-client-behavior)

How the connection method changes client behavior

The way a developer connects to your gateway determines which model IDs, `anthropic-beta` values, and request fields Claude Code sends, and which defaults it applies. Your gateway sees one of three client behaviors:

- **Amazon Bedrock or Agent Platform format**: the developer sets `CLAUDE_CODE_USE_BEDROCK=1` with `ANTHROPIC_BEDROCK_BASE_URL`, or `CLAUDE_CODE_USE_VERTEX=1` with `ANTHROPIC_VERTEX_BASE_URL`, pointing at your gateway. Claude Code uses that provider’s model IDs, request fields, and defaults.
- **Anthropic Messages format**: the developer sets `ANTHROPIC_BASE_URL` to your gateway. Claude Code treats the gateway as the Claude API and can’t tell which upstream you forward to.
- **Claude apps gateway sign-in**: the developer signs in to a [Claude apps gateway](claude-apps-gateway.md). That gateway speaks the Anthropic Messages format but can route to any upstream, so Claude Code sends only the `anthropic-beta` values and model-capability assumptions that Amazon Bedrock and Agent Platform also accept.


[​](#requests-and-defaults-by-connection-method)

Requests and defaults by connection method

The table below compares the three connection methods, one behavior per row. It leaves out Microsoft Foundry and Claude Platform on AWS, which also use the Anthropic Messages format but which Claude Code reaches through their own variables. For those, see the [Microsoft Foundry](../02-Claude-Code-CLI/microsoft-foundry.md) and [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md) pages.

| Behavior                                                                                                            | Amazon Bedrock or Agent Platform format                                                                                                                                                                                     | Anthropic Messages format                                                                                                                                                              | Claude apps gateway sign-in                                                                                                                   |
|:--------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------|
| Model IDs in requests by default                                                                                    | The provider’s form, such as `us.anthropic.claude-opus-4-8` on Amazon Bedrock                                                                                                                                               | Anthropic IDs, such as `claude-opus-4-8`                                                                                                                                               | Anthropic IDs                                                                                                                                 |
| `anthropic-beta` values sent                                                                                        | The subset Amazon Bedrock and Agent Platform accept                                                                                                                                                                         | The full set described under [feature pass-through](#feature-pass-through), unless the developer sets [`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`](#disable-pre-release-capabilities)    | The subset Amazon Bedrock and Agent Platform accept                                                                                           |
| Request fields for a model ID Claude Code doesn’t recognize, such as a gateway alias                                | Thinking with a fixed budget rather than adaptive reasoning, and no effort or context management fields. Claude Code still sends [mid-conversation system messages](#feature-pass-through)                                  | Everything current Claude models accept on the Claude API, including adaptive reasoning, effort, and context management, which an Amazon Bedrock or Agent Platform upstream can reject | Same as the Amazon Bedrock or Agent Platform format                                                                                           |
| One-hour [prompt cache TTL](../02-Claude-Code-CLI/prompt-caching.md#choose-the-ttl-yourself) when a developer opts in               | Requested through the `ttl` field in `cache_control`, with no beta value                                                                                                                                                    | Requested through the `ttl` field plus an `extended-cache-ttl` value in `anthropic-beta`, which you must forward                                                                       | Requested through the `ttl` field, with no beta value. See [One-hour prompt cache](claude-apps-gateway-config.md#one-hour-prompt-cache) |
| Model for [background tasks](../17-Billing-Plans/costs.md#background-token-usage) unless `ANTHROPIC_DEFAULT_HAIKU_MODEL` pins one | The default Sonnet model, or the main model once one is selected, as the [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md#4-pin-model-versions) and [Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md#5-pin-model-versions) pages describe | The main model, or the default Haiku model when `ANTHROPIC_API_KEY` or `apiKeyHelper` supplies an Anthropic Console key and `ANTHROPIC_AUTH_TOKEN` is unset                            | The main model                                                                                                                                |

For the features each connection supports and the telemetry it sends to Anthropic by default, see [Feature availability](../02-Claude-Code-CLI/feature-availability.md#availability-by-model-provider) and [Default behaviors by API provider](data-usage.md#default-behaviors-by-api-provider).


[​](#settings-for-unrecognized-model-ids)

Settings for unrecognized model IDs

Two client-side settings change what Claude Code assumes for a model ID it doesn’t recognize, whichever connection method the developer uses:

- **Context window**: Claude Code assumes 200K, or 1M when the ID carries `[1m]`. To declare the real window, see [Correct the window for a gateway or custom model ID](../02-Claude-Code-CLI/model-config.md#correct-the-window-for-a-gateway-or-custom-model-id)
- **Capabilities**: to give a gateway alias the capabilities of the model behind it, map that model’s Anthropic ID to your alias with a [`modelOverrides`](../02-Claude-Code-CLI/errors.md#unrecognized-model-id-on-a-request) entry in the settings you distribute. For where the `ANTHROPIC_DEFAULT_*_MODEL_SUPPORTED_CAPABILITIES` variables apply, see [feature pass-through](#feature-pass-through)


[​](#request-headers)

Request headers

Claude Code includes these headers on API requests. Header names are case-insensitive on the wire. Forward `anthropic-version` and `anthropic-beta` unchanged, plus `anthropic-workspace-id` when the upstream is the [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md); the rest the gateway may consume for routing, attribution, and tracing, and need not forward.

| Header                          | Description                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|:--------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `Authorization`, `x-api-key`    | The developer’s gateway credential, in one or both headers depending on which [credential variable](llm-gateway-connect.md#set-the-credential-variable) they set                                                                                                                                                                                                                                                                             |
| `anthropic-version`             | API version, currently `2023-06-01`. Amazon Bedrock- and Google Cloud’s Agent Platform-format requests also carry the `anthropic_version` body field, whose value is the provider dialect string, not this header’s value                                                                                                                                                                                                                          |
| `anthropic-beta`                | Comma-separated capability values for the request. Forward the header verbatim; don’t allowlist individual values, because the set changes with Claude Code releases. When the developer authenticates with a claude.ai login, which is possible when `ANTHROPIC_BASE_URL` is set without a gateway credential variable, this header also carries an OAuth capability that the upstream requires, and stripping it fails those requests with `401` |
| `x-claude-code-session-id`      | A unique identifier for the current Claude Code session. Use it to aggregate all requests from one session without parsing request bodies                                                                                                                                                                                                                                                                                                          |
| `x-claude-code-agent-id`        | Identifier of the [subagent](../09-Agents-Patterns/sub-agents.md) that issued the request, present only on requests from an agent Claude Code spawned inside the session. Use it with the session ID to attribute cost to parallel agents                                                                                                                                                                                                                          |
| `x-claude-code-parent-agent-id` | Identifier of the agent that spawned the requesting agent, present only for nested agents                                                                                                                                                                                                                                                                                                                                                          |

Subagent IDs are generated fresh each time Claude Code spawns a subagent. Teammate agents, the named members of an [agent team](../09-Agents-Patterns/agent-teams.md), reuse a stable name-based ID across reconnections. In both cases the ID identifies an agent, not a person or a device, so don’t treat the agent ID header as a user identifier. If your developers set `ANTHROPIC_CUSTOM_HEADERS`, those headers appear on requests as well.


[​](#gateway-hint-headers)

Gateway hint headers

Claude Code can also send routing hints: per-request facts a gateway or router can use to schedule, cache, or attribute a request. Requires Claude Code v2.1.273 or later. Whether a request carries them depends on where Claude Code sends it:

- Direct connection to the Anthropic API: sent by default
- Custom base URL: off by default, because a proxy that rejects unknown headers would fail the request. To receive them, set [`CLAUDE_CODE_GATEWAY_HINT_HEADERS=1`](../02-Claude-Code-CLI/env-vars.md) for your developers, for example in the `env` block of [managed settings](managed-settings.md)
- Any other backend, including Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, and Claude Platform on AWS: sent only when `CLAUDE_CODE_GATEWAY_HINT_HEADERS=1` is set

Setting `CLAUDE_CODE_GATEWAY_HINT_HEADERS` to `0` stops the headers on every connection. The headers carry only what the rows below list: fixed vocabularies, tool names, durations, and a random prompt identifier, never prompt text or file contents. Every value is printable ASCII.

| Header                              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
|:------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `x-claude-code-request-class`       | What kind of request this is: `main` for a turn of the main conversation, `subagent` for a turn of a [subagent](../09-Agents-Patterns/sub-agents.md), `workflow` for an agent running inside a workflow, `compaction` for the summarization request that compacts a conversation, or `auxiliary` for side requests such as session titles, classifiers, and summaries. Sent on every request                                                                                                                       |
| `x-claude-code-agent-type`          | The kind of subagent that issued the request: a built-in agent type name such as `Explore`, `Plan`, or `general-purpose`, or `custom` for a user-defined agent, `teammate` for an [agent team](../09-Agents-Patterns/agent-teams.md) member running in the lead’s process, or `fork` for a [fork](../09-Agents-Patterns/sub-agents.md#fork-the-current-conversation). Present only on a subagent’s own turns; a subagent’s compaction or side requests keep the agent ID but carry no type. A user-chosen agent name is never sent |
| `x-claude-code-compaction`          | Present on the request that summarizes the conversation during a [compaction](../02-Claude-Code-CLI/prompt-caching.md#compacting-the-conversation). The value says what triggered it: `auto` when the context window approached capacity, `manual` for `/compact`, or `reactive` when the API rejected a request as too long. The value is also `auto` for [idle compaction](../02-Claude-Code-CLI/settings-reference.md#idlecompaction). Absent on every other request                                                            |
| `x-claude-code-context-compacted`   | Present once, on the first main-conversation request after a compaction, with the same values as `x-claude-code-compaction`. The conversation prefix before this request is no longer used, so a cache keyed on it can be dropped                                                                                                                                                                                                                                                                  |
| `x-claude-code-prev-tool-durations` | Measured run time of the tool calls whose results this request carries, as `<name>=<ms>;<name>=<ms>`, for example `Bash=742;Read=9`. Sent on the next request of the same conversation after a batch of tool calls, from the main session or a subagent                                                                                                                                                                                                                                            |
| `x-claude-code-prompt-id`           | Random UUID that identifies the user prompt a request serves. Requests serving one prompt share the value, including the turns of subagents that prompt started. Requests not attributed to a prompt omit it. Use it to group a session’s requests by prompt. Requires Claude Code v2.1.283 or later                                                                                                                                                                                               |

Before parsing `x-claude-code-prev-tool-durations`, check how Claude Code builds the value and what it leaves out:

- Entries: one per tool call that ran, in the order its result was collected, in whole milliseconds
- Cap: Claude Code sends at most 32 entries and 4 KB, keeping the first entries
- Encoding: tool names are percent-encoded, covering `%`, `;`, `=`, comma, space, and any character outside printable ASCII
- Parsing: split on `;`, then on `=`, and decode each name
- Absence: compaction calls, side requests, and the first request of a new prompt never carry it. Don’t read a missing header as a turn that ran no tools
- Times: each one excludes permission prompts and hooks, and parallel tool calls each report their own time, so the entries don’t add up to the gap between requests


[​](#forward-as-open-lists)

Forward as open lists

Treat the headers and body fields as open lists, not closed ones. Claude Code gains capabilities over releases, and they arrive as new `anthropic-beta` values, new request body fields, and occasionally new `anthropic-*` or `x-claude-code-*` headers. When forwarding to an Anthropic-format upstream, pass `anthropic-*` request headers and request body fields through unchanged rather than allowlisting the ones you see today. A gateway pinned to an observed list strips the next capability’s header or field and breaks it on the release that introduces it. The exception is a non-Anthropic upstream such as Amazon Bedrock or Google Cloud’s Agent Platform, where bridging the schema difference is the gateway’s job; see [feature pass-through](#feature-pass-through).


[​](#response-headers)

Response headers

Claude Code reads these response headers to detect stalled streams, to decide whether and when to retry, and to show usage limits. The table lists what to return for each. Also forward error response bodies unmodified, so Claude Code’s [capability-rejection recovery](#automatic-retry-and-error-forwarding) can match the upstream’s error wording.

| Header                          | What to return and why                                                                                                                                                                                                                                                                                                                         |
|:--------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `content-type`                  | Return `text/event-stream` on streamed Anthropic Messages-format responses, and `application/vnd.amazon.eventstream`, unmodified, on Amazon Bedrock-format responses, where [a different type fails the request](../02-Claude-Code-CLI/amazon-bedrock.md#streaming-errors-behind-a-gateway-or-proxy)                                                           |
| `retry-after`                   | Return integer seconds rather than an HTTP date. Claude Code waits at least that long before the next [automatic retry](../02-Claude-Code-CLI/errors.md#automatic-retries), and outside [`CLAUDE_CODE_RETRY_WATCHDOG`](../02-Claude-Code-CLI/env-vars.md) sessions a value above 60 stops the retries and shows the error at once                                              |
| `x-should-retry`                | Pass the upstream’s value through unchanged. Claude Code reads this header as one input when deciding whether to retry a failed request: `true` marks the response retryable and `false` marks it not retryable. For retry counts, backoff, and which failures Claude Code retries, see [automatic retries](../02-Claude-Code-CLI/errors.md#automatic-retries) |
| `anthropic-ratelimit-unified-*` | Forward the upstream’s values unchanged on every response. Claude Code reads them on successful responses to show usage against plan limits to developers signed in with claude.ai, and on a `429` to tell a plan limit or spend cap from a temporary throttle; see [usage limits](../02-Claude-Code-CLI/errors.md#usage-limits)                               |


[​](#system-prompt-attribution-block)

System prompt attribution block

Claude Code prepends a short attribution block to the system prompt containing the client version and a fingerprint derived from the conversation. The `api.anthropic.com` endpoint strips the block before processing when it arrives unchanged as the first system block, so it doesn’t affect first-party prompt caching. Any other upstream receives it as part of the prompt. The strip is positional, so it only works when the gateway forwards the `system` array unchanged. To keep the block out of the prompt without losing other system content:

- Forward the `system` array exactly as received, keeping the block first: prepending another system block, reordering the array, or converting it to a single string defeats the strip, and the block then reaches the model and the prompt cache key.
- Keep the block in its own array entry: the endpoint treats a merged block that starts with the attribution header as attribution in its entirety and drops everything merged into it, including the rest of the system prompt.
- If your gateway must reshape system content, set [`CLAUDE_CODE_ATTRIBUTION_HEADER=0`](../02-Claude-Code-CLI/env-vars.md) so Claude Code omits the block. Anthropic and the cloud providers’ Claude endpoints read the block for attribution, so omit it at the client rather than stripping or moving it in the gateway.

The variable exists for gateway and third-party caching compatibility, not as a privacy control: on a direct connection the full request already goes to the Anthropic API either way. When both of these hold, Claude Code keeps the block on [auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) classifier requests even when you set the variable to `0`:

- The requests go to `api.anthropic.com`, with `ANTHROPIC_BASE_URL` unset or naming that host and no third-party provider selected.
- The active credential isn’t an [Anthropic profile or federation credential](../02-Claude-Code-CLI/authentication.md#anthropic-profiles-and-federation-credentials).

Classifier requests skip the rest of Claude Code’s system prompt, so on those requests the block is the only marker in the request body that identifies them as Claude Code traffic. When either condition fails, through an LLM gateway, on a third-party provider, or with a profile or federation credential active, setting `0` removes the block from classifier requests too. Before v2.1.229, this exception didn’t exist: setting `0` removed the block from those classifier requests, and when the API declined the unidentified requests, auto mode failed on every action it sent to the classifier. From Claude Code v2.1.181, the block is stable for the lifetime of a conversation when requests route through a custom base URL, so a gateway-side prompt cache keyed on the full request body works without disabling it, and any provider your gateway forwards to receives a stable prompt prefix. Before v2.1.181 the block included a per-request token that changed the start of the system prompt on every request. On those versions, set `CLAUDE_CODE_ATTRIBUTION_HEADER=0` when your gateway does either of these:

- Implements a prompt cache keyed on the request body.
- Forwards requests to a third-party provider such as Amazon Bedrock, Microsoft Foundry, or Google Cloud’s Agent Platform, in the Anthropic Messages format or the provider’s own, where the changing prefix reduces prompt-cache reuse on that provider.


[​](#feature-pass-through)

Feature pass-through

Claude Code treats an `ANTHROPIC_BASE_URL` gateway as an Anthropic-format endpoint and sends it the beta headers and request body fields it sends to `api.anthropic.com`, except a small set of diagnostics and defaults reserved for direct connections, such as the fine-grained tool streaming default covered below. That set varies by release, so don’t depend on its contents. Fine-grained tool streaming is one of the direct-connection defaults: it is off by default whenever requests route through a custom base URL, and a gateway receives it when developers set [`CLAUDE_CODE_ENABLE_FINE_GRAINED_TOOL_STREAMING=1`](../02-Claude-Code-CLI/env-vars.md).


[​](#beta-header-and-body-field-pairs)

Beta header and body field pairs

Capabilities that add body fields pair them with a beta header, and the pair travels together. A gateway that strips the header while passing the body, or forwards an Anthropic-format body to an upstream with a different schema, produces `400` errors. When both halves are absent together, the feature turns off quietly. A gateway that rewrites or redacts request bodies for content inspection breaks the pairing the same way stripping does, so inspect without modifying. The table notes where a feature deviates from the pairing.

| Feature                                                                                                                                                                                                                                          | Header and body pair                                                                                                                                                                                                                                                          | Symptom when broken                                                                                                                                                                                                                                      | Remediation                                                                                                                                                                                                                                                                                                                                                             |
|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Adaptive reasoning](../02-Claude-Code-CLI/model-config.md#adjust-effort-level)                                                                                                                                                                                  | No beta header. Claude Code sends `thinking: {"type": "adaptive"}` for Claude Opus 4.6, Claude Sonnet 4.6, Claude Haiku 5.5, Claude Fable 5, and later models, and treats model names it doesn’t recognize, such as gateway aliases, as current models that receive the field | `400` naming the `thinking` field or the `adaptive` tag when the upstream model build doesn’t accept it                                                                                                                                                  | Upgrade the upstream. On Opus 4.6 and Sonnet 4.6, developers can set `CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1` instead                                                                                                                                                                                                                                                  |
| [Context management](../04-API-Reference/Guides/build-with-claude-context-editing.md)                                                                                                                                                      | Context management beta header pairs with the `context_management` body field                                                                                                                                                                                                 | `400` with `Extra inputs are not permitted`. Common when a gateway accepts Anthropic-format requests but forwards them to Amazon Bedrock. On v2.1.304 or later, Claude Code [retries on its own without the pair](#automatic-retry-and-error-forwarding) | Forward both, or [`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`](#disable-pre-release-capabilities)                                                                                                                                                                                                                                                                        |
| [Extended context](../04-API-Reference/Guides/build-with-claude-context-windows.md#context-window-sizes-by-model) and [interleaved thinking](../04-API-Reference/Guides/build-with-claude-extended-thinking.md#interleaved-thinking) | Beta headers only, no body field                                                                                                                                                                                                                                              | Silently unavailable when the header is stripped; the upstream never sees the capability request                                                                                                                                                         | Forward `anthropic-beta` verbatim                                                                                                                                                                                                                                                                                                                                       |
| Beta [tool fields](../04-API-Reference/Agents-Tools/agents-and-tools-tool-use-overview.md)                                                                                                                                                       | Tool-related beta headers pair with tool schema fields such as `strict` and `defer_loading`                                                                                                                                                                                   | `400` naming the unrecognized tool schema field when the body passes through without its header. For `defer_loading`, see [Retry without tool search](#retry-without-tool-search)                                                                        | Forward both, or [`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`](#disable-pre-release-capabilities)                                                                                                                                                                                                                                                                        |
| [Effort](../04-API-Reference/Guides/build-with-claude-effort.md) and [structured outputs](../04-API-Reference/Guides/build-with-claude-structured-outputs.md)                                                                        | The `output_config` body field carries effort, structured-output format, and task budget settings; each pairs with its own beta header                                                                                                                                        | `400` naming `output_config`, often `Extra inputs are not permitted`, on Amazon Bedrock and Google Cloud’s Agent Platform upstreams                                                                                                                      | Forward the field and its headers together, or have developers set [`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`](#disable-pre-release-capabilities), which removes the format and task budget settings but not effort. To remove only the format, they can set [`CLAUDE_CODE_DISABLE_STRUCTURED_OUTPUTS=1`](../02-Claude-Code-CLI/env-vars.md) instead, which requires v2.1.288 or later |
| Mid-conversation system messages                                                                                                                                                                                                                 | No beta header. Claude Code sends some reminder and attachment text as `role: "system"` entries in `messages` for Claude Opus 4.8, models released after it, and unrecognized model IDs                                                                                       | `400` such as `Unexpected role "system"`                                                                                                                                                                                                                 | Claude Code [retries without system messages](#automatic-retry-and-error-forwarding). To stop system messages on an alias of a model released before Claude Opus 4.8, add a [`modelOverrides` entry](#settings-for-unrecognized-model-ids) to the settings you distribute                                                                                               |
| [Effort set per turn](../02-Claude-Code-CLI/prompt-caching.md#changing-effort-level)                                                                                                                                                                             | On Opus 5.5, Sonnet 5.5, Haiku 5.5, and Fable 5.1, a beta header pairs with `output_config.effort` on `role: "system"` entries, either a system message or an entry with empty content                                                                                        | `400` with `messages.N: system content must contain at least one block` when your gateway strips the field from an empty entry                                                                                                                           | Forward both, or have developers set [`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`](#disable-pre-release-capabilities) to stop the field and the empty entries                                                                                                                                                                                                            |
| [Prompt caching](../02-Claude-Code-CLI/prompt-caching.md)                                                                                                                                                                                                        | No beta pairing. Claude Code attaches `cache_control` markers to `system` blocks and to `messages` entries, including `role: "system"` entries appended mid-conversation                                                                                                      | No error: the conversation bills as uncached input on every turn, visible as high `input_tokens` with little or no cache activity in `usage`                                                                                                             | Forward `cache_control` unchanged wherever it appears, and don’t convert block-form `system` or message content to plain strings                                                                                                                                                                                                                                        |
| [Token counting](../04-API-Reference/Guides/build-with-claude-token-counting.md)                                                                                                                                                           | No beta pairing; uses the `count_tokens` endpoint                                                                                                                                                                                                                             | No error: Claude Code falls back to a character-based estimate, so `/context` shows approximate counts                                                                                                                                                   | Expose the endpoint for exact token counts                                                                                                                                                                                                                                                                                                                              |

The `ANTHROPIC_DEFAULT_*_MODEL_SUPPORTED_CAPABILITIES` [variables](../02-Claude-Code-CLI/model-config.md) declare model capabilities only in the provider configurations: `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY`, and [`CLAUDE_CODE_USE_MANTLE`](../02-Claude-Code-CLI/amazon-bedrock.md#use-the-mantle-endpoint). They have no effect behind an `ANTHROPIC_BASE_URL` gateway.


[​](#automatic-retry-and-error-forwarding)

Automatic retry and error forwarding

When the upstream rejects one of these capabilities, Claude Code can retry the request on its own with the rejected part removed or changed:

- When a `400` says the `thinking.type` value `enabled` or `adaptive` isn’t supported, Claude Code retries once with the other type and keeps the `thinking` field. Later requests to that model keep the switched type until Claude Code exits. Before v2.1.121, Claude Code didn’t retry, so [`thinking.type.enabled is not supported`](../02-Claude-Code-CLI/errors.md#thinking-type-enabled-is-not-supported-for-this-model) reached the developer
- When the upstream rejects a `role: "system"` entry inside `messages`, Claude Code retries once without those mid-conversation system messages and carries their text in user messages instead. Later requests in the conversation keep that shape until the developer runs `/clear` or the conversation compacts
- When the upstream rejects only the `cache_control` marker on a mid-conversation system message, Claude Code keeps the system messages and retries once with the marker moved. The marker goes on the last entry in `messages` that isn’t a system message, in that retry and in later requests until Claude Code exits
- When the upstream rejects a [thinking signature](../04-API-Reference/Guides/build-with-claude-extended-thinking.md), including with a `400` whose message says the block is `bound to a different conversation`, Claude Code removes earlier thinking blocks from the request, retries, and keeps them out of every later request. New responses still include thinking
- When the gateway or its upstream rejects the [advisor tool](../02-Claude-Code-CLI/advisor.md) entry in `tools` as an unrecognized tool type, Claude Code retries the request once without that entry and its `anthropic-beta` value. Later requests to that base URL leave the advisor out until Claude Code exits, and `/advisor` is unavailable to the developer for that time. Claude Code recognizes this rejection by a `400` or `422` response whose message names the tool type after `Input tag`, such as `Input tag 'advisor_20260301'`. Before v2.1.280, Claude Code didn’t retry this rejection
- When the upstream rejects `output_config.effort`, Claude Code retries the request without effort and leaves it out of later requests to that model until Claude Code exits. Claude Code recognizes this rejection by a `400` whose message names `output_config.effort` together with `Extra inputs are not permitted`, or says the model doesn’t support the effort parameter
- When the upstream rejects the `context_management` field or its `anthropic-beta` value, the gateway sees the request once more without them. If the gateway sees them again in a later request and the upstream rejects them again, the same retry follows. Claude Code recognizes this rejection by its message, for example one containing `context_management: Extra inputs are not permitted`. Before v2.1.304, Claude Code didn’t retry this rejection
- When the upstream rejects a beta tool field other than `defer_loading`, such as `strict`, the `400` reaches the developer. Before it does, the gateway can see the request again [without the tool search fields](#retry-without-tool-search). To stop sending such fields, have developers set [`CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`](#disable-pre-release-capabilities)

The `bound to a different conversation` rejection comes from the API’s [preserved thinking](../04-API-Reference/Guides/build-with-claude-preserved-thinking.md) check, which fails when `system`, `tools`, or earlier `messages` content differs from the request that produced the thinking. A gateway that rewrites any of that content can cause the rejection itself; [Libraries, proxies, and gateways](../04-API-Reference/Guides/build-with-claude-preserved-thinking.md#libraries-proxies-gateways) covers what to pass through unchanged. Most of this retry logic matches on the upstream’s error wording, so forward error response bodies unmodified. A gateway that wraps upstream errors in its own envelope breaks the recovery path, even when it preserves the status code, unless the envelope’s message carries a stable `capability_rejected:` token. [Claude apps gateway substitutes those tokens for cloud providers’ error wording](claude-apps-gateway-config.md#upstream-error-messages), for example `capability_rejected: prompt_too_long`.


[​](#retry-without-tool-search)

Retry without tool search

On v2.1.305 or later, a request carrying [`defer_loading` fields and `tool_reference` blocks](../06-MCP-Tools/General/mcp.md#tool-search-through-a-gateway) can arrive again without them after a `400` or `422` whose cause Claude Code doesn’t recognize. After a successful retry, later requests omit them too. The developer’s requests keep working without any change on the gateway, with their MCP tools loaded upfront. Claude Code tries tool search again later, so the gateway can see another rejected request followed by its retry. To keep the fields out of every request, have developers set `ENABLE_TOOL_SEARCH=false`. For tool search itself, Claude Code expects the gateway to forward the `defer_loading` fields and `tool_reference` blocks with their `anthropic-beta` value.


[​](#disable-pre-release-capabilities)

Disable pre-release capabilities

When your gateway or its upstream rejects pre-release `anthropic-beta` values or the body fields that pair with them, and you can’t forward both halves, have developers set `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`. With the variable set, Claude Code stops sending pre-release capabilities and the `anthropic-beta` values that pair with them, including:

- Context management and its `context_management` body field
- The beta tool schema fields, such as `strict` and `defer_loading`. The standard `name`, `description`, `input_schema`, and `cache_control` tool fields stay
- The structured-output `output_config.format` field. Requires Claude Code v2.1.287 or later
- The `output_config.task_budget` field
- [MCP tool search](../06-MCP-Tools/General/mcp.md#scale-with-mcp-tool-search), so every MCP tool loads upfront unless your organization keeps it on through managed settings

The variable doesn’t strip every `anthropic-beta` value. What it leaves in place includes:

- The `anthropic-beta` values for extended context, interleaved thinking, and effort, which cloud providers also accept
- The top-level `output_config.effort` field. [Automatic retry and error forwarding](#automatic-retry-and-error-forwarding) covers an upstream that rejects it
- The adaptive reasoning `thinking` field, which has no beta header
- The `role: "system"` entries that carry [mid-conversation system messages](#feature-pass-through)
- The OAuth `anthropic-beta` value that subscription authentication requires
- Header values and body fields developers add themselves through [`ANTHROPIC_BETAS`](../02-Claude-Code-CLI/env-vars.md) or [`CLAUDE_CODE_EXTRA_BODY`](../02-Claude-Code-CLI/env-vars.md)

When a host platform that embeds Claude Code sets [`CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST`](../02-Claude-Code-CLI/env-vars.md), `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS` doesn’t stop auto mode sessions on Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, or a [Claude apps gateway](claude-apps-gateway.md) from asking the server for [classifier review](../02-Claude-Code-CLI/permission-modes.md#server-side-classifier-review). That review adds an `anthropic-beta` value and a `safeguards` request field. Set `CLAUDE_CODE_AUTO_MODE_SERVER=0` to stop it there. On Claude Code v2.1.227 or later, your organization can keep [MCP tool search](../06-MCP-Tools/General/mcp.md#scale-with-mcp-tool-search) on under this variable through [managed settings](managed-settings.md). What Claude Code sends with that override in place depends on how you connect:

- On a direct connection without the HIPAA configuration, or through a gateway set with `ANTHROPIC_BASE_URL`, Claude Code keeps sending the tool-search beta header, `defer_loading` tool fields, and `tool_reference` blocks
- On a cloud provider, or signed in through a [Claude apps gateway](claude-apps-gateway.md), the override has no effect
- In a session that [gets the HIPAA configuration](../02-Claude-Code-CLI/hipaa-setup.md#check-how-developers-sign-in-and-connect), the override has no effect and tool search stays off

The set of capabilities Claude Code sends grows over releases. For current beta header strings, see the [beta headers reference](../04-API-Reference/Endpoints/beta-headers.md); test your gateway against new Claude Code releases rather than pinning to an observed list.


[​](#model-discovery)

Model discovery

When `ANTHROPIC_BASE_URL` points at a gateway that exposes the Anthropic Messages format, Claude Code can query the gateway’s `/v1/models` endpoint at startup and add the returned models to the `/model` picker. If you or your administrator set `replaceBuiltInOptions` in a [`modelPicker`](../02-Claude-Code-CLI/settings-reference.md#modelpicker) lineup, Claude Code hides the discovered models from the picker. Developers enable it by setting [`CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1`](../02-Claude-Code-CLI/env-vars.md), in their own environment or through managed settings. Discovery is off by default so that gateways backed by a shared API key don’t surface every model the key can access to every user.


[​](#when-discovery-runs)

When discovery runs

Discovery applies only to the Anthropic Messages format. It doesn’t run when:

- Any `CLAUDE_CODE_USE_*` provider variable is set, even if `ANTHROPIC_BASE_URL` is also set
- `ANTHROPIC_BASE_URL` is unset or points at `api.anthropic.com`

Discovery still runs when [nonessential traffic is turned off](llm-gateway-connect.md#turn-off-traffic-outside-the-gateway-path), because the request goes only to your gateway. Before v2.1.257, discovery didn’t run while nonessential traffic was turned off.


[​](#request-and-response)

Request and response

The request is `GET /v1/models?limit=1000` with a timeout of 3 seconds by default, and any redirect is treated as failure so the credential can’t leak to a redirect target. A gateway that responds slower than the timeout, or one that redirects `/v1/models`, even `http` to `https`, fails discovery silently; serve the endpoint directly at the configured base URL. To give a slow gateway longer, set [`CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY_TIMEOUT_MS`](../02-Claude-Code-CLI/env-vars.md#variables). The variable requires Claude Code v2.1.269 or later. Claude Code sends the discovery request with both credential headers below and omits a header whose value doesn’t resolve. Sending both headers requires Claude Code v2.1.248 or later. Earlier versions send only `Authorization` when `ANTHROPIC_AUTH_TOKEN` is set and only `x-api-key` otherwise.

- `Authorization`: `ANTHROPIC_AUTH_TOKEN` as a bearer token, otherwise the [`apiKeyHelper`](llm-gateway-connect.md#rotate-credentials-with-apikeyhelper) value as a bearer token.
- `x-api-key`: the API key Claude Code resolved, such as `ANTHROPIC_API_KEY`. When a helper value is the only credential, this header carries it too, so the value arrives in both headers.

Claude Code also sends any headers from `ANTHROPIC_CUSTOM_HEADERS`. When a custom header has a non-empty value, Claude Code sends it in place of a built-in header of the same name, matching names case-insensitively. When neither credential header’s value resolves, Claude Code skips discovery and writes a `[gatewayDiscovery] skipped` line to the debug log of a `claude --debug` session. If you supply a credential only through `ANTHROPIC_CUSTOM_HEADERS`, Claude Code still skips discovery. Claude Code reads `id`, the optional `display_name`, and the optional `description` from each entry in the response’s `data` array:

```python
{
  "data": [
    {
      "id": "claude-sonnet-4-6",
      "display_name": "Claude Sonnet 4.6",
      "description": "Default model for everyday coding tasks"
    },
    { "id": "claude-opus-4-8" }
  ]
}
```

Claude Code keeps an entry when its `id` contains `claude` or `anthropic` anywhere in the string, matched case-insensitively, and ignores the rest. Provider-prefixed IDs such as `vertex_ai/claude-sonnet-4-6` or `bedrock/anthropic.claude-sonnet-4-5` pass the filter; an ID that contains neither substring doesn’t. Before v2.1.223, Claude Code kept an entry only when its `id` began with `claude` or `anthropic`, which hid provider-prefixed IDs.


[​](#picker-entries-and-caching)

Picker entries and caching

The picker is the interactive model list that opens when a developer runs `/model` in Claude Code. Each discovered entry uses `display_name` as its name when the gateway sends one that differs from the `id`. Otherwise the entry shows the model’s name when Claude Code [recognizes the `id`](../02-Claude-Code-CLI/model-config.md#customize-pinned-model-display-and-capabilities), and the `id` when it doesn’t. For example, an entry with the `id` `my-gateway-claude-sonnet-4-6` and no `display_name` appears as `Sonnet 4.6`. Discovery adds only models that the [`availableModels` managed setting](../02-Claude-Code-CLI/settings-reference.md#availablemodels) allows. Each entry also shows the model’s `description`, collapsed to one line. An entry without a `description` reads “From gateway” instead. Before v2.1.257, every discovered entry read “From gateway”. A discovered ID doesn’t get its own row when it matches a row already in the picker:

- Same ID: the discovered ID exactly matches an existing row’s ID, or the two IDs are spellings of the same [Fable](../02-Claude-Code-CLI/model-config.md#work-with-fable) version.
- Same model as a built-in alias: when a discovered explicit ID names the model that a built-in alias currently resolves to, the picker shows only the alias row. For example, while `sonnet` resolves to `claude-sonnet-5-5`, a discovered `claude-sonnet-5-5` collapses into the `sonnet` row, and a discovered `claude-sonnet-5` still gets its own row.

Results are cached to `~/.claude/cache/gateway-models.json`, or `%USERPROFILE%\.claude\cache\gateway-models.json` on Windows, and refreshed on each startup. If you set [`CLAUDE_CONFIG_DIR`](../02-Claude-Code-CLI/env-vars.md), the cache lives under that directory instead. If the request fails or the gateway doesn’t implement `/v1/models`, the picker falls back to the cached list from the previous startup or to the built-in model list. If your gateway serves Claude models under aliases that don’t match the discovery filter, developers can add those aliases manually with the [model configuration](../02-Claude-Code-CLI/model-config.md) variables.


[​](#related-resources)

Related resources

For the rest of the gateway documentation set and the underlying API references:

- [Gateway overview](gateways.md): what a gateway is and how to choose between Claude apps gateway and another product
- [Other LLM gateways](llm-gateway.md): how to roll out a gateway your organization runs and how it interacts with claude.ai subscriptions
- [Roll out an LLM gateway for your organization](llm-gateway-rollout.md): the admin checklist that uses this guide
- [Connect Claude Code to an LLM gateway](llm-gateway-connect.md): per-developer configuration and the troubleshooting table
- [Beta headers reference](../04-API-Reference/Endpoints/beta-headers.md): the current set of `anthropic-beta` values
- [Messages API](../04-API-Reference/Endpoints/messages.md): the API format an Anthropic-format gateway implements

---

## Roll out an LLM gateway for your organization - Claude Code Docs

Source: https://code.claude.com/docs/en/llm-gateway-rollout
Path: 13-Enterprise-Admin/llm-gateway-rollout.md

## On this page

- [Prerequisites](#prerequisites)
  - [Gateway requirements](#gateway-requirements)
- [Rollout steps](#rollout-steps)
  - [Confirm the gateway routes your models](#confirm-the-gateway-routes-your-models)
  - [Issue developer credentials](#issue-developer-credentials)
  - [Test Claude Code against the gateway](#test-claude-code-against-the-gateway)
  - [Distribute the configuration](#distribute-the-configuration)
  - [What to distribute](#what-to-distribute)
  - [Distribute through managed settings](#distribute-through-managed-settings)
  - [The HIPAA configuration behind a gateway](#the-hipaa-configuration-behind-a-gateway)
  - [Hand developers the values to set themselves](#hand-developers-the-values-to-set-themselves)
  - [Verify the rollout](#verify-the-rollout)
- [Maintain the gateway](#maintain-the-gateway)
  - [Plan Claude Code version upgrades](#plan-claude-code-version-upgrades)
- [Related resources](#related-resources)

Other gateways

# Roll out an LLM gateway for your organization

Copy pageCopy page

Deploy a gateway product for Claude Code: configure it to forward what Claude Code sends, issue developer credentials, distribute the configuration through managed settings, and verify the rollout.

Copy pageCopy page

This page walks an administrator through rolling out an LLM gateway for Claude Code. It assumes you have a gateway product deployed that meets the [gateway requirements](#gateway-requirements). Deploying or operating any specific product isn’t covered here; deploy yours following its vendor’s documentation.

- To connect Claude Code on your own machine to an existing gateway, see [Connect Claude Code to an LLM gateway](llm-gateway-connect.md)
- For what Claude Code sends to a gateway and what to forward, see the [gateway compatibility guide](llm-gateway-protocol.md)


[​](#prerequisites)

Prerequisites

To complete the rollout, you’ll need:

- A gateway deployed on your infrastructure, serving HTTPS at the exact address you’ll distribute to developers, not an address that redirects to it, and configured to route Claude model names to your provider
- A provider credential for the gateway to forward with:
  - For the Anthropic API: an API key from the [Claude Console](../04-API-Reference/Other/playground.md)
  - For a cloud provider: cloud credentials with model access. See the prerequisites on the [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md#prerequisites), [Google Cloud’s Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md#prerequisites), or [Microsoft Foundry](../02-Claude-Code-CLI/microsoft-foundry.md#prerequisites) page
- A way to deliver settings files to developer machines, such as MDM or configuration management
  - If you don’t have one yet, [how settings reach devices](admin-setup.md#decide-how-settings-reach-devices) compares the options


[​](#gateway-requirements)

Gateway requirements

Whichever product provides the gateway, it must:

- **Accept a supported API format**: one of the formats in the [API formats table](llm-gateway-protocol.md#api-formats). The rollout steps below assume the Anthropic Messages API at `POST /v1/messages`, which most gateways serve
- **Stream responses**: pass server-sent events through as they arrive, including keep-alive pings, instead of buffering the whole response; [streaming](llm-gateway-protocol.md#streaming) covers what buffering or stripped pings break
- **Route Claude model names**: map each name developers use to an upstream model. Claude Code sends a model name such as `claude-sonnet-4-6` in each request; in most gateway products the mapping is a model list or routing table in the gateway’s own configuration
- **Forward headers and body unchanged**: pass `anthropic-beta`, `anthropic-version`, and the request body through in both directions; the [feature pass-through table](llm-gateway-protocol.md#feature-pass-through) maps each to the feature that breaks without it
- **Return upstream errors unmodified**: Claude Code’s automatic recovery matches on error wording, so wrapping errors in the gateway’s own envelope breaks it, unless the envelope’s message carries one of the `capability_rejected:` tokens a [Claude apps gateway substitutes for cloud providers’ error wording](claude-apps-gateway-config.md#upstream-error-messages)
- **Exempt the path from request-body WAF inspection**: Claude Code prompts carry source code and XML-style tags that match cross-site-scripting body rules; a WAF in front of the gateway returns `403` on real sessions while short test requests pass

Optionally, serve `GET /v1/models` so Claude Code can populate the model picker from your gateway with [model discovery](llm-gateway-protocol.md#model-discovery).


[​](#rollout-steps)

Rollout steps

The rollout takes five steps, each with a checkpoint:

1.  [Confirm the gateway routes your models](#confirm-the-gateway-routes-your-models)
2.  [Issue each developer a credential](#issue-developer-credentials)
3.  [Test Claude Code against the gateway](#test-claude-code-against-the-gateway)
4.  [Distribute the base URL and credentials](#distribute-the-configuration)
5.  [Verify from a developer machine](#verify-the-rollout)

The steps involve three different credentials, and the checkpoints name them by placeholder so you can tell which one is at fault when something fails:

| Credential                        | Who holds it                                                                                         | Placeholder in checkpoints                                  |
|:----------------------------------|:-----------------------------------------------------------------------------------------------------|:------------------------------------------------------------|
| Provider credential               | The gateway, which forwards it to the upstream provider                                              | Configured on the gateway; never appears in client commands |
| Gateway administrative credential | You, if your gateway product issues one for its admin or test interface                              | `<gateway-key>`                                             |
| Developer key                     | Each developer, issued by the gateway in [Issue developer credentials](#issue-developer-credentials) | `<developer-key>`                                           |


[​](#confirm-the-gateway-routes-your-models)

Confirm the gateway routes your models

Your gateway should already be configured with your provider credential, listening at its base URL, and forwarding requests to your provider’s API. Test that the path works end to end with a minimal request, substituting two values from your deployment:

- `<gateway-key>` is whatever credential lets you call the gateway right now: an administrative key, a test key, or your own developer key if you’ve already issued one. Not every gateway product has a separate admin credential; if yours doesn’t, issue yourself a developer key in [Issue developer credentials](#issue-developer-credentials) first
- `model` is a Claude model name your gateway is configured to route. The example uses `claude-sonnet-4-6`; substitute a name you’ve configured

- Bash or Zsh

- PowerShell

```python
curl -X POST "https://llm-gateway.example.com/v1/messages" \
  -H "Authorization: Bearer <gateway-key>" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model": "claude-sonnet-4-6", "max_tokens": 1, "messages": [{"role": "user", "content": "."}]}'
```

```python
Invoke-RestMethod -Method Post -Uri "https://llm-gateway.example.com/v1/messages" `
  -Headers @{ "Authorization" = "Bearer <gateway-key>"; "anthropic-version" = "2023-06-01" } `
  -ContentType "application/json" `
  -Body '{"model": "claude-sonnet-4-6", "max_tokens": 1, "messages": [{"role": "user", "content": "."}]}'
```

**Checkpoint**: a `200` with a `content` field means the gateway reached the provider with that model name. A `404` means that name isn’t routed at the gateway; a `401` from the provider means the gateway’s provider credential is wrong. Repeat the request once per Claude model name in your gateway’s routing configuration. A name the gateway doesn’t route returns `404` to any developer who selects it, so test every name before rollout.

Avoid serving the gateway behind a redirect. A redirect can drop the request body or strip the credential header on inference requests, and [model discovery](llm-gateway-protocol.md#model-discovery) treats any redirect as a failure so the credential cannot leak to a redirect target.


[​](#issue-developer-credentials)

Issue developer credentials

Each developer needs their own gateway key to authenticate. Create a credential per developer at the gateway, following your product’s credential management documentation. Confirm a freshly issued key works against the gateway with the same request as [Confirm the gateway routes your models](#confirm-the-gateway-routes-your-models), replacing `<gateway-key>` with the new `<developer-key>`:

- Bash or Zsh

- PowerShell

```python
curl -X POST "https://llm-gateway.example.com/v1/messages" \
  -H "Authorization: Bearer <developer-key>" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model": "claude-sonnet-4-6", "max_tokens": 1, "messages": [{"role": "user", "content": "."}]}'
```

```python
Invoke-RestMethod -Method Post -Uri "https://llm-gateway.example.com/v1/messages" `
  -Headers @{ "Authorization" = "Bearer <developer-key>"; "anthropic-version" = "2023-06-01" } `
  -ContentType "application/json" `
  -Body '{"model": "claude-sonnet-4-6", "max_tokens": 1, "messages": [{"role": "user", "content": "."}]}'
```

**Checkpoint**: a `200` with a `content` field means the developer key reaches the gateway and the gateway forwards it. A `401` here, when [the previous step](#confirm-the-gateway-routes-your-models) succeeded, means the developer key is wrong or hasn’t taken effect at the gateway yet. Issuing one key per developer rather than a shared key is what makes per-developer usage attribution and individual offboarding work. The environment variable that holds the key depends on which header the gateway reads. For a gateway that checks credentials in the `Authorization: Bearer` header, developers set their key in `ANTHROPIC_AUTH_TOKEN`. For a gateway that reads keys from the `x-api-key` header, developers set `ANTHROPIC_API_KEY` instead; the [credential table](llm-gateway-connect.md#set-the-credential-variable) covers the mapping.


[​](#test-claude-code-against-the-gateway)

Test Claude Code against the gateway

Run Claude Code through the gateway yourself before distributing anything, using the same configuration the rollout will deliver fleet-wide. Type these directly in a terminal, not in a `.env` or settings file; they last only for this terminal session, so closing it returns your machine to its normal configuration. Use `ANTHROPIC_API_KEY` instead of `ANTHROPIC_AUTH_TOKEN` if your gateway reads the `x-api-key` header:

- Bash or Zsh

- PowerShell

```python
export ANTHROPIC_BASE_URL=https://llm-gateway.example.com
export ANTHROPIC_AUTH_TOKEN="<developer-key>"
```

```python
$env:ANTHROPIC_BASE_URL = "https://llm-gateway.example.com"
$env:ANTHROPIC_AUTH_TOKEN = "<developer-key>"
```

Then send a one-shot prompt through the gateway:

```python
claude -p "Reply with one word: connected"
```

**Checkpoint**: the prompt returns a response, and the request appears in the gateway’s log as a `POST` to the `/v1/messages` path with status `200`. Claude Code appends a query string such as `?beta=true`, so match on the path, not the full URL. Two failure messages point in different directions:

- `Not logged in`: check the gateway log to tell the two causes apart. If it’s empty, no credential reached the session and no request left the machine; re-run the exports in the shell you’re testing from. If it shows a rejected request with `x-api-key` in the `401` body, the gateway expects keys in that header instead; switch to `ANTHROPIC_API_KEY`
- `Failed to authenticate. API Error: 401` means a credential was sent and rejected, and the gateway log says where: a `401` naming `api.anthropic.com` or your provider’s endpoint means the gateway reached the upstream but its provider credential was rejected, so the developer key worked and the provider credential the gateway holds is wrong or a placeholder

A wrong or unreachable base URL produces a different symptom: Claude Code [retries the connection with backoff](../02-Claude-Code-CLI/errors.md#automatic-retries) and can sit with no output for several minutes before reporting an error. If the command appears to hang, check the gateway log instead of waiting; no arriving request means `ANTHROPIC_BASE_URL` doesn’t point at the gateway.


[​](#distribute-the-configuration)

Distribute the configuration

Every developer machine needs the gateway address and a credential. You can distribute them centrally through [managed settings](managed-settings.md#delivery-mechanisms), so developers configure nothing, or give developers the values to set themselves.


[​](#what-to-distribute)

What to distribute

The same set of variables applies whichever path you choose. Most rollouts only need `ANTHROPIC_BASE_URL` and a credential; include the conditional rows when your gateway setup calls for them.

| Variable or setting                                                                                                                                                                                                                   | What it does                                                                                                                                                                                                                        | Include when                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ANTHROPIC_BASE_URL`                                                                                                                                                                                                                  | Sends Claude Code’s API requests to the gateway instead of `api.anthropic.com`                                                                                                                                                      | Always                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `apiKeyHelper`, or a credential in `ANTHROPIC_AUTH_TOKEN` or `ANTHROPIC_API_KEY`                                                                                                                                                      | Authenticates each request to the gateway. The helper runs a command to fetch the key; the variables hold a static key, sent as `Authorization: Bearer` and `x-api-key` respectively                                                | Always; one of the three                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `ANTHROPIC_CUSTOM_HEADERS`                                                                                                                                                                                                            | Adds extra HTTP headers to every API request                                                                                                                                                                                        | Your gateway requires a tenant or routing header on every request                                                                                                                                                                                                                                                                                                                                                                                                  |
| `CLAUDE_CODE_GATEWAY_HINT_HEADERS`                                                                                                                                                                                                    | Sends the [gateway hint headers](llm-gateway-protocol.md#gateway-hint-headers), which classify each request for routing and scheduling decisions at the gateway. Requires Claude Code v2.1.273 or later                       | Your gateway reads the hint headers                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY`                                                                                                                                                                                          | Queries the gateway’s `/v1/models` at startup and adds the returned names to the `/model` picker                                                                                                                                    | Your gateway serves `/v1/models` and you want developers’ pickers populated from it                                                                                                                                                                                                                                                                                                                                                                                |
| `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS`                                                                                                                                                                                              | Stops Claude Code sending pre-release capability headers and body fields. [Disable pre-release capabilities](llm-gateway-protocol.md#disable-pre-release-capabilities) covers the exact scope                                 | Your gateway forwards to an Amazon Bedrock or Google Cloud’s Agent Platform upstream that rejects beta fields. See [Gateway requirements](#gateway-requirements)                                                                                                                                                                                                                                                                                                   |
| `CLAUDE_CODE_SKIP_FAST_MODE_NETWORK_ERRORS` or `CLAUDE_CODE_SKIP_FAST_MODE_ORG_CHECK`                                                                                                                                                 | Restores [fast mode](../02-Claude-Code-CLI/fast-mode.md) when its availability check, which calls `api.anthropic.com` directly rather than following `ANTHROPIC_BASE_URL`, fails, is intercepted, or is skipped for lack of an Anthropic credential | Your organization uses fast mode, and developers authenticate with `ANTHROPIC_AUTH_TOKEN` alone, with a gateway-issued key in `ANTHROPIC_API_KEY` or from an `apiKeyHelper`, or your network blocks or intercepts direct requests to `api.anthropic.com`; [use fast mode behind proxies and LLM gateways](../02-Claude-Code-CLI/fast-mode.md#use-fast-mode-behind-proxies-and-llm-gateways) covers which of the two variables matches your configuration                           |
| `ANTHROPIC_MODEL` or [`ANTHROPIC_DEFAULT_HAIKU_MODEL`](../02-Claude-Code-CLI/model-config.md)                                                                                                                                                         | Set which model name Claude Code requests for the main session and for background traffic                                                                                                                                           | Your gateway routes model names that don’t match Claude Code’s defaults, or you route [background functionality](../17-Billing-Plans/costs.md#background-token-usage) to a different model. Route both the override names and the built-in model IDs Claude Code requests when no override is set, since some background sub-calls request a built-in ID regardless of the override; [model configuration](../02-Claude-Code-CLI/model-config.md) covers which model each part of a session uses |
| `ANTHROPIC_BEDROCK_BASE_URL`, `ANTHROPIC_VERTEX_BASE_URL`, `ANTHROPIC_FOUNDRY_BASE_URL`, or `ANTHROPIC_AWS_BASE_URL` with the [variables for that provider](llm-gateway-connect.md#route-to-a-cloud-provider-through-a-gateway) | Point Claude Code at the gateway through a provider-specific base URL. Amazon Bedrock and Google Cloud’s Agent Platform also switch to those providers’ native request format                                                       | Your gateway fronts Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, or the Claude Platform on AWS; see [API formats](llm-gateway-protocol.md#api-formats)                                                                                                                                                                                                                                                                                  |


[​](#distribute-through-managed-settings)

Distribute through managed settings

Deliver the variables through the `env` block of a [managed settings file](managed-settings.md#delivery-mechanisms), pushed by MDM, registry policy, or configuration management:

```python
{
  "env": {
    "ANTHROPIC_BASE_URL": "https://llm-gateway.example.com"
  },
  "apiKeyHelper": "/usr/local/bin/get-gateway-key"
}
```

Add the conditional variables from the table to the same `env` block. A managed `ANTHROPIC_BASE_URL` is enforced and cannot be overridden by a developer’s shell export, since Claude Code applies it over the process environment and lower-precedence settings. Don’t include `forceLoginMethod`, `forceLoginOrgUUID`, or `forceLoginGatewayUrl` in managed settings alongside a gateway credential. `forceLoginMethod` or `forceLoginOrgUUID`, with any value, blocks `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and `apiKeyHelper` at startup, and developers can’t proceed. They see `This machine's managed settings require a first-party login`, or [`Administrator policy requires a Cloud gateway sign-in`](../02-Claude-Code-CLI/errors.md#administrator-policy-requires-a-cloud-gateway-sign-in) when the file sets `forceLoginMethod` to `"gateway"` or sets `forceLoginGatewayUrl`. [Server-managed settings](server-managed-settings.md#platform-availability) delivery requires a direct connection to `api.anthropic.com`, so it does not reach gateway-routed sessions. Gateway deployments use this file-based managed settings path, which enforces the same keys. For the credential, distribute one [`apiKeyHelper`](llm-gateway-connect.md#rotate-credentials-with-apikeyhelper) command in the managed settings file as shown above; the command authenticates to your secrets store as the local developer, so each machine receives its own key. Alternatively, deliver each developer their key through your existing secrets process and have them set `ANTHROPIC_AUTH_TOKEN` themselves. Some environments need separate delivery:

- The desktop app reads gateway routing from its third-party inference configuration, not from managed settings; deploy that file through MDM alongside managed settings so desktop sessions route through the gateway too. See the [desktop third-party configuration docs](https://claude.com/docs/third-party/claude-desktop/configuration) and the [desktop gateway docs](https://claude.com/docs/third-party/claude-desktop/gateway)
- CI runners need `ANTHROPIC_BASE_URL` and the credential set in the [runner’s environment](llm-gateway-connect.md#configure-each-surface)
- WSL on managed Windows machines reads the Windows managed settings only when [`wslInheritsWindowsSettings`](../02-Claude-Code-CLI/settings-reference.md#wslinheritswindowssettings) is `true`


[​](#the-hipaa-configuration-behind-a-gateway)

The HIPAA configuration behind a gateway

Sessions that go through a gateway aren’t eligible for the HIPAA configuration. [Check how developers sign in and connect](../02-Claude-Code-CLI/hipaa-setup.md#check-how-developers-sign-in-and-connect) lists which sign-in and connection methods are. To restrict features for those sessions in the managed settings file, use the keys in [Map egress paths to managed controls and events](monitoring-usage.md#map-egress-paths-to-managed-controls-and-events). These keys don’t make a session eligible for the HIPAA configuration, and they don’t cover everything the configuration changes. For example:

- **Cloud sessions**: no managed key turns them off. See [Admin console controls](../16-Mobile-Desktop/desktop.md#admin-console-controls)
- **Anthropic credentials in child processes**: the configuration [removes them](../02-Claude-Code-CLI/hipaa-setup.md#anthropic-credentials-in-commands-hooks-and-mcp-servers) from the processes Claude Code starts, and no settings key does only that

Don’t use `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC` in place of these keys. It turns off some of these features, but it also turns off the auto-updater, and it leaves WebFetch on.


[​](#hand-developers-the-values-to-set-themselves)

Hand developers the values to set themselves

If you don’t have managed-settings distribution in place, send each developer what they need to follow the [connect page](llm-gateway-connect.md#configure-claude-code-yourself):

- The gateway URL
- Their personal credential
- **Which variable to put the credential in**: `ANTHROPIC_AUTH_TOKEN` for a bearer-token gateway, or `ANTHROPIC_API_KEY` for an `x-api-key` gateway. Telling developers which one saves them the trial-and-error described on the [connect page](llm-gateway-connect.md#set-the-credential-variable)
- Any conditional variables from the [What to distribute table](#what-to-distribute), with their values

The [connect page](llm-gateway-connect.md#configure-claude-code-yourself) walks developers through setting each one. **Checkpoint**: on a developer machine, `claude` starts a session without showing the login screen, since the distributed credential satisfies authentication. Then run `/status` and open the **Status** tab: the `Anthropic base URL` line shows the gateway address, and for managed distribution the `Setting sources` line includes managed settings. A login screen, or a missing `Anthropic base URL` line, means the configuration didn’t reach the machine.


[​](#verify-the-rollout)

Verify the rollout

Confirm everything works from a developer machine, not the gateway host, so the test covers the network path developers use. Send a streaming request, which checks the endpoint, streaming pass-through, and model routing at once:

- Bash or Zsh

- PowerShell

```python
curl -N -X POST "https://llm-gateway.example.com/v1/messages" \
  -H "Authorization: Bearer <developer-key>" \
  -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"model": "claude-sonnet-4-6", "max_tokens": 16, "stream": true, "messages": [{"role": "user", "content": "count to 3"}]}'
```

```python
$body = '{"model": "claude-sonnet-4-6", "max_tokens": 16, "stream": true, "messages": [{"role": "user", "content": "count to 3"}]}'
$body | curl.exe -N -X POST "https://llm-gateway.example.com/v1/messages" `
  -H "Authorization: Bearer <developer-key>" `
  -H "anthropic-version: 2023-06-01" `
  -H "content-type: application/json" `
  --data-binary '@-'
```

You should see `data:` lines arrive incrementally. The whole response arriving at once after a pause means the gateway is buffering, which stalls Claude Code; a `404` means the model name isn’t routed. Repeat per model name. Then start `claude` and send a message. Each symptom at this step has one cause:

- A login prompt means a credential gap. Run `/status` and open the **Status** tab: when the `Setting sources` line doesn’t include managed settings, the distribution didn’t reach the machine; when it does, the developer credential wasn’t delivered, so set `ANTHROPIC_AUTH_TOKEN` or the `apiKeyHelper`
- `Failed to authenticate` errors mean the gateway is rejecting requests; its log says which credential failed. A rejection the gateway logs itself names the developer key, while a `401` from `api.anthropic.com` or your provider’s endpoint means the provider credential the gateway holds was rejected
- A one-time approval prompt for the key is expected on first use when the gateway expects keys in the `x-api-key` header, set as `ANTHROPIC_API_KEY`. With `ANTHROPIC_AUTH_TOKEN`, no prompt appears and the variable takes over silently; a previously saved claude.ai login is inactive for that session

If your organization uses [fast mode](../02-Claude-Code-CLI/fast-mode.md), run `/fast` here too: the availability check calls `api.anthropic.com` directly rather than following the gateway base URL, so a gateway-routed session can report fast mode as unavailable or disabled even though inference works. [Use fast mode behind proxies and LLM gateways](../02-Claude-Code-CLI/fast-mode.md#use-fast-mode-behind-proxies-and-llm-gateways) maps each message to the variable that restores it, distributed with [the rest of the configuration](#distribute-the-configuration). Finally, check the gateway’s logs for the message you sent: the credential identifies the developer, and the [`x-claude-code-session-id` header](llm-gateway-protocol.md#request-headers) groups requests by session. If features fail with the [troubleshooting symptoms](llm-gateway-connect.md#troubleshoot-gateway-errors), the gateway is stripping headers or rewriting errors; see the [gateway requirements](#gateway-requirements) above.


[​](#maintain-the-gateway)

Maintain the gateway

After rollout, three kinds of change reach the gateway over time. Each has a symptom to watch for and an action to take.

| Change                                                                       | Symptom when the gateway hasn’t kept up                                                                                                                         | Action                                                                                                                                                                                                                                                            |
|:-----------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| New Claude Code releases add `anthropic-beta` values and request body fields | Developers report `400` errors naming a new field after they update Claude Code; see [feature pass-through](llm-gateway-protocol.md#feature-pass-through) | Forward `anthropic-*` headers and request bodies verbatim rather than allowlisting; test new Claude Code releases against the gateway before they reach developers, checking the areas in [Plan Claude Code version upgrades](#plan-claude-code-version-upgrades) |
| New Claude models become available                                           | Developers selecting a new model name get `404`; the `/model` picker doesn’t list it                                                                            | Add the model name to the gateway’s routing configuration, then re-run the [routing check](#confirm-the-gateway-routes-your-models). If you distribute `ANTHROPIC_MODEL` or the default-model variables, update the managed settings                              |
| Credentials expire or need rotation                                          | All developer requests start failing with `401` from the upstream                                                                                               | Rotate the gateway’s provider credential on its own schedule; developer keys rotate at the gateway, and an [`apiKeyHelper`](llm-gateway-connect.md#rotate-credentials-with-apikeyhelper) handles per-developer rotation without redistributing settings     |

When sizing per-key rate limits, account for the client [retrying transient failures](../02-Claude-Code-CLI/errors.md#automatic-retries), including `429` responses, up to 10 times with backoff, honoring `Retry-After`. Keep the [compatibility guide](llm-gateway-protocol.md) as the reference for what each Claude Code release sends.


[​](#plan-claude-code-version-upgrades)

Plan Claude Code version upgrades

Some Claude Code behavior is built into the installed version rather than set at your gateway, so moving developers to a new release can change behavior across your deployment even when the gateway configuration hasn’t changed. To control when that happens, pin developers to a tested version with [`requiredMaximumVersion`](../02-Claude-Code-CLI/settings-reference.md#requiredmaximumversion), or with [`DISABLE_UPDATES`](../01-Getting-Started/setup.md#disable-auto-updates) if you distribute Claude Code through your own channel. Before you raise the pin, read the new release’s [changelog](../02-Claude-Code-CLI/changelog.md) entry and [test it against the gateway](#test-claude-code-against-the-gateway). When you test a release, new headers or request fields that the gateway rejects appear as the `400` errors described in [Maintain the gateway](#maintain-the-gateway). The table below covers version-dependent changes that don’t produce an error, with the setting that keeps each one constant across upgrades.

| Area                         | What can change when developers upgrade                                                                                                                                                                                                                                                                                                                                                                    | Setting that keeps it constant                                                                                                                                                                                                                                                                                                                                       |
|:-----------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Feature-flag defaults        | Sessions that [don’t fetch feature flags from Anthropic](../02-Claude-Code-CLI/env-vars.md#features-that-need-feature-flag-fetching), such as sessions on a cloud provider or with telemetry turned off, use the flag defaults built into the installed version. When a release changes one of those defaults, the behavior changes for those developers as soon as they upgrade                                           | The version pin itself, `requiredMaximumVersion` or `DISABLE_UPDATES`                                                                                                                                                                                                                                                                                                |
| Model capability assumptions | A model ID that the installed version doesn’t recognize, such as the gateway alias `prod-opus`, runs on default assumptions for [adaptive reasoning](../02-Claude-Code-CLI/model-config.md#adaptive-reasoning-and-fixed-thinking-budgets), the effort parameter, and the [context window](../02-Claude-Code-CLI/model-config.md#correct-the-window-for-a-gateway-or-custom-model-id) until a later version recognizes the ID or you map it | Route Anthropic model IDs at the gateway, or add a [`modelOverrides`](../02-Claude-Code-CLI/model-config.md#override-model-ids-per-version) entry that maps the Anthropic model ID to your alias. On a cloud provider connection, you can instead [declare a pinned model’s capabilities](../02-Claude-Code-CLI/model-config.md#customize-pinned-model-display-and-capabilities)                     |
| Default model and aliases    | The model that new sessions start on by default, and the models that aliases such as `opus` and `sonnet` resolve to, are [built into each version](../02-Claude-Code-CLI/model-config.md#pin-models-for-third-party-deployments) and can change when developers upgrade                                                                                                                                                    | [`ANTHROPIC_DEFAULT_MODEL`](../02-Claude-Code-CLI/model-config.md#set-a-default-model-for-new-sessions) for the model new sessions start on, and the [`ANTHROPIC_DEFAULT_*_MODEL` variables](../02-Claude-Code-CLI/model-config.md#environment-variables), such as `ANTHROPIC_DEFAULT_OPUS_MODEL`, for what each alias resolves to. `ANTHROPIC_DEFAULT_MODEL` requires Claude Code v2.1.236 or later |


[​](#related-resources)

Related resources

- [Connect Claude Code to an LLM gateway](llm-gateway-connect.md): the developer-facing setup steps, with per-surface configuration and a troubleshooting table you can hand to developers
- [Gateway compatibility guide](llm-gateway-protocol.md): the reference for gateway operators, covering endpoints, headers to forward, and the feature pass-through table
- [Which value Claude Code uses](../02-Claude-Code-CLI/settings.md#which-value-claude-code-uses): how managed, project, and user settings combine
- [Delivery mechanisms](managed-settings.md#delivery-mechanisms): where the managed file goes on each platform
- [Set up Claude Code for your organization](admin-setup.md): the wider rollout this gateway is one part of, including policy enforcement, usage visibility, and data handling

---

## Other LLM gateways - Claude Code Docs

Source: https://code.claude.com/docs/en/llm-gateway
Path: 13-Enterprise-Admin/llm-gateway.md

## On this page

- [What a gateway provides](#what-a-gateway-provides)
- [Roll out a gateway](#roll-out-a-gateway)
- [Subscriptions and gateways](#subscriptions-and-gateways)
- [Related pages](#related-pages)

Other gateways

# Other LLM gateways

Copy pageCopy page

Route Claude Code through an LLM gateway your organization already runs. Covers connecting Claude Code to a gateway, rolling one out for your organization, and what Claude Code sends to a gateway.

Copy pageCopy page

This section covers using a gateway product your organization already runs, rather than [Claude apps gateway](claude-apps-gateway.md). For what a gateway is, how it sits between Claude Code and your provider, and how to choose between Claude apps gateway and another product, see the [gateway overview](gateways.md).

- If you’re a developer connecting to an existing gateway: [connect Claude Code to your gateway](llm-gateway-connect.md)
- If you’re an admin rolling out a gateway for your organization: [deploy and distribute a gateway](llm-gateway-rollout.md)
- If you’re configuring a gateway product: the [gateway compatibility guide](llm-gateway-protocol.md)

Any gateway that exposes a [supported API format](llm-gateway-protocol.md#api-formats) works. Anthropic doesn’t endorse, maintain, or audit third-party gateway products, and doesn’t support routing Claude Code to non-Claude models through any gateway. Deploy the gateway following its own documentation, then complete the Claude Code side with the [rollout steps below](#roll-out-a-gateway).


[​](#what-a-gateway-provides)

What a gateway provides

A gateway gives your organization one place to manage:

- **Credentials**: the provider key stays server-side; developers hold gateway credentials instead
- **Usage tracking**: attribute usage by developer or team, regardless of which provider serves the request
- **Cost controls**: enforce budgets and rate limits in one place
- **Audit logging**: log every model request for compliance
- **Provider switching**: change the provider in gateway configuration, without touching developer machines

All of these except provider switching apply whether the upstream is Anthropic’s API or a [cloud provider](../02-Claude-Code-CLI/third-party-integrations.md). Provider switching without reconfiguring developer machines also depends on the gateway exposing a single [Anthropic-format endpoint](llm-gateway-protocol.md#api-formats) regardless of upstream. A gateway that exposes a provider’s own format ties the client configuration to that provider and changes [what Claude Code sends and which defaults it applies](llm-gateway-protocol.md#how-the-connection-method-changes-client-behavior). The tradeoff is that the gateway becomes infrastructure your organization operates. Claude Code adds capabilities with each release, and a gateway that doesn’t forward them breaks the corresponding features, so the gateway product needs to be kept updated as Claude Code evolves. The [gateway compatibility guide](llm-gateway-protocol.md) covers what to forward.


[​](#roll-out-a-gateway)

Roll out a gateway

When you’re ready to roll out an LLM gateway to your organization, the sequence is the same whichever gateway product you choose:

1.  Deploy the gateway and give it your provider credential, so it can authenticate the requests it forwards.
2.  Issue each developer a gateway credential, so usage is attributed to the developer and offboarding revokes one credential.
3.  Distribute the configuration through a [managed settings file](managed-settings.md#delivery-mechanisms) and your secrets tooling, so every machine receives the base URL and a credential. When both are distributed, developers don’t configure anything. If you don’t have settings distribution in place, developers follow the [connect page](llm-gateway-connect.md) to set the variables themselves.
4.  Have each developer [check for the configuration in Claude Code](llm-gateway-connect.md#check-for-an-existing-configuration), so distribution problems surface before they depend on the gateway.

[Roll out an LLM gateway for your organization](llm-gateway-rollout.md) walks each step and shows the configuration files to distribute at each one. The gateway is one part of organization setup; for policy enforcement, usage visibility, and data handling decisions, see [Set up Claude Code for your organization](admin-setup.md). To make a gateway reached through `ANTHROPIC_BASE_URL` the only destination a managed machine may use, set [`allowedProviders`](../02-Claude-Code-CLI/settings-reference.md#allowedproviders) to `["customEndpoint"]` in the same managed settings file and put the gateway’s `ANTHROPIC_BASE_URL` in that file’s `env` block. Claude Code then refuses a session pointed anywhere else, including at Anthropic directly or at a developer’s own proxy, and accepts `ANTHROPIC_BASE_URL` only with the value you set there. For a gateway reached through a provider-specific endpoint variable such as `ANTHROPIC_BEDROCK_BASE_URL`, the `allowedProviders` entry says which variable to pin. Requires Claude Code v2.1.285 or later.


[​](#subscriptions-and-gateways)

Subscriptions and gateways

While a [gateway credential variable](llm-gateway-connect.md#set-the-credential-variable) or `apiKeyHelper` is active, requests carry that credential in place of a developer’s claude.ai subscription login, and the subscription’s usage limits don’t apply to them. Claude Code keeps a saved claude.ai login on the machine but doesn’t send it with those requests. That traffic is billed per token to whoever owns the credential the gateway forwards, such as your organization’s Anthropic Console account, or your Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry account when the gateway routes there. [`ANTHROPIC_BASE_URL`](llm-gateway-connect.md#set-the-base-url-and-credential) is the variable that points Claude Code at the gateway. Setting only that variable, without a gateway credential, doesn’t replace the subscription. Requests still route through the gateway, but a saved claude.ai login remains the active credential, so its usage limits and billing apply. Gateways that pass this traffic on to Anthropic must forward the OAuth capability in `anthropic-beta`; see the [request headers reference](llm-gateway-protocol.md#request-headers).


[​](#related-pages)

Related pages

- [Gateway overview](gateways.md): how a gateway works and how to choose between Claude apps gateway and another product
- [Claude apps gateway](claude-apps-gateway.md): Anthropic’s self-hosted gateway with SSO sign-in and OTLP telemetry
- [Connect Claude Code to an LLM gateway](llm-gateway-connect.md): set the base URL and credential on your own machine, with per-surface configuration and a troubleshooting table
- [Roll out an LLM gateway for your organization](llm-gateway-rollout.md): the admin checklist for deploying a gateway, issuing developer credentials, and distributing managed settings
- [Gateway compatibility guide](llm-gateway-protocol.md): what Claude Code sends to a gateway, for operators configuring one, covering endpoints, headers to forward, and feature pass-through

---

## Manage model access for your organization | Claude Help Center

Source: https://support.claude.com/en/articles/15694740-manage-model-access-for-your-organization
Path: 13-Enterprise-Admin/manage-model-access-for-your-organization.md

# Manage model access for your organization


This guide explains how to control which Claude models members of your organization can use, and how to cap the effort level members can select on each model. Model access and effort limits can be set for your whole organization or for specific custom roles.

Model access settings are available for Enterprise plan organizations. Primary Owners, Owners, and members whose custom role grants the Identity & Access permission can manage them in **[Organization settings \> Models](https://claude.ai/admin-settings/models)**.

To set the model new conversations start on, see **[Set a default model for your organization](https://support.claude.com/en/articles/15330088)**.

------------------------------------------------------------------------

## How model access works

Model access and effort limits are determined at two levels:

- **Organization level:** each model is enabled or disabled for everyone in your organization. Disabling a model here removes it for every member, including Owners and Admins. You can also set a maximum effort level for each enabled model, which applies to every member.

- **Custom role level:** for members on custom roles, each role grants access to a subset of the models enabled at the organization level. A role can also cap the maximum effort level members can select on each model, at or below the organization's cap for that model.

The organization setting is the ceiling. A role can't grant access to a model that's disabled for the organization, and a role can't allow an effort level higher than the organization's cap. When the feature first becomes available, every model is enabled and set to its highest effort level at both levels, so nothing changes for your members until you adjust these settings.

**Note:** Haiku models are always available to every member and can’t be disabled. This guarantees members always have at least one model to fall back to.

## Who each level affects

- Disabling a model or capping its effort level at the organization level affects every member, including Primary Owners, Owners, Admins, and Users.

- Role-level model access and effort limits affect only members whose role is set to "Custom." Members with the User, Admin, or Owner roles can use every model enabled at the organization level, up to the organization's effort cap for that model.

------------------------------------------------------------------------

## Enable or disable a model for your organization

1.  Navigate to **[Organization settings \> Models](https://claude.ai/admin-settings/models)**.

2.  Under **Model access**, find the model you want to change.

3.  To enable a model, switch the toggle next to it on, then click the role dropdown to select the roles that can access it.

4.  To disable a model, click the role dropdown and deselect the roles before switching the toggle off.

5.  Click "Save."

If any custom role uses the model you’re disabling as its default, you’ll be prompted to change that role’s default before the change can be saved.


------------------------------------------------------------------------

## Limit the maximum effort level for your organization

Effort limits determine how much computation members can apply per response on each model. Higher effort levels produce more thorough responses but consume more usage. An organization-level effort cap applies to every member and is the highest level any custom role can allow.

1.  Navigate to **[Organization settings \> Model](https://claude.ai/admin-settings/models)**[s](https://claude.ai/admin-settings/models).

2.  Under **Model access**, find the model you want to change.

3.  Click the effort level dropdown to select the maximum level.

4.  Click "Save."

If any custom role has an effort cap higher than the new organization cap for that model, the role's cap is lowered to match. Members see only effort levels at or below the organization cap in their model menu. Available effort levels differ depending on the model, and some models don't support effort level settings at all. For an explanation of each level, see **[Change the model, effort, and thinking settings](../15-Claude-AI-Features/change-the-model-effort-and-thinking-settings-claude-help-center.md)**.

------------------------------------------------------------------------

## Set model access for a custom role

1.  Navigate to **[Organization settings \> Roles](https://claude.ai/admin-settings/roles)**.

2.  Click the role you want to edit, or click “Add role” to create one.

3.  Select the "Models" tab.

4.  Under **Model access**, switch each model on or off. Models disabled at the organization level appear but can’t be enabled until you turn them on for the organization.

5.  Click “Save.”

Only models the role grants access to can be selected as that role’s default model.


------------------------------------------------------------------------

## Limit the maximum effort level for a custom role

Effort limits determine how much computation members on a role can apply per response on each model. Higher effort levels produce more thorough responses but consume more usage. A role's effort cap can't be higher than the organization's cap for that model.

1.  Navigate to **[Organization settings \> Roles](https://claude.ai/admin-settings/roles)**.

2.  Click the role you want to edit.

3.  Select the "Models" tab.

4.  Next to a model, click the gear icon and choose a level.

5.  Click "Save" to save your changes.


Members on the role see only effort levels at or below the cap in their model menu. Note that available effort levels differ depending on the model, and some models don’t support effort level settings at all. For an explanation of each level, see **[Change the model, effort, and thinking settings](../15-Claude-AI-Features/change-the-model-effort-and-thinking-settings-claude-help-center.md)**.

------------------------------------------------------------------------

## How access combines across multiple roles

If a member belongs to multiple groups with different custom roles, model settings combine like other role permissions:

- **Model access is additive.** The member can use every model granted by any of their roles, as long as it’s enabled at the organization level.

- **Effort limits take the highest cap.** For each model, the member gets the highest maximum effort level any of their roles allows, never exceeding the organization's cap for that model.

For how default models are chosen across multiple roles, see **[Set a default model for your organization](https://support.claude.com/en/articles/15330088)**.

For details on creating roles and assigning them to groups, see **[Manage custom roles on Enterprise plans](https://support.claude.com/en/articles/13930452)**.

------------------------------------------------------------------------

## What users see

In every covered product, the model picker shows only the models the member has access to. Effort levels above the organization's cap, or above a role's cap, don't appear in the effort menu.

Model availability also depends on the product. Each product supports a different set of models, so an enabled model appears only in the products that support it.

If you disable a model a member is using in an open conversation or session, that conversation falls back to the member's default model the next time they open it. If the member sends a message while you're making the change, they'll see an error that the model isn't available and be prompted to switch. If you lower a model's effort cap while a member has a higher level selected, their next message on that model uses the new maximum.

------------------------------------------------------------------------

## Where model access settings apply

Model access settings are enforced across these products:

[TABLE]

**Note:** Members on Claude Code CLI versions earlier than 2.1.199 still see disabled models and effort levels in the picker, but requests using them are rejected.

## Model access and Claude Code managed settings

If your organization also configures Claude Code through `managed-settings.json`, the `availableModels` setting and model access work together. In Claude Code CLI and IDE, members see only models that appear in `availableModels` *and* are enabled by their model access settings—a model removed by either one is unavailable.

Managed settings for models apply only to local Claude Code sessions in your terminal or IDE, not to cloud sessions or to Claude Code in Claude Desktop. For consistent behavior across all Claude Code surfaces, we recommend using model access settings alone. For more on managed settings, see **[Claude Code settings](../02-Claude-Code-CLI/settings.md#settings-files)**.

---

## Deploy managed settings - Claude Code Docs

Source: https://code.claude.com/docs/en/managed-settings
Path: 13-Enterprise-Admin/managed-settings.md

## On this page

- [Deploy a managed settings file](#deploy-a-managed-settings-file)
- [Choose a delivery mechanism](#choose-a-delivery-mechanism)
  - [Where and when a policy applies](#where-and-when-a-policy-applies)
  - [Where each mechanism stores the policy](#where-each-mechanism-stores-the-policy)
  - [Split a file-based policy across teams](#split-a-file-based-policy-across-teams)
- [How Claude Code combines managed sources](#how-claude-code-combines-managed-sources)
  - [When an admin document counts as present](#present-admin-documents)
  - [Keys read from every admin source](#keys-read-from-every-admin-source)
  - [Compose every managed source](#compose-every-managed-source)
  - [Compute the policy with a helper program](#compute-the-policy-with-a-helper-program)
  - [Let an embedding host add policy](#let-an-embedding-host-add-policy)
  - [Keep Cowork folder access when only managed rules apply](#keep-cowork-folder-access-when-only-managed-rules-apply)
  - [What a developer can change](#what-a-developer-can-change)
- [Check that a policy is in force](#check-that-a-policy-is-in-force)
  - [Read the source in /status](#read-the-source-in-%2Fstatus)
  - [Find entries Claude Code dropped](#find-entries-claude-code-dropped)
  - [Keys that fail closed](#keys-that-fail-closed)
  - [Invalid values inside sandbox](#invalid-values-inside-sandbox)
- [Keys only a managed source can set](#keys-only-a-managed-source-can-set)
- [Turn telemetry off for your organization](#turn-telemetry-off-for-your-organization)
- [See also](#see-also)

Setup and access

# Deploy managed settings

Copy pageCopy page

Deploy managed settings to every developer’s machine: delivery mechanisms per OS, how Claude Code combines managed sources, and how to verify enforcement.

Copy pageCopy page

Managed settings are the settings your organization deploys to every developer’s machine. Claude Code applies them above every other level, so no user, project, local, or `--settings` value overrides them, apart from a few [security-sensitive exceptions](../02-Claude-Code-CLI/settings.md#exceptions-to-managed-settings-precedence) where a stricter value from a lower level still counts. This page is for the administrator who deploys managed settings or debugs why one isn’t applying. To decide what to enforce, start with the [Decide what to enforce](admin-setup.md#decide-what-to-enforce) table. For the claude.ai console path, see [Server-managed settings](server-managed-settings.md). For which file a developer’s own values go in, see [Settings](../02-Claude-Code-CLI/settings.md).


[​](#deploy-a-managed-settings-file)

Deploy a managed settings file

This is the quickest way to put a policy on each machine: a `managed-settings.json` file. If you haven’t picked how to deliver managed settings yet, or your devices are under MDM or developers run cloud sessions, read [Choose a delivery mechanism](#choose-a-delivery-mechanism) first.

1

Write managed-settings.json

Write a `managed-settings.json` that holds the keys you’ve decided to enforce, in the same JSON shape as `settings.json`. The [Decide what to enforce](admin-setup.md#decide-what-to-enforce) table lists the keys behind each control, and each entry in the [settings reference](../02-Claude-Code-CLI/settings-reference.md) says whether a managed source can set it. This file blocks two file reads, turns off bypass mode, and makes Claude Code ignore permission rules from user, project, and local files and from `--allowedTools`:

managed-settings.json

```python
{
  "permissions": {
    "deny": [
      "Read(./.env)",
      "Read(./secrets/**)"
    ],
    "disableBypassPermissionsMode": "disable"
  },
  "allowManagedPermissionRulesOnly": true
}
```

For a fuller example that shows the shape of more managed keys, including the login method, models, MCP servers, and marketplaces, see [An organization’s managed settings](../02-Claude-Code-CLI/settings-example.md#an-organizations-managed-settings).

2

Place the file on each machine

Save the file as `managed-settings.json` in the system directory for the operating system, using whatever tooling already places files on your fleet:

- **macOS**: `/Library/Application Support/ClaudeCode/managed-settings.json`
- **Linux and WSL**: `/etc/claude-code/managed-settings.json`
- **Windows**: `C:\Program Files\ClaudeCode\managed-settings.json`

3

Confirm the policy applied

On one machine, run `/status` inside Claude Code. The `Setting sources` line shows `Enterprise managed settings (file)`. Roll out to the rest of the fleet after that; [Check that a policy is in force](#check-that-a-policy-is-in-force) covers what to look at when the line is missing.


[​](#choose-a-delivery-mechanism)

Choose a delivery mechanism

The file in the steps above is one of four ways to get managed settings onto a machine. Every mechanism carries the same policy keys as a `settings.json` file, so the [settings reference](../02-Claude-Code-CLI/settings-reference.md) applies to all of them. A few keys are tied to particular sources, and each entry’s Scope line says which:

- **Delivery controls**: [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper), [`wslInheritsWindowsSettings`](../02-Claude-Code-CLI/settings-reference.md#wslinheritswindowssettings), and [`managedSourcesBehavior`](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior)
- **Gateway login keys**: [`forceLoginGatewayUrl`](../02-Claude-Code-CLI/settings-reference.md#forcelogingatewayurl), [`gatewayInternalNetworks`](../02-Claude-Code-CLI/settings-reference.md#gatewayinternalnetworks), and the `"gateway"` value of [`forceLoginMethod`](../02-Claude-Code-CLI/settings-reference.md#forceloginmethod)

A managed settings file, an MDM profile, or the claude.ai console applies one policy to everyone it reaches. To give one group of developers a different policy, deploy a different file or profile to that group; the claude.ai console [can’t target a group yet](server-managed-settings.md#current-limitations), while a self-hosted [Claude apps gateway](claude-apps-gateway.md) delivers managed settings per IdP group. When more than one mechanism delivers a policy to the same machine, Claude Code by default uses one and ignores the others. [How Claude Code combines managed sources](#how-claude-code-combines-managed-sources) gives the order and the opt-in that applies every source. The MDM and file rows are together called endpoint-managed settings, because the policy is stored on the developer’s device, as opposed to the server-managed row, where Claude Code fetches it. Pick a mechanism by how you already manage devices, using the table below.

| Mechanism                                                   | How you deliver it                                                                                                                                                                                                | When Claude Code reads it                                                                                                                                                                                                                                    | Use it when                                                                                    |
|:------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------|
| [Server-managed settings](server-managed-settings.md) | In the claude.ai admin console, or on a self-hosted [Claude apps gateway](claude-apps-gateway.md)                                                                                                           | Fetched at startup and polled hourly; see [changes that need approval](#where-and-when-a-policy-applies)                                                                                                                                                     | You want one place to change policy for a claude.ai organization without touching each machine |
| MDM or OS-level policy                                      | As a macOS configuration profile or a Windows `HKLM` registry value, through Jamf, Intune, Group Policy, or a similar tool; see [where each mechanism stores the policy](#where-each-mechanism-stores-the-policy) | Read at startup and checked for changes every 30 minutes                                                                                                                                                                                                     | You already manage devices with MDM or Group Policy                                            |
| File-based                                                  | As `managed-settings.json` in a system directory on each machine; see [where each mechanism stores the policy](#where-each-mechanism-stores-the-policy)                                                           | Read at startup and reloaded when a file changes                                                                                                                                                                                                             | Machines without MDM, Linux hosts, or images you build yourself                                |
| HKCU registry, Windows and WSL                              | As a Windows `HKCU` registry value; see [where each mechanism stores the policy](#where-each-mechanism-stores-the-policy)                                                                                         | Read at startup and checked for changes every 30 minutes; Claude Code uses it only when [no admin document is present above it](#present-admin-documents) and no [host-supplied parent settings](#let-an-embedding-host-add-policy) supply a restrictive key | You can’t write the machine-level `HKLM` key                                                   |

Starter templates for Jamf, Iru, Intune, and Group Policy are in the [MDM examples repository](https://github.com/anthropics/claude-code/tree/main/examples/mdm). If your organization has the [HIPAA configuration](../02-Claude-Code-CLI/hipaa-setup.md#deploy-managed-settings) applied, see `settings-hipaa.json` and `README-hipaa.md` in the [settings examples repository](https://github.com/anthropics/claude-code/tree/main/examples/settings) for a fuller `managed-settings.json` with sandboxing, a network allowlist, credential protections, and local data retention. For managed MCP servers, which you deploy alongside any of these through `managed-mcp.json` or provide through the [`managedMcpServers`](../02-Claude-Code-CLI/settings-reference.md#managedmcpservers) key, see [Managed MCP configuration](../06-MCP-Tools/General/managed-mcp.md).


[​](#where-and-when-a-policy-applies)

Where and when a policy applies

A deployed policy reaches the developer’s sessions as follows:

- **Surfaces**: on the developer’s machine, the terminal, the VS Code and JetBrains extensions, the desktop app’s Code tab, and [Agent SDK](../05-Agent-SDK/agent-sdk-typescript.md) sessions read all of these sources. Agent SDK sessions load managed settings even when `settingSources` excludes the user, project, and local files.
- **Cloud sessions**: a session in an Anthropic-hosted environment doesn’t read a device’s MDM profile or file, so policy for it has to come from server-managed settings. A session in a [self-hosted environment](self-hosted-environments.md) also reads the managed settings file in its runner image, by default only when server-managed settings deliver no policy key, apart from the [keys Claude Code reads from every admin source](#keys-read-from-every-admin-source). [How Claude Code combines managed sources](#how-claude-code-combines-managed-sources) covers the opt-in that applies both.
- **Claude Tag sessions**: [Claude Tag](https://claude.com/docs/claude-tag/overview) sessions run in cloud environments but don’t receive server-managed settings. In a [self-hosted environment](self-hosted-environments.md), they still read the managed settings file in the runner image. Configure Claude Tag itself in the [Claude Tag admin settings](https://claude.com/docs/claude-tag/admins/customize).
- **Cowork sessions**: [Cowork](https://claude.com/docs/cowork/overview) in the Claude Desktop app runs its sessions on Claude Code. In a Cowork session, Claude Code never fetches server-managed settings from the claude.ai admin console, even when the user signs in with a Team or Enterprise account, so which policy applies depends on where the session runs:
  - **On the user’s machine**: by default, Claude Code in a Cowork session reads the MDM or OS-level policy and the managed settings file on that device, so deploy policy there.
  - **In a full VM sandbox**: when your Claude Desktop managed configuration sets [`requireCoworkFullVmSandbox`](https://claude.com/docs/third-party/claude-desktop/configuration#requirecoworkfullvmsandbox), Claude Code runs inside a virtual machine where the device’s MDM policy and managed settings file aren’t present.
  - **Remote Cowork sessions**: these run on Anthropic-managed VMs, where Claude Code has no device policy to read.

  Wherever the session runs, claude.ai applies the admin console’s [`strictKnownMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#strictknownmarketplaces) and [`blockedMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#blockedmarketplaces) lists itself when anyone adds a marketplace from a git repository on claude.ai or from **Customize** in Cowork. [How restrictions work](../08-Plugins-Skills/plugins-org.md#restrict-what-users-can-install) describes that check. The [surface coverage](../02-Claude-Code-CLI/model-config.md#surface-coverage) table compares Cowork with the other surfaces.
- **Running sessions**: most changes reach a running session on the schedule in the [delivery mechanism table](#choose-a-delivery-mechanism), without a restart.
  - Changes to [`forceRemoteSettingsRefresh`](../02-Claude-Code-CLI/settings-reference.md#forceremotesettingsrefresh), [`requiredMinimumVersion`](../02-Claude-Code-CLI/settings-reference.md#requiredminimumversion), and [some user-editable keys](../02-Claude-Code-CLI/settings.md#when-edits-take-effect) take effect at the next session start.
  - A new or changed [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) entry takes effect at the next launch. If server-managed settings shadow the helper at that launch, the helper runs as soon as a fetch reports those settings removed.
- **Changes that need approval**: apart from the [updates that wait for the next launch](server-managed-settings.md#fetch-and-caching-behavior), a server-managed change to a setting that [needs approval](server-managed-settings.md#security-approval-dialogs), such as a hook or an `env` variable, waits for the developer to accept the dialog in an interactive session, and applies for the current run in a session an IDE extension or the Agent SDK hosts. Other server-managed changes apply on the next poll.
- **Long-lived sessions**: a session left open for weeks can still lag a rollout. [`requiredMinimumVersion`](../02-Claude-Code-CLI/settings-reference.md#requiredminimumversion) blocks an outdated binary from starting and doesn’t end a session that’s already running.


[​](#where-each-mechanism-stores-the-policy)

Where each mechanism stores the policy

The keys are the same everywhere, but each mechanism stores them in a different place and shape:

- **Server-managed**: Anthropic’s servers, or your gateway, hold the policy. Claude Code keeps a local cache that it applies at startup and [replaces on each successful fetch](server-managed-settings.md#security-considerations).
- **macOS configuration profile**: the `com.anthropic.claudecode` managed preferences domain. Use the same top-level keys as `managed-settings.json`, with nested settings as dictionaries and lists as plist arrays.
- **Windows HKLM registry**: the JSON as a `REG_SZ` or `REG_EXPAND_SZ` value named `Settings` under `HKLM\SOFTWARE\Policies\ClaudeCode`.
- **File-based**: `managed-settings.json`, an optional `managed-settings.d/` directory, and `managed-mcp.json` in the system directory: `/Library/Application Support/ClaudeCode/` on macOS, `/etc/claude-code/` on Linux and WSL, and `C:\Program Files\ClaudeCode\` on Windows. Claude Code doesn’t read the legacy Windows path `C:\ProgramData\ClaudeCode\managed-settings.json`. You can deploy any of these files as a symbolic link, and Claude Code reads the file the link leads to. The policy still applies when the link leads outside the system directory or Claude Code can’t confirm that it stays inside. A warning naming the file then appears in `/status` and `claude doctor`. To clear the warning, point the link at a target inside the system directory.
- **Windows HKCU registry**: the same `Settings` value under `HKCU\SOFTWARE\Policies\ClaudeCode`.


[​](#split-a-file-based-policy-across-teams)

Split a file-based policy across teams

If several teams own parts of one policy, put each part in its own file in `managed-settings.d/`, next to `managed-settings.json` in the same system directory, instead of editing one shared file. Claude Code merges `managed-settings.json` first, then every `*.json` file in the directory in alphabetical order. Name the files with numeric prefixes to control the order, such as `10-telemetry.json` and `20-security.json`. Claude Code ignores hidden files and files that don’t end in `.json`. When two files set the same key, Claude Code combines them by these rules:

- **Single values**, such as `"model": "opus"` or `"cleanupPeriodDays": 7`: the later file’s value replaces the earlier one
- **Lists**, such as `permissions.deny` or `sandbox.network.allowedDomains`: the two lists combine, with duplicates removed
- **Nested blocks**, such as `env` or `sandbox`: the two blocks merge key by key, and each key inside follows these same rules
- **`fallbackModel`**: the later chain replaces the earlier one whole
- **[`extraKnownMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#extraknownmarketplaces) and [`managedMcpServers`](../02-Claude-Code-CLI/settings-reference.md#managedmcpservers)**: a later entry with the same name replaces the earlier one whole
- **[`modelPicker`](../02-Claude-Code-CLI/settings-reference.md#modelpicker)**: the later lineup replaces the earlier one whole


[​](#how-claude-code-combines-managed-sources)

How Claude Code combines managed sources

When your organization delivers more than one managed source to the same machine, the [`managedSourcesBehavior`](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior) key decides what Claude Code does with the others:

- **`"first-wins"`, the default**: Claude Code uses the highest-ranked source that delivers at least one policy key and ignores the rest rather than merging them, apart from the keys in [Keys read from every admin source](#keys-read-from-every-admin-source). Claude Code shows no warning for the sources it skips; `/status` [names the source it used and the ones it skipped](#read-the-source-in-/status).
- **`"merge"`**: Claude Code applies every admin source that delivers a policy key and combines them by kind of key: on most keys the higher-ranked source’s value applies, lists union, and locks take the strictest value. [Compose every managed source](#compose-every-managed-source) says where to set the key and how each kind of key combines. Requires Claude Code v2.1.242 or later.

Both settings rank the sources the same way. These terms recur in this section:

- **Policy key**: any settings key other than the two control keys, [`wslInheritsWindowsSettings`](../02-Claude-Code-CLI/settings-reference.md#wslinheritswindowssettings) and [`managedSourcesBehavior`](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior). A managed settings file or MDM policy that contains only those doesn’t count, and Claude Code moves on to the next source.
- **Admin source**: one of the first three sources below. The HKCU registry is user-writable and isn’t one.

Claude Code checks the sources in this order, highest priority first:

1.  Remote settings, delivered from claude.ai as [server-managed settings](server-managed-settings.md) or by a [Claude apps gateway](claude-apps-gateway.md). Claude Code fetches this source only when the session authenticates to Anthropic’s API directly with an [eligible credential](server-managed-settings.md#platform-availability), signs in to a gateway with `/login`, or runs in Claude Desktop’s Code tab behind a gateway that [serves that session settings](claude-apps-gateway-config.md#apply-code-settings-in-the-code-tab). On other providers, or when `ANTHROPIC_BASE_URL` points somewhere other than Anthropic’s API, it starts at the next source
2.  MDM or OS-level policies: the macOS plist or the HKLM registry key
3.  Managed settings files, `managed-settings.d/*.json` and `managed-settings.json` merged together
4.  The HKCU registry, on Windows, and on WSL once the HKLM registry or the Windows managed settings file turns [`wslInheritsWindowsSettings`](../02-Claude-Code-CLI/settings-reference.md#wslinheritswindowssettings) on and the HKCU value also sets it. Claude Code reads it only when [no admin document is present above it](#present-admin-documents) and no [host-supplied parent settings](#let-an-embedding-host-add-policy) supply a restrictive key

This diagram shows the ranking, with examples of the cross-source keys Claude Code reads from the first three sources under either setting:


[​](#present-admin-documents)

When an admin document counts as present

In the [ranking of managed sources](#how-claude-code-combines-managed-sources), Claude Code never applies the user-writable HKCU registry beneath an admin document that is present. A document is present when:

- It sets any policy key to a value other than `null`, even a value Claude Code can’t read
- It is an HKLM value, managed settings file, or `managed-settings.d` directory that exists but can’t be read

On WSL, `/etc/claude-code` is user-writable as well, and the [`wslInheritsWindowsSettings`](../02-Claude-Code-CLI/settings-reference.md#wslinheritswindowssettings) entry says when the Windows documents stand above it.


[​](#keys-read-from-every-admin-source)

Keys read from every admin source

Under the default `"first-wins"` setting, Claude Code reads most keys only from the [source it selected](#how-claude-code-combines-managed-sources), and ignores a value in a lower-ranked source even when the selected source leaves that key unset. A few keys work differently. Claude Code reads them from every admin source, so a lower-ranked MDM policy or managed settings file can still set them when the selected source doesn’t. Claude Code leaves the user-writable HKCU registry out of that scan; when HKCU is the only source and no host supplies parent settings, HKCU applies like any selected source. The cross-source keys include:

- `sandbox.network.allowManagedDomainsOnly` and `sandbox.filesystem.allowManagedReadPathsOnly`: a `true` in any admin source turns the lock on. While a lock is on, Claude Code unions the allowlist it locks, `sandbox.network.allowedDomains` together with `WebFetch(domain:...)` allow rules, or `sandbox.filesystem.allowRead`, across every admin source. Without the lock, Claude Code treats the allowlist like any other key, so under `"first-wins"` an unselected admin source’s allowlist is ignored
- `allowAllClaudeAiMcps`
- `allowManagedMcpServersOnly`: a `true` in any admin source turns the MCP allowlist lock on. While the lock is on, the managed `allowedMcpServers` list comes from the highest-ranked admin source that sets one. A server-managed list replaces a lower source’s list rather than combining with it. If no admin source sets a list, every server that passes the denylist loads, unless [parent settings](#let-an-embedding-host-add-policy) supply a list. Without the lock, Claude Code reads `allowedMcpServers` from the managed source it applies, so under `"first-wins"` an unselected admin source’s list is ignored. Requires Claude Code v2.1.273 or later
- `deniedMcpServers` and [`disableClaudeAiConnectors`](../02-Claude-Code-CLI/settings-reference.md#disableclaudeaiconnectors): an entry or a `true` in any admin source applies. Requires Claude Code v2.1.273 or later
- The sandbox binary paths `sandbox.bwrapPath` and `sandbox.socatPath`
- The sandbox `ripgrep` binary, [`sandbox.ripgrep`](../02-Claude-Code-CLI/settings-reference.md#sandbox-ripgrep)
- `sandbox.filesystem.disabled` and `sandbox.network.strictAllowlist`
- [`useAutoModeDuringPlan`](../02-Claude-Code-CLI/settings-reference.md#useautomodeduringplan), [`syncClaudeAiSkills`](../02-Claude-Code-CLI/settings-reference.md#syncclaudeaiskills), and [`syncClaudeAiPlugins`](../02-Claude-Code-CLI/settings-reference.md#syncclaudeaiplugins), where a `false` from any admin source turns the behavior off. A `false` in the developer’s user or local settings turns it off too; each key can only deny
- [`enableArtifact`](../02-Claude-Code-CLI/settings-reference.md#enableartifact), where a `false` from any admin source turns the [Artifact tool](../02-Claude-Code-CLI/artifacts.md) off. A `false` in the developer’s user, project, or local settings turns it off too, and no source turns it back on; see [which lower-level values still count](../02-Claude-Code-CLI/settings.md#exceptions-to-managed-settings-precedence). Requires Claude Code v2.1.242 or later
- [`maxEffortLevel`](../02-Claude-Code-CLI/settings-reference.md#maxeffortlevel), where the lowest cap in any admin source applies. If a developer sets a lower cap in their own settings or with `--settings`, Claude Code applies that one; no source can raise the cap. Requires Claude Code v2.1.267 or later
- A commit-trailer opt-out in `attribution`, or in the deprecated `includeCoAuthoredBy`, from any tier
- [`forceRemoteSettingsRefresh`](server-managed-settings.md)
- `env`, merged per variable across the admin sources: each variable comes from the highest-priority source that defines it, so lower sources fill in variables the higher ones leave unset. A few variables follow their own rules; [Per-key exceptions across managed sources](server-managed-settings.md#per-key-exceptions-across-managed-sources) names each one. Requires Claude Code v2.1.223 or later. Before v2.1.223, Claude Code applied the selected source’s whole `env` block only

The [gateway login keys](#choose-a-delivery-mechanism) follow a separate rule. Claude Code never reads them from server-managed settings, so while server-managed settings are the selected source, the highest-ranked admin source on the machine that carries a policy key still supplies them. A value in an admin source ranked below that one, or in the HKCU registry, is ignored. [`allowedProviders`](../02-Claude-Code-CLI/settings-reference.md#allowedproviders) has its own rule: its entry’s Scope note says how a list set on the machine combines with a server-managed one. Requires Claude Code v2.1.285 or later. When an admin source sets `allowManagedMcpServersOnly` or an `allowedMcpServers` list and that value isn’t the one in force, `/status` and `claude doctor` name that source and key.


[​](#compose-every-managed-source)

Compose every managed source

To have Claude Code apply every admin source your organization delivers, set [`managedSourcesBehavior`](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior) to `"merge"` in the highest-ranked source you deploy. Claude Code reads the key only from the highest-ranked source that carries either the key or a policy key, so a lower source can’t opt itself into merging with the source above it, and a machine that never receives server-managed settings needs the key in its MDM profile too. The user-writable HKCU registry never merges with another source. Requires Claude Code v2.1.242 or later. Under `"merge"`, Claude Code adds a lower source’s list entries, such as `permissions.allow` rules and hooks, to the policy, so turn it on only when every source ranked below your highest one is under an administrator’s control. This table shows how Claude Code combines each kind of key under `"merge"`. The [`managedSourcesBehavior` entry](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior) names every key in three of the rows: restriction allowlists, values taken whole, and keys read from the highest-ranked source only.

| Kind of key                                   | How Claude Code combines it                                                                                                                         | Examples                                                                                                                    |
|:----------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------|
| Lists                                         | Combines the entries from every source                                                                                                              | `permissions.allow`, `hooks`, `sandbox.network.allowedDomains`, `deniedMcpServers`, `deniedModels`                          |
| Locks                                         | Applies the strictest value any source sets; a looser value applies only from the highest-ranked source                                             | `allowManagedHooksOnly`, `permissions.disableBypassPermissionsMode`, `crossSessionInbound`, `availableModelsMatch`          |
| Restriction allowlists                        | Takes the list whole from the highest-ranked source that sets it, without adding entries from lower sources                                         | `availableModels`, `allowedMcpServers`, `strictKnownMarketplaces`, `allowedChannelPlugins`, and the `fallbackModel` chain   |
| Values taken whole                            | Takes the value whole from the highest-ranked source that sets it, without combining entries or fields from lower sources                           | `sandbox.credentials.awsPairs`, `sandbox.ripgrep`                                                                           |
| Provided MCP servers                          | Combines the server names from every source; when two sources set the same name, applies the higher-ranked source’s whole entry                     | `managedMcpServers`                                                                                                         |
| Keys read from the highest-ranked source only | Ignores the key in every lower source, even when the highest-ranked source leaves it unset                                                          | Credential helpers such as `apiKeyHelper`, login pins such as `forceLoginOrgUUID`, `modelPicker`, `permissions.defaultMode` |
| `env`                                         | Merges per variable across admin sources under either setting, as [Keys read from every admin source](#keys-read-from-every-admin-source) describes |                                                                                                                             |
| Every other key                               | Takes the value from the highest-ranked source that sets it                                                                                         | `model`, `cleanupPeriodDays`                                                                                                |

To confirm which sources combined on a machine, [read the `Setting sources` line in `/status`](#read-the-source-in-/status); that section says what each label means.


[​](#compute-the-policy-with-a-helper-program)

Compute the policy with a helper program

A [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) is an executable your MDM policy or managed settings file names, and Claude Code runs it to compute managed settings at startup. When the selected source configures one and the helper emits a `managedSettings` object, that output changes what Claude Code reads:

- **The emitted `managedSettings` object is the only managed settings for the session**, including for the [keys it otherwise reads from every admin source](#keys-read-from-every-admin-source), apart from [`forceRemoteSettingsRefresh`, which has its own startup rule](../02-Claude-Code-CLI/settings-reference.md#forceremotesettingsrefresh)

For which helper runs fail, and what Claude Code does when one does, see [Helper failures](../02-Claude-Code-CLI/settings-reference.md#helper-failures).


[​](#let-an-embedding-host-add-policy)

Let an embedding host add policy

When another application launches Claude Code, such as Claude Desktop, an IDE extension, or an Agent SDK app, that host can pass its own managed settings through the SDK `managedSettings` option. Claude Code calls these parent settings. By default, Claude Code ignores parent settings whenever an admin source is present: server-managed settings, an MDM or OS-level policy, or a managed settings file. To have Claude Code merge parent settings alongside an admin source, set [`parentSettingsBehavior`](../02-Claude-Code-CLI/settings-reference.md#parentsettingsbehavior) to `"merge"` in the highest-priority managed source; Claude Code reads the key from that source only. Claude Code then keeps only the host’s values that restrict what Claude can do, with one gap to know about: unless you also set the `allowManaged*Only` locks, the host’s permission allow rules and sandbox allowlists still apply. See [Restrict parent settings](claude-apps-gateway.md#restrict-parent-settings) for the locks. A [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) can turn parent merging off regardless of this key; its entry says when. Claude Code also applies these checks to parent-supplied values on their own:

- When any admin source sets `allowManagedPermissionRulesOnly`, Claude Code drops [parent-supplied](claude-apps-gateway.md#restrict-parent-settings) permission allow rules and `additionalDirectories` as it reads them, even when a higher-priority source leaves the key unset. The key’s effect on your own permission rules comes from the managed settings Claude Code applies, or from parent settings you’ve chosen to merge
- Claude Code enforces the `forceLoginOrgUUID` or `allowedMcpServers` value in the managed settings it applies and blocks a parent-supplied one. Outside the MCP allowlist lock, a value in a lower admin source that Claude Code doesn’t apply neither applies nor blocks the parent’s. On Claude Code v2.1.273 or later, while `allowManagedMcpServersOnly` is on, the `allowedMcpServers` list from the highest-ranked admin source that sets one applies and blocks the parent’s, as a [cross-source key](#keys-read-from-every-admin-source). The parent’s list applies only when no admin source sets one. The [`managedSourcesBehavior`](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior) entry says which source supplies each key under `"merge"`. Before v2.1.223, a value in any admin source blocked the parent’s
- For `availableModels`, Claude Code enforces the value in the managed settings it applies and blocks a parent-supplied list
- For `strictKnownMarketplaces`, Claude Code likewise enforces the list in the managed settings it applies and blocks a parent-supplied one. The parent’s list applies only when no applied managed source sets one. Requires Claude Code v2.1.282 or later
- For `allowedProviders`, a list in the [selected managed source](#which-managed-source-claude-code-uses) blocks a parent-supplied one, and under the [`managedSourcesBehavior`](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior) `"merge"` opt-in a list in any admin source does. Requires Claude Code v2.1.285 or later
- A parent-supplied `blockedMarketplaces` applies in addition to any blocklist that a managed source sets. Requires Claude Code v2.1.282 or later


[​](#keep-cowork-folder-access-when-only-managed-rules-apply)

Keep Cowork folder access when only managed rules apply

[Cowork](https://claude.com/docs/cowork/overview) in the Claude Desktop app runs its sessions on Claude Code and grants each session access to its working folders, such as the folder the user connects, through allow rules it supplies when it launches the session. When your managed policy sets [`allowManagedPermissionRulesOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedpermissionrulesonly), Claude Code keeps only the allow rules in the managed policy: it drops allow rules a host supplies as parent settings, as `--allowedTools`, or in a settings file, so writes to those folders lose their pre-approval. In a Cowork session that asks before edits, Cowork can’t show the prompt, and Claude reports each write as blocked because the path resolves to a protected location or a path outside the connected folder. To restore the writes, add allow rules for those folders to the managed source Claude Code [selects](#precedence-within-the-managed-tier) on those machines: on an MDM-managed fleet, that’s the MDM policy rather than a separate managed settings file. This example uses the file form, and an MDM policy takes the same keys. It keeps `allowManagedPermissionRulesOnly` set and allows edits under a `CoworkProjects` folder in each user’s home directory; replace the path with the folders your users connect:

managed-settings.json

```python
{
  "allowManagedPermissionRulesOnly": true,
  "permissions": {
    "allow": [
      "Edit(~/CoworkProjects/**)"
    ]
  }
}
```

After you deploy the policy, Claude can save files under that folder in a new Cowork session. [Read and Edit rules](../02-Claude-Code-CLI/permissions.md#read-and-edit) cover the path syntax, including the `//` form for absolute paths.


[​](#what-a-developer-can-change)

What a developer can change

A developer’s own settings files, `--settings` values, and project files never override a managed value; the [exceptions](../02-Claude-Code-CLI/settings.md#exceptions-to-managed-settings-precedence) only let a stricter lower-level value count. These cases sit outside that rule:

- **The model for a session**: a managed `model` is a default, not a lock. `--model` and `ANTHROPIC_MODEL` still pick the model for that session, so deploy [`availableModels`](../02-Claude-Code-CLI/settings-reference.md#availablemodels) to restrict the choice.
- **The auto-compact window for a session**: a managed [`autoCompactWindow`](../02-Claude-Code-CLI/settings-reference.md#autocompactwindow) is a default too. The `--autocompact` flag and the `CLAUDE_CODE_AUTO_COMPACT_WINDOW` variable still set the [auto-compact window](../02-Claude-Code-CLI/model-config.md#set-the-auto-compact-window) for that session.
- **Local admin rights**: a developer who is an administrator on the machine can edit the managed source itself, which is why MDM tooling can redeploy the profile or file on a schedule and why the HKLM registry and the macOS managed preferences domain exist.
- **The server-managed cache**: server-managed settings come from Anthropic’s servers, and an edit to the local cache [lasts only until the next successful fetch](server-managed-settings.md#security-considerations).
- **Other tools**: managed settings bind Claude Code only. A developer who calls the API from another tool isn’t under them.


[​](#check-that-a-policy-is-in-force)

Check that a policy is in force

A developer reports that a policy isn’t applying, or you want to confirm a rollout landed before pushing it to the fleet. Two commands on that machine answer it: `/status` shows which managed source Claude Code selected, and `claude doctor` lists what it dropped.


[​](#read-the-source-in-/status)

Read the source in /status

On the developer’s machine, run `/status` inside Claude Code and read the `Setting sources` line. When a managed source is in effect, the line lists `Enterprise managed settings` with the source Claude Code selected in parentheses:

- `(remote)`: server-managed settings from claude.ai or a gateway
- `(plist)` or `(HKLM)`: an MDM or OS policy
- `(file)`, `(drop-ins)`, or `(file + drop-ins)`: `managed-settings.json`, the drop-in directory, or both
- `(remote + file, merged)`, or another list ending in `, merged`: your organization [composes every managed source](#compose-every-managed-source), and Claude Code merged the listed sources into the policy. A lower source can still supply `env` variables without appearing in the list. Requires Claude Code v2.1.242 or later
- `(HKCU)`: the user-writable registry fallback
- `(parent process)`: an [embedding host](#let-an-embedding-host-add-policy) supplied restrictive settings
- `(helper)`: a [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) configured by the selected MDM or file source

When Claude Code found a managed source on the machine and didn’t select it, a second line, `Skipped sources`, names each such source. Read it to distinguish a policy that never reached the machine from one that reached it and that a higher-priority source overrode. Requires Claude Code v2.1.242 or later. When the policy isn’t applying, the `Setting sources` line tells you which of two problems you have:

- **The line is missing**: Claude Code found no managed source that delivers a policy key. If you deployed a managed settings file, check that it sits at the path for the OS and that it contains a [policy key](#how-claude-code-combines-managed-sources) rather than only the control keys. A file that isn’t valid JSON doesn’t produce this state; Claude Code [refuses to start](#find-entries-claude-code-dropped) instead. When you deployed through server-managed settings instead, run `claude doctor`, which reports the [fetch outcome](server-managed-settings.md#verify-settings-delivery).
- **The line names a source other than the one you deployed**: a higher-priority source is present and Claude Code ignored yours, and `Skipped sources` lists it. [How Claude Code combines managed sources](#how-claude-code-combines-managed-sources) gives the order.


[​](#find-entries-claude-code-dropped)

Find entries Claude Code dropped

If your managed settings file, MDM profile, registry value, or server-managed payload fails schema validation, Claude Code first skips each individual entry it can repair, such as one invalid permission rule, and warns about each one. Claude Code then drops any value that still fails, unless the value belongs to one of the keys that [fail closed](#keys-that-fail-closed) instead. Claude Code is stricter with the `managedSettings` a [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) emits: it makes the same entry repairs, but any schema violation that survives fails the whole helper run, and at startup Claude Code refuses to start, the same as for a helper that exits non-zero. When a managed settings file, drop-in file, MDM plist, or HKLM registry value is present but can’t be parsed as a JSON object, Claude Code refuses to start and prints [an error naming the source](../02-Claude-Code-CLI/errors.md#managed-settings-document-could-not-be-parsed), even when another admin source delivers a valid policy. Each source fails this way when:

- **Managed settings file or drop-in file**: the file isn’t valid JSON, or its top level isn’t an object
- **MDM plist**: macOS’s `plutil` reports the plist malformed, or its converted content isn’t a JSON object
- **HKLM registry value**: the `Settings` value isn’t a string, is empty, or doesn’t hold a JSON object

Three source states don’t cause this refusal:

- An absent file, profile, or registry value isn’t a failure; Claude Code runs without that source.
- An empty managed settings file counts as `{}`.
- A malformed value in the user-writable HKCU registry key never blocks launch. Claude Code reports it as a notice in `/status` and `claude doctor` instead.

When a managed settings file, drop-in file, `managed-settings.d/` directory, MDM profile, or HKLM registry value exists but can’t be read, and no admin source supplies a policy, what happens depends on why the read failed:

- If the operating system denied the read, for example on a root-only file, every session starts without that source’s policies. `/status` and `claude doctor` record the failure, and a run with `-p` also prints it to stderr.
- For any other read failure, such as an I/O error, every session exits at startup with [a message to contact an administrator](../02-Claude-Code-CLI/errors.md#unable-to-read-managed-policy-settings).

To find a dropped entry, look in one of three places:

- Interactive sessions show a dialog at startup listing the invalid entries.
- Non-interactive runs with `-p` print a summary to stderr.
- [`claude doctor`](../02-Claude-Code-CLI/debug-your-config.md) lists each invalid entry with its source and field.


[​](#keys-that-fail-closed)

Keys that fail closed

When a managed source sets a top-level key that has a single restrictive value, such as `allowManagedPermissionRulesOnly`, `disableAutoMode`, or `skipDangerousModePermissionPrompt`, to something Claude Code can’t read, the key reads as that value until you fix it. The report says the key `was present but invalid` and names the value Claude Code treats it as. For a key inside `sandbox`, see [Invalid values inside `sandbox`](#invalid-values-inside-sandbox). These cases don’t fail closed:

- A `null` removes the key.
- An invalid `disableAllHooks`, even a quoted Boolean, is dropped with a warning, because enforcing `true` would also unload the hooks your own managed settings deploy.
- For every other Boolean key the rule covers, the string `"true"` or `"false"` reads as that Boolean, with a notice in `/status` asking you to drop the quotes.

Claude Code repairs the `permissions`, `autoMode`, `worktree`, and `attribution` blocks per field instead of dropping them whole:

- A lock inside one, such as `permissions.disableBypassPermissionsMode`, reads as its restrictive value.
- An invalid `permissions.defaultMode` reads as `default`.
- While a `deny` or `ask` list in `permissions` can’t be read at all, Claude Code withholds `allow` and `additionalDirectories`, so the grants never apply without the restrictions written beside them. The report names each withheld grant and the list that couldn’t be read.
- In `autoMode`, a `soft_deny` or `hard_deny` list that can’t be read, or that lost an invalid entry, withholds `allow` and `environment` the same way.

The fail-closed rule for keys with a single restrictive value and the per-field repairs require Claude Code v2.1.282 or later. These keys have their own fallback:

| Field                                                                      | Behavior when present but invalid                                                                                                                                                                                                                                                                                                                                                                                                                               |
|:---------------------------------------------------------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `allowedMcpServers`                                                        | Enforced as an empty allowlist until the value is fixed, so no MCP servers that users add are admitted. Servers your organization delivers through [`managedMcpServers`](../02-Claude-Code-CLI/settings-reference.md#managedmcpservers) still load, and `managed-mcp.json` servers load per [Servers that skip the allowlist check](../06-MCP-Tools/General/managed-mcp.md#servers-that-skip-the-allowlist-check). An individual invalid entry is stripped and the valid subset is enforced.      |
| [`allowedProviders`](../02-Claude-Code-CLI/settings-reference.md#allowedproviders)         | Enforced as an empty allowlist until the value is fixed, so every API provider is refused and Claude Code doesn’t start on the machine. If only an individual entry isn’t a known provider name, Claude Code drops and reports that entry and enforces the rest.                                                                                                                                                                                                |
| `allowedHttpHookUrls`                                                      | Claude Code enforces an empty managed [allowlist](../02-Claude-Code-CLI/settings-reference.md#allowedhttphookurls) until you fix the value, so an HTTP hook runs only if another settings file lists its URL. If only an individual entry is invalid, Claude Code strips that entry and enforces the rest.                                                                                                                                                                      |
| `httpHookAllowedEnvVars`                                                   | Claude Code enforces an empty managed [allowlist](../02-Claude-Code-CLI/settings-reference.md#httphookallowedenvvars) until you fix the value, so a header variable is interpolated only if another settings file names it. If only an individual entry is invalid, Claude Code strips that entry and enforces the rest.                                                                                                                                                        |
| `allowedChannelPlugins`                                                    | Claude Code enforces an empty allowlist until you fix the value, so no channel plugin passed to `--channels` is admitted. If only an individual entry is invalid, it strips that entry and enforces the rest.                                                                                                                                                                                                                                                   |
| `strictKnownMarketplaces`                                                  | Enforced as an empty allowlist until the value is fixed, so no [marketplace source](../08-Plugins-Skills/plugins-org.md#restrict-what-users-can-install) is admitted. An individual entry that is invalid or can’t be enforced, such as a `hostPattern` regex that doesn’t compile, is stripped and the valid subset is enforced.                                                                                                                                              |
| `availableModels`                                                          | Enforced as an empty allowlist until fixed, so only the Default model is available; a non-string entry is stripped and the valid subset enforced.                                                                                                                                                                                                                                                                                                               |
| [`availableModelsMatch`](../02-Claude-Code-CLI/settings-reference.md#availablemodelsmatch) | Treated as `exact` until the value is fixed.                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `forceLoginOrgUUID`                                                        | No organization is permitted to log in until the value is fixed.                                                                                                                                                                                                                                                                                                                                                                                                |
| `gatewayInternalNetworks`                                                  | When the invalid value comes from the highest managed source on the machine, `/login` refuses every new [cloud gateway](claude-apps-gateway.md#allow-a-gateway-on-public-address-space-you-own) sign-in on that machine until the value is fixed.                                                                                                                                                                                                         |
| `crossSessionInbound`                                                      | Treated as `refuse`, the most restrictive value, so inbound [cross-session messages](../02-Claude-Code-CLI/cross-session-messaging.md#control-inbound-messages) are refused until the value is fixed. The developer sees [a warning](../02-Claude-Code-CLI/errors.md#crosssessioninbound-must-be-one-of-accept-hold-refuse).                                                                                                                                                                    |
| `deniedMcpServers`                                                         | An individual invalid entry is stripped and the valid subset is enforced. A wholly invalid value is dropped with a warning, since denying every server would block servers the policy never named.                                                                                                                                                                                                                                                              |
| [`deniedModels`](../02-Claude-Code-CLI/settings-reference.md#deniedmodels)                 | A non-string entry is stripped and the rest of the list is enforced. A wholly invalid value is dropped with a warning and blocks no models until it is fixed.                                                                                                                                                                                                                                                                                                   |
| `blockedMarketplaces`                                                      | An individual invalid entry is stripped and the valid subset is enforced. An entry that parses but can never match, such as a `hostPattern` regex that doesn’t compile, is kept with a warning. It blocks nothing until fixed, but [marketplace restrictions](../08-Plugins-Skills/plugins-org.md#restrict-what-users-can-install) stay active. A wholly invalid value is dropped with a warning, since blocking every marketplace would block sources the policy never named. |
| `sandbox`                                                                  | When one value inside the block is invalid, Claude Code doesn’t drop the whole block. For what happens to each kind of invalid field, see [Invalid values inside `sandbox`](#invalid-values-inside-sandbox).                                                                                                                                                                                                                                                    |
| `sandbox.credentials`                                                      | A recoverable invalid entry is degraded to `mode: "deny"` with a warning; an unrecoverable one is stripped; valid entries stay enforced. See [invalid credential entries](../02-Claude-Code-CLI/settings-reference.md#invalid-credential-entries-in-managed-settings).                                                                                                                                                                                                          |
| `strictPluginOnlyCustomization`                                            | Treated as `true`, locking all four surfaces, when the value is neither a boolean nor an array. An array entry that this version doesn’t recognize as a surface locks nothing; a status note counts such entries so you can check them for typos.                                                                                                                                                                                                               |
| `enabledPlugins`                                                           | An invalid entry is dropped with a warning and the other entries stay enforced. A value that isn’t a map of plugin IDs, or whose every entry is invalid, is dropped whole with a warning.                                                                                                                                                                                                                                                                       |

`allowedHttpHookUrls` and `httpHookAllowedEnvVars` merge across settings files, so entries in your user, project, or local settings still apply while the managed list is empty. The fallbacks for those two keys and for `allowedChannelPlugins` require Claude Code v2.1.267 or later; earlier versions drop the whole key when its value or any entry is invalid. The `strictKnownMarketplaces` and `blockedMarketplaces` fallbacks require Claude Code v2.1.277 or later; earlier versions drop the whole key when its value or any entry is invalid. The `strictPluginOnlyCustomization` and `enabledPlugins` fallbacks require Claude Code v2.1.282 or later. `requiredMinimumVersion` and `requiredMaximumVersion` fail open by design: an invalid value is dropped rather than enforced. This tolerance applies only to managed settings. User, project, and local settings files remain strict: a file whose JSON or top-level shape fails validation is rejected as a whole and reported, and an individual entry that fails, such as a malformed permission rule, is skipped with a warning while the rest of the file applies.


[​](#invalid-values-inside-sandbox)

Invalid values inside `sandbox`

When one value in your managed `sandbox` block is invalid, Claude Code doesn’t drop the whole block, because it validates each field on its own. This per-field handling requires Claude Code v2.1.283 or later. On versions before v2.1.283, Claude Code drops every `sandbox` field except [`credentials`](../02-Claude-Code-CLI/settings-reference.md#invalid-credential-entries-in-managed-settings) when a value outside `credentials` is invalid. The warning you get for an invalid field names the field and tells you what happens to it. What happens depends on what the field controls:

- If you set a Boolean key to a quoted `"true"` or `"false"`, the value counts as that Boolean. Instead of a warning, `/status` shows a notice asking you to remove the quotes.
- If `failIfUnavailable` is invalid, Claude Code drops the value rather than treating it as `true`, so an unreadable value never stops sessions from starting across your fleet.
- Claude Code treats every other invalid Boolean as the value that keeps the sandbox strictest until you fix it. A key that turns the sandbox or one of its restrictions on, such as `enabled` or `network.allowManagedDomainsOnly`, counts as `true`. A key that loosens it, such as `allowUnsandboxedCommands`, counts as `false`.
- In a list outside `credentials`, such as `excludedCommands` or `network.allowedDomains`, Claude Code drops an invalid entry and keeps the rest of the list. A list that isn’t an array, or that has no valid entry, doesn’t apply at all.
- While `network.deniedDomains` or any entry in it is invalid, Claude Code also withholds `network.allowedDomains`, so the managed allowlist grants nothing until you fix the deny list.
- While `filesystem.denyRead`, `filesystem.denyWrite`, or any entry in either is invalid, Claude Code also withholds both `filesystem.allowRead` and `filesystem.allowWrite` until you fix the deny list.


[​](#keys-only-a-managed-source-can-set)

Keys only a managed source can set

Claude Code reads the following keys only from a managed source; placing them in user or project settings files has no effect. Most of them are locks: the value a lock governs, such as permission rules or `sandbox.network.allowedDomains`, is an ordinary key that any level can set, and the lock tells Claude Code to honor only the managed value. The table covers the permission, plugin, and delivery controls. For any key not listed here, the Scope column of the [settings reference](../02-Claude-Code-CLI/settings-reference.md#all-settings) index says whether it’s managed-only.

| Setting                                                                                                                    | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
|:---------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [`allowAllClaudeAiMcps`](../02-Claude-Code-CLI/settings-reference.md#allowallclaudeaimcps)                                                 | Load the claude.ai connectors Claude Code fetches itself alongside a deployed `managed-mcp.json` instead of suppressing them                                                                                                                                                                                                                                                                                                                                                                                  |
| [`allowedChannelPlugins`](../02-Claude-Code-CLI/settings-reference.md#allowedchannelplugins)                                               | Allowlist of channel plugins that may push messages. Replaces the default Anthropic allowlist when set. Requires `channelsEnabled: true`. See [Restrict which channel plugins can run](../02-Claude-Code-CLI/channels.md#restrict-which-channel-plugins-can-run)                                                                                                                                                                                                                                                              |
| [`allowManagedHooksOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedhooksonly)                                               | When `true`, restricts which hooks run; see [what runs under `allowManagedHooksOnly`](../02-Claude-Code-CLI/settings-reference.md#what-runs-under-allowmanagedhooksonly) for the full effect list                                                                                                                                                                                                                                                                                                                             |
| [`allowManagedMcpServersOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedmcpserversonly)                                     | When `true`, only `allowedMcpServers` from managed settings are respected. `deniedMcpServers` still merges from all sources. See [Keys read from every admin source](#keys-read-from-every-admin-source) for which managed sources can set it, and [Managed MCP configuration](../06-MCP-Tools/General/managed-mcp.md)                                                                                                                                                                                                          |
| [`allowManagedPermissionRulesOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedpermissionrulesonly)                           | Makes managed settings the only settings source of permission rules. The entry lists every source it ignores                                                                                                                                                                                                                                                                                                                                                                                                  |
| [`blockedMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#blockedmarketplaces)                                                   | Blocklist of marketplace sources. Blocked sources are checked before downloading, so they never touch the filesystem. See [managed marketplace restrictions](../08-Plugins-Skills/plugins-org.md#restrict-what-users-can-install)                                                                                                                                                                                                                                                                                            |
| [`channelsEnabled`](../02-Claude-Code-CLI/settings-reference.md#channelsenabled)                                                           | Allow [channels](../02-Claude-Code-CLI/channels.md) for the organization. See [enterprise controls](../02-Claude-Code-CLI/channels.md#enterprise-controls) for the default on each plan                                                                                                                                                                                                                                                                                                                                                       |
| [`disableCommandPluginSources`](../02-Claude-Code-CLI/settings-reference.md#disablecommandpluginsources)                                   | When `true`, blocks [`command` plugin sources](../08-Plugins-Skills/plugins-marketplace-reference.md#command-plugin-source) entirely, so the marketplace-declared command never runs. Also blocks marketplace [`headersHelper` commands](../08-Plugins-Skills/plugins-host-marketplace.md#authenticate-archive-downloads), except for a marketplace that managed settings themselves declare. When unset, follows `allowManagedHooksOnly`. Requires Claude Code v2.1.229 or later, and the `headersHelper` block requires v2.1.238 or later |
| [`disableSideloadFlags`](../02-Claude-Code-CLI/settings-reference.md#disablesideloadflags)                                                 | Reject the `--plugin-dir`, `--plugin-url`, `--agents`, and `--mcp-config` flags at startup. In cloud sessions, Claude Code instead starts the session and drops the server-delivered `--mcp-config` servers, apart from the exceptions its [reference entry](../02-Claude-Code-CLI/settings-reference.md#disablesideloadflags) lists. Requires Claude Code v2.1.193 or later                                                                                                                                                  |
| [`forceRemoteSettingsRefresh`](../02-Claude-Code-CLI/settings-reference.md#forceremotesettingsrefresh)                                     | When `true`, blocks CLI startup until remote managed settings are freshly fetched and exits if the fetch fails. See [fail-closed enforcement](server-managed-settings.md#enforce-fail-closed-startup)                                                                                                                                                                                                                                                                                                   |
| [`managedMcpServers`](../02-Claude-Code-CLI/settings-reference.md#managedmcpservers)                                                       | Remote MCP servers provided to every user alongside their own. It provides servers rather than locking anything down. See [Provide servers through managed settings](../06-MCP-Tools/General/managed-mcp.md#provide-servers-through-managed-settings). Requires Claude Code v2.1.259 or later                                                                                                                                                                                                                                   |
| [`managedSourcesBehavior`](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior)                                             | Whether Claude Code applies only the highest-priority managed source or [composes every one of them](#compose-every-managed-source)                                                                                                                                                                                                                                                                                                                                                                           |
| [`parentSettingsBehavior`](../02-Claude-Code-CLI/settings-reference.md#parentsettingsbehavior)                                             | Whether host-supplied parent settings merge under the managed policy                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| [`pluginSuggestionMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#pluginsuggestionmarketplaces)                                 | Marketplaces whose plugins Claude Code may suggest to users                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| [`pluginTrustMessage`](../02-Claude-Code-CLI/settings-reference.md#plugintrustmessage)                                                     | Custom message appended to the plugin trust warning shown before installation                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper)                                                                 | Executable that computes managed settings at startup; see [Compute managed settings with a policy helper](../02-Claude-Code-CLI/settings-reference.md#policyhelper)                                                                                                                                                                                                                                                                                                                                                           |
| [`sandbox.filesystem.allowManagedReadPathsOnly`](../02-Claude-Code-CLI/settings-reference.md#sandbox-filesystem-allowmanagedreadpathsonly) | When `true`, only `filesystem.allowRead` paths from managed settings are respected. `denyRead` still merges from all sources                                                                                                                                                                                                                                                                                                                                                                                  |
| [`sandbox.network.allowManagedDomainsOnly`](../02-Claude-Code-CLI/settings-reference.md#sandbox-network-allowmanageddomainsonly)           | Honor only managed `allowedDomains` and `WebFetch(domain:...)` allow rules; block other domains without prompting                                                                                                                                                                                                                                                                                                                                                                                             |
| [`strictKnownMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#strictknownmarketplaces)                                           | Controls which plugin marketplace sources users can add and install plugins from. See [managed marketplace restrictions](../08-Plugins-Skills/plugins-org.md#restrict-what-users-can-install)                                                                                                                                                                                                                                                                                                                                |
| [`strictPluginOnlyCustomization`](../02-Claude-Code-CLI/settings-reference.md#strictpluginonlycustomization)                               | Block skills, agents, hooks, and MCP servers from user and project sources; `true` locks all four, an array names which                                                                                                                                                                                                                                                                                                                                                                                       |
| [`wslInheritsWindowsSettings`](../02-Claude-Code-CLI/settings-reference.md#wslinheritswindowssettings)                                     | When set in the HKLM registry or a file under `C:\Program Files\ClaudeCode`, have WSL read the Windows policy chain, and read `/etc/claude-code` only when [no Windows admin document is present](#present-admin-documents); the entry gives the order                                                                                                                                                                                                                                                        |

On Team and Enterprise plans, an Owner enables or disables [Remote Control](../02-Claude-Code-CLI/remote-control.md) and [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) organization-wide in [Claude Code admin settings](https://claude.ai/admin-settings/claude-code). When an Owner turns Remote Control off, already-connected sessions that run Claude Code v2.1.286 or later disconnect too. Each one disconnects the next time it refreshes your organization’s policy, about once an hour. For what happens in those sessions, see [`Remote Control was turned off by your organization's policy`](../02-Claude-Code-CLI/remote-control.md#remote-control-was-turned-off-by-your-organizations-policy).Remote Control can additionally be disabled per device with the [`disableRemoteControl`](../02-Claude-Code-CLI/settings-reference.md#disableremotecontrol) setting. Cloud sessions have no per-device managed settings key.To check whether these organization settings reached a given machine, run `claude doctor` there and read the `Organization policy` line, which says where Claude Code loaded the policy from or why it didn’t load. Requires Claude Code v2.1.261 or later. In a running session, `/status` shows the same line when the policy didn’t load.


[​](#turn-telemetry-off-for-your-organization)

Turn telemetry off for your organization

Claude Code sends Anthropic operational [telemetry](data-usage.md#telemetry-services) by default on sessions that use the Anthropic API, whether directly, through an LLM gateway, or through a custom `ANTHROPIC_BASE_URL`; [Default behaviors by API provider](data-usage.md#default-behaviors-by-api-provider) says which providers send it. To turn it off for every developer without relying on each person’s shell, deliver `DISABLE_TELEMETRY` through the `env` block of your managed settings. This example sets `DISABLE_TELEMETRY` for everyone the policy reaches:

```python
{
  "env": {
    "DISABLE_TELEMETRY": "1"
  }
}
```

Claude Code applies a value of `1` without showing the user the [approval dialog](server-managed-settings.md#environment-variables-and-the-approval-dialog). If you turn telemetry off, Claude Code stops sending the usage data that feeds your organization’s [analytics dashboard](analytics.md) for the developers the policy reaches. The variable also turns off [feature-flag fetching](../02-Claude-Code-CLI/env-vars.md#features-that-need-feature-flag-fetching) for those developers. For Remote Control, see the [Remote Control requirements](../02-Claude-Code-CLI/remote-control.md#requirements). [Where and when a policy applies](#where-and-when-a-policy-applies) says which delivery mechanism reaches each surface, and [Platform availability](server-managed-settings.md#platform-availability) says which sessions skip the server-managed settings fetch. If your organization uses customer-managed encryption keys and routes Claude Code through a gateway, [Configure proxies and gateways](../02-Claude-Code-CLI/third-party-integrations.md#configure-proxies-and-gateways) says why those sessions need this variable.


[​](#see-also)

See also

- [Set up Claude Code for your organization](admin-setup.md): decide what to enforce and how
- [Server-managed settings](server-managed-settings.md): deliver policy from the claude.ai console or a gateway
- [Managed MCP configuration](../06-MCP-Tools/General/managed-mcp.md): control which MCP servers developers can use
- [All settings](../02-Claude-Code-CLI/settings-reference.md): every key, with whether a managed source can set it
- [Example settings files](../02-Claude-Code-CLI/settings-example.md#an-organizations-managed-settings): a complete `managed-settings.json` showing the shape of the managed keys

---

## Microsoft Entra ID SSO/SCIM email mismatch | Claude Help Center

Source: https://support.claude.com/en/articles/13917829-microsoft-entra-id-sso-scim-email-mismatch
Path: 13-Enterprise-Admin/microsoft-entra-id-sso-scim-email-mismatch.md

# Microsoft Entra ID SSO/SCIM email mismatch

March 24, 2026


Claude uses email as the primary identifier to match SSO logins to provisioned seats. In Microsoft Entra ID, SCIM provisioning and SSO authentication are configured in *separate places* and can pull email from different user attributes—causing a mismatch that blocks access. This guide walks through how to identify the problem, fix the attribute mapping, and clean up any side effects.

**Applies to:** Enterprise plans and Console organizations using SCIM provisioning. Team plans don't have SCIM provisioning, so this mismatch scenario doesn't apply — see **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195)** for what's available on each plan.

------------------------------------------------------------------------

## Symptoms

People may experience one or more of the following when attempting to access your organization via SSO:

- **"Account creation is blocked"** — The person authenticates via SSO but Claude cannot find a matching provisioned account. If organization creation is restricted (recommended), the person is blocked entirely.

- **Landing on a free personal account** — If organization creation is not restricted, the person bypasses your organization and creates or lands on a free personal account.

- **"Please confirm your email" mismatch** — The SSO callback shows a different email than the one the person entered at login.

- **Claude Code authentication failure** — The Claude Code CLI shows an email mismatch error during the authentication flow.

------------------------------------------------------------------------

## How this happens

Microsoft Entra ID user accounts have multiple email-like attributes that can hold *different values*. SCIM provisioning and SSO authentication are configured in separate admin areas and each can pull from a different attribute:

[TABLE]

The mismatch occurs when SCIM pulls email from one attribute while SSO sends the email from another. Even a subtle difference blocks access—Claude requires an **exact string match**.

**Common confusion:** Entra has two separate admin areas. The **SCIM provisioning app** lives under **Enterprise applications** (Microsoft's term for integrated apps—unrelated to your Claude plan). The **SSO/OIDC app** lives under **App registrations**. IT admins frequently navigate to the wrong location.

------------------------------------------------------------------------

## Diagnostic steps

### Step 1 — Confirm the mismatch

1.  **Check the SCIM email:** In Entra Admin Center → **Enterprise applications → \[Claude SCIM app\] → Provisioning → Provisioning logs**, find a recently provisioned person and inspect **Modified attributes**. The value mapped to emails\[type eq "work"\].value is what SCIM sent to Claude.

2.  **Check the SSO email:** In **Enterprise applications → \[Claude app\] → Single sign-on → Attributes & Claims**, find the email claim. For OIDC apps, check **App registrations → \[Claude app\] → Token configuration**.

3.  If the two source attributes point to **different fields**, you've confirmed the mismatch.

### Step 2 — Identify the scope

- If **most or all** provisioned people share the same format mismatch, this is a **systemic attribute mapping problem**. The fix is in the SCIM attribute mapping config.

- If only **one or two people** are affected, check their user profile directly in Entra.

### Step 3 — Check the OIDC token claims (OIDC apps only)

1.  In Entra Admin Center, go to **App registrations** (not Enterprise applications).

2.  Find the Claude OIDC app and click "Token configuration."

3.  Check the email optional claim.

4.  Cross-reference with your SCIM attribute mapping to confirm whether they match.

------------------------------------------------------------------------

## Resolution

### Fix the SCIM attribute mapping

Navigate to the **SCIM provisioning app** — not the SSO/OIDC app:

1.  Go to **Entra Admin Center → Enterprise applications**.

2.  Search for the Claude SCIM app. Look for the app with a **Provisioning** section.

3.  Click **Provisioning → Edit provisioning → Attribute mappings**.

4.  Find the row where the SCIM attribute is emails\[type eq "work"\].value. Click it to edit.

5.  Change the **Source attribute** to match what SSO sends — typically mail.

6.  Also check the userName mapping and update if needed.

7.  Click "Save."

### Trigger a full provisioning sync

**Full sync required — incremental won't work.** You must trigger a **full restart** of the provisioning cycle.

1.  Go to the **Provisioning** overview page.

2.  Click **Restart provisioning**.

3.  Wait for the sync to complete.

4.  Verify in provisioning logs that emails have updated to the correct format.

------------------------------------------------------------------------

## Post-fix cleanup

After correcting the attribute mapping and completing the full sync:

- **Rogue free accounts:** If organization creation wasn't restricted before the fix, some people may have created free personal Claude accounts. Contact **[our Support team](../15-Claude-AI-Features/how-to-get-support.md)** for removal.

- **Ghost accounts (wrong-email seats):** The originally provisioned accounts with the incorrect email may still exist in your organization, occupying seats that can never be used. Contact our Support team to deprovision these.

- **Seat availability:** If ghost accounts are occupying all contracted seats, new logins will fail with an out-of-seats error even after the mapping is fixed.

- **Re-adding affected people:** After ghost accounts are removed, people with the corrected email may need to be re-invited or re-provisioned.

- **Prevent future occurrences:** Enable "Restrict organization creation" in your organization's Identity and access settings.

------------------------------------------------------------------------

## Verification

1.  Check a sample of provisioned people—confirm their email in the provisioning log matches the email format that SSO sends.

2.  Ask an affected person to clear browser cookies for claude.ai, then log in via SSO. They should land directly in your organization's workspace.

3.  Confirm people aren't creating free accounts—with organization creation restricted, blocked people see a clear error instead of landing on a personal account.

4.  If Claude Code was affected, have the person re-run `claude auth login --enterprise` and confirm the email matches their provisioned seat.

------------------------------------------------------------------------

## Common issues

[TABLE]

------------------------------------------------------------------------

## When to contact Support

Contact **[our Support team](https://support.claude.com/en/articles/9015913)** with your organization's domain, the affected person's email, and attribute mapping screenshots when:

- SCIM and SSO attributes appear identical but people still cannot access their seats.

- You need confirmation of the email Claude recorded during SCIM provisioning for specific people.

- You need help cleaning up ghost accounts or rogue free orgs.

- People are hitting an out-of-seats error despite available contracted seats.

---

## Microsoft Entra ID SSO setup | Claude Help Center

Source: https://support.claude.com/en/articles/13917889-microsoft-entra-id-sso-setup
Path: 13-Enterprise-Admin/microsoft-entra-id-sso-setup.md

# Microsoft Entra ID SSO setup

August 4, 2026


This guide walks you through configuring single sign-on (SSO) for Claude using Microsoft Entra ID (formerly Azure Active Directory) as your identity provider. It applies to Team plans, Enterprise plans, and Console organizations.

## Prerequisites

- A Claude Team plan, Enterprise plan, or Console organization with a parent organization (see **[Important considerations before enabling SSO](https://support.claude.com/en/articles/10276682)** for Console parent org requirements)

- Owner or Primary Owner role (Team and Enterprise) or Admin role (Console)

- Microsoft Entra ID P1 or P2 license (required for SCIM provisioning)

- Global Administrator or Application Administrator role in Entra

- Your domain verified in Claude's Identity and access settings — see **[Set up single sign-on](https://support.claude.com/en/articles/13132885)** for the full setup path including domain verification

## Where to find your configuration values

The Entity ID, Reply URL (ACS URL), and SCIM credentials referenced below are provided in the WorkOS setup flow within your Identity and access settings — not by contacting Support.

- **Team and Enterprise plans:** go to **[claude.ai/admin-settings/identity](https://claude.ai/admin-settings/identity)**

- **Console organizations:** go to **[platform.claude.com/settings/identity](https://platform.claude.com/settings/identity)**

Start the SSO setup flow there and keep it open alongside the Entra Admin Center as you work through the steps below.

------------------------------------------------------------------------

## Step 1 — Add Claude as an enterprise application in Entra

**Note:** "Enterprise applications" below refers to the section in Microsoft Entra's admin center—it's Microsoft's term for any app you integrate, and is unrelated to your Claude plan type.

1.  In the **Entra Admin Center**, go to **Enterprise applications → New application**.

2.  Search for "Claude" in the gallery. If available, select it; otherwise choose **Create your own application** and name it "Claude".

3.  Select **Integrate any other application you don't find in the gallery** and click "Create."

## Step 2 — Configure SAML SSO

1.  In your new application, click **Single sign-on → SAML**.

2.  In **Basic SAML Configuration**, enter: **Identifier (Entity ID)** from the WorkOS setup flow, **Reply URL (ACS URL)** from the WorkOS setup flow, and **Sign-on URL:** **[https://claude.ai/login](https://claude.ai/login)**.

3.  In **Attributes & Claims**, ensure the email claim is set to send user.mail (or the same attribute you'll use for SCIM).

4.  Download the **Federation Metadata XML** and upload it in the WorkOS setup flow when prompted.

## Step 3 — Configure SCIM provisioning

**Note:** SCIM provisioning is available on Enterprise plans and eligible Console organizations only. If you're on a Team plan, skip this step — you can use JIT provisioning instead. See **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195)**.

1.  In the application, go to **Provisioning → Get Started**.

2.  Set **Provisioning Mode** to **Automatic**.

3.  Enter the **Tenant URL** and **Secret Token** from the WorkOS setup flow.

4.  Click **Test Connection** to verify.

5.  Under **Mappings**, ensure the email attribute mapping points to the same field as your SSO email claim (typically user.mail).

6.  Set **Provisioning Status** to **On** and save.

**Critical:** The attribute used for SCIM email and SSO email must be identical. Mismatches are the most common cause of login failures. For troubleshooting, see **[Microsoft Entra ID SSO/SCIM email mismatch](https://support.claude.com/en/articles/13917829)**.

## Step 4 — Assign people and groups

1.  In **Users and groups**, assign the people or groups who should have access to Claude.

2.  Only assigned people and groups will be provisioned and allowed to SSO.

## Step 5 — Verify

1.  If you enabled SCIM, trigger a provisioning cycle and confirm people appear in Claude's admin settings.

2.  Have a test user complete SSO login and verify they land in your organization's workspace.

------------------------------------------------------------------------

## Need help?

See **[Set up single sign-on](https://support.claude.com/en/articles/13132885)** for the full end-to-end flow including domain verification and choosing a provisioning approach. If you run into issues, contact **[our Support team](https://support.claude.com/en/articles/9015913)** with your Entra tenant ID and a screenshot of your SAML configuration.

---

## Migrate your organization from Team to Enterprise | Claude Help Center

Source: https://support.claude.com/en/articles/13779868-migrate-your-organization-from-team-to-enterprise
Path: 13-Enterprise-Admin/migrate-your-organization-from-team-to-enterprise.md

# Migrate your organization from Team to Enterprise


When upgrading from a Team plan to an Enterprise plan, we recommend you keep the same Team organization and follow the upgrade path to change it to Enterprise. This will allow you to preserve your data (memberships/roles, conversations, and projects) and some of the settings from your Team plan organization. If you create a brand new Enterprise organization, then you'll need to set up everything from scratch.

There are two ways to upgrade: upgrade using the self-serve flow, or follow the sales-assisted flow if you’re working with an Anthropic account executive on an invoiced or contracted agreement. The steps and timing differ, so use the section that matches your situation.

## What's retained (same organization upgrade)

- All chat history and conversations

- Projects and shared content

- User memberships and roles

## Before you migrate

If you previously set a spend limit on your Team plan, consider increasing it before your go-live date so users aren’t locked out. Once the org-level spend limit is reached, all users lose access immediately until the spend limit is raised or the new month begins.

## Migrate from Team to self-serve Enterprise

You can migrate from a Team plan to a self-serve Enterprise plan by following these steps:

1.  Navigate to **[claude.ai/upgrade](https://claude.ai/upgrade)** and click "Get Enterprise plan."

2.  When prompted, choose "Upgrade an existing organization" and select your Team plan organization.

3.  Add the number of seats needed for all your team members (Enterprise organizations have a 20 seat minimum).

4.  Set a per-user spend limit and a starting usage balance for the whole team.

5.  Your payment information will be saved from previous Team plan payments, but you can click the pencil icon to change it if needed.

    1.  When you migrate from a Team to a self-serve Enterprise plan, we only support credit card payments. After you migrate to a self-serve Enterprise plan, you can choose to use a credit card, debit card, or bank transfer. For more information, see **[Self-serve vs. sales-assisted Enterprise](../17-Billing-Plans/what-is-the-enterprise-plan.md#h_3058c781c5)**.

6.  Review your order summary and click "Confirm upgrade."

**Important:** Migrating an organization from Team to Enterprise via this pathway is not reversible. Please ensure that an Enterprise plan is the right fit for your organization before initiating this change.

## Migrate from Team to sales-assisted Enterprise

You can migrate from a Team plan to a sales-assisted Enterprise plan through the Anthropic **[sales team](https://claude.com/contact-sales?utm_source=support&utm_medium=article&utm_content=enterprise-plan_contact-sales_intro)**. If your organization is migrating to a sales-assisted Enterprise plan, review the following before your go-live date:

- **Plan your activation timing:** Work with your account executive to agree on a go-live time before your contract is countersigned. Once your agreement is countersigned, your organization is provisioned on your contracted start date. Activation may not complete until later that day, so plan your internal rollout for the next business morning to avoid disruption mid-workday.

- **Expect a brief disruption at cutover:** Your users won’t be able to access Claude while provisioning is in progress. After the migration is complete, all users, including admins and standard users, should log out and log back in to refresh their session and clear any cached legacy settings.

## After you migrate

After your self-serve or sales-assisted migration is complete, we recommend reviewing the following settings.

### Capabilities

Some features that are turned on by default on Team plans may be turned off by default on Enterprise plans, so it's worth checking that your configuration matches your preferences.

Your underlying data is preserved even when a capability is turned off. For example, if your Team plan had skills enabled and members had created custom skills, those skills are not deleted during the migration—they're retained in your organization. When an admin re-enables skills on the Enterprise plan, any previously created custom skills will be available again. The same applies to other default-off capabilities: turning the setting back on restores access to existing content rather than starting from scratch.

The following capabilities are default-off for Enterprise plans:

- Skills and by dependency, Skill creation and Skill sharing (both public and within the organization)

- Code execution and file creation

- Claude Design, Claude Slides, and Claude Docs (in Organization settings \> Artifacts)

- Standalone Claude Design at claude.ai/design

- Claude in Chrome

### Per-user spend limits

Go to **[Organization settings \> Usage](https://claude.ai/admin-settings/usage)** and review per-user spend limits for all members. Clear or adjust any that are no longer appropriate for your Enterprise plan configuration.

### Seat assignments

During migration, some users may appear as **No seat assigned** rather than being automatically mapped to seat tiers. Admins should verify all users have correct seat assignments after the cutover. Pay attention to your highest-usage members, and note that users without a seat won't have access until an admin corrects their seat assignment.

For detailed guidance, refer to **[Purchase and manage seats on Enterprise plans](https://support.claude.com/en/articles/13393991)**.

### Role-based access controls

After migrating your organization from a Team plan to an Enterprise plan, an Owner or Primary Owner can follow the instructions to configure custom roles, groups, and group spend limits: **[Set up role-based permissions on Enterprise plans](https://support.claude.com/en/articles/13930458-set-up-role-based-permissions-on-enterprise-plans)**.

Read more about groups, group spend limits, and custom roles:

- **[Manage groups and group spend limits on Enterprise plans](../17-Billing-Plans/manage-groups-and-group-spend-limits-on-enterprise-plans.md)**

- **[Manage custom roles on Enterprise plans](../17-Billing-Plans/manage-custom-roles-on-enterprise-plans.md)**

------------------------------------------------------------------------

## Additional information

### SSO and identity setup timeline

- **Domain verification (DNS):** Allow 24–48 hours for DNS changes to propagate globally, though many changes take effect within 10 minutes.

- **SCIM provisioning sync:** Microsoft Entra ID pushes changes approximately every 40 minutes. Okta syncs more frequently.

For detailed setup instructions, refer to **[Set up single sign-on (SSO)](https://support.claude.com/en/articles/13132885)** and **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195).**

**Note:** Once you turn on SSO, existing users will be forced to log out and log back in.

### Billing and usage configuration

For usage-based Enterprise plans, usage is billed based on actual consumption. For more detailed information, refer to **[How am I billed for my Enterprise plan?](../17-Billing-Plans/how-am-i-billed-for-my-enterprise-plan.md)**

If you had purchased usage credits for your Team plan, any unused balance will roll over and become available on your new usage-based Enterprise plan.

**Note:** Enterprise plans aren’t eligible for monthly API credits. If your team has claimed them, new credits stop when you migrate. Credits you already have stay usable until they expire.

### Provisioning process

On the start date, you'll be provisioned and able to use the new features by the end of the day. After initial setup, Owners and Primary Owners can self-serve additional seats by navigating to **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)** and clicking "Manage" under **Total seats**.

------------------------------------------------------------------------

## Helpful resources

- **[Set up single sign-on (SSO)](https://support.claude.com/en/articles/13132885-set-up-single-sign-on-sso)**

- **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195-set-up-jit-or-scim-provisioning)**

- **[Purchase and manage seats on Enterprise plans](https://support.claude.com/en/articles/13393991-purchase-and-manage-seats-on-enterprise-plans)**

- **[Manage members on Team and Enterprise plans](https://support.claude.com/en/articles/13133750-manage-members-on-team-and-enterprise-plans)**

- **[Enterprise billing information](../17-Billing-Plans/how-am-i-billed-for-my-enterprise-plan.md)**

- **[Use Claude Code with your Team or Enterprise plan](https://support.claude.com/en/articles/11845131-use-claude-code-with-your-team-or-enterprise-plan)**

---

## Minimum age requirement access restriction | Claude Help Center

Source: https://support.claude.com/en/articles/13117299-minimum-age-requirement-access-restriction
Path: 13-Enterprise-Admin/minimum-age-requirement-access-restriction.md

# Minimum age requirement access restriction

March 16, 2026


This article explains why you may be unable to create a Claude account or sign in to an existing account when using the Claude mobile app.

## Why am I seeing this message?

We require all users to be at least 18 years old to create and use a Claude account. If you're seeing this message, it means the app store on your device (App Store or Play Store) has identified you as under 18 based on your account information.

In certain US states, new laws require app stores to verify users' ages and share that information with app developers. Claude uses this information to enforce our minimum age requirement. This requirement is being rolled out in stages across different states.

## What if my age is incorrect?

Anthropic does not have access to your app store account information and cannot change it. If you believe your age has been reported incorrectly, you'll need to contact the appropriate support team for your device:

- **iOS:** Contact [Apple Support](https://support.apple.com/en-us/108380)

- **Android:** Contact [Google Play Support](https://support.google.com/googleplay)

Your age information is managed through your Google account or Apple Account. If your account is part of a family group (such as Google Family Link or Apple Family Sharing), your age may have been set by a parent or guardian when the account was created. You may need to work with them to review or update your account information.

---

## Monitoring - Claude Code Docs

Source: https://code.claude.com/docs/en/monitoring-usage
Path: 13-Enterprise-Admin/monitoring-usage.md

## On this page

- [Quick start](#quick-start)
- [Administrator configuration](#administrator-configuration)
  - [How managed settings lock the OTLP destination](#how-managed-settings-lock-the-otlp-destination)
- [Configuration details](#configuration-details)
  - [Common configuration variables](#common-configuration-variables)
  - [mTLS authentication](#mtls-authentication)
  - [Metrics cardinality control](#metrics-cardinality-control)
  - [Traces (beta)](#traces-beta)
  - [Span hierarchy](#span-hierarchy)
  - [Span attributes](#span-attributes)
  - [Dynamic headers](#dynamic-headers)
  - [Settings configuration](#settings-configuration)
  - [Script requirements](#script-requirements)
  - [Refresh behavior](#refresh-behavior)
  - [Multi-team organization support](#multi-team-organization-support)
  - [Attribute telemetry to Desktop SSH sessions](#attribute-telemetry-to-desktop-ssh-sessions)
  - [Example configurations](#example-configurations)
- [Telemetry from cloud sessions and Claude Tag](#telemetry-from-cloud-sessions-and-claude-tag)
  - [Attribute telemetry to cloud sessions](#attribute-telemetry-to-cloud-sessions)
- [Available metrics and events](#available-metrics-and-events)
  - [Standard attributes](#standard-attributes)
  - [Repository attributes](#repository-attributes)
  - [Metrics](#metrics)
  - [Metric details](#metric-details)
  - [Session counter](#session-counter)
  - [Lines of code counter](#lines-of-code-counter)
  - [Pull request counter](#pull-request-counter)
  - [Commit counter](#commit-counter)
  - [Cost counter](#cost-counter)
  - [Token counter](#token-counter)
  - [Code edit tool decision counter](#code-edit-tool-decision-counter)
  - [Active time counter](#active-time-counter)
  - [Events](#events)
  - [Event correlation attributes](#event-correlation-attributes)
  - [User prompt event](#user-prompt-event)
  - [Assistant response event](#assistant-response-event)
  - [Tool result event](#tool-result-event)
  - [API request event](#api-request-event)
  - [API error event](#api-error-event)
  - [API refusal event](#api-refusal-event)
  - [API request body event](#api-request-body-event)
  - [API response body event](#api-response-body-event)
  - [Tool decision event](#tool-decision-event)
  - [Safety event](#safety-event)
  - [Permission mode changed event](#permission-mode-changed-event)
  - [Auth event](#auth-event)
  - [MCP server connection event](#mcp-server-connection-event)
  - [Internal error event](#internal-error-event)
  - [Plugin installed event](#plugin-installed-event)
  - [Plugin loaded event](#plugin-loaded-event)
  - [Skill activated event](#skill-activated-event)
  - [At mention event](#at-mention-event)
  - [API retries exhausted event](#api-retries-exhausted-event)
  - [Hook registered event](#hook-registered-event)
  - [Hook execution start event](#hook-execution-start-event)
  - [Hook execution complete event](#hook-execution-complete-event)
  - [Hook plugin metrics event](#hook-plugin-metrics-event)
  - [Compaction event](#compaction-event)
  - [Subagent completed event](#subagent-completed-event)
  - [Feedback survey event](#feedback-survey-event)
  - [Retention sweep event](#retention-sweep-event)
  - [Managed settings resolved event](#managed-settings-resolved-event)
- [Interpret metrics and events data](#interpret-metrics-and-events-data)
  - [Usage monitoring](#usage-monitoring)
  - [Cost monitoring](#cost-monitoring)
  - [Alerting and segmentation](#alerting-and-segmentation)
  - [Detect retry exhaustion](#detect-retry-exhaustion)
  - [Event analysis](#event-analysis)
  - [Map input tokens to OpenTelemetry GenAI semantic conventions](#map-input-tokens-to-opentelemetry-genai-semantic-conventions)
- [Audit security events](#audit-security-events)
  - [Attribute actions to users](#attribute-actions-to-users)
  - [Audit MCP activity](#audit-mcp-activity)
  - [Map security questions to events](#map-security-questions-to-events)
  - [Map egress paths to managed controls and events](#map-egress-paths-to-managed-controls-and-events)
  - [Check the retention sweep](#check-the-retention-sweep)
  - [Send events to a SIEM](#send-events-to-a-siem)
- [Backend considerations](#backend-considerations)
  - [Backends for metrics](#backends-for-metrics)
  - [Backends for events and logs](#backends-for-events-and-logs)
  - [Backends for traces](#backends-for-traces)
- [Service information](#service-information)
- [ROI measurement resources](#roi-measurement-resources)
- [Security and privacy](#security-and-privacy)
- [Monitor Claude Code on Amazon Bedrock](#monitor-claude-code-on-amazon-bedrock)

Usage and costs

# Monitoring

Copy pageCopy page

Learn how to enable and configure OpenTelemetry for Claude Code.

Copy pageCopy page

Track Claude Code usage, costs, and tool activity across your organization by exporting telemetry data through OpenTelemetry (OTel). Claude Code exports metrics as time series data via the standard metrics protocol, events via the logs/events protocol, and optionally [distributed traces via OpenTelemetry](#traces-beta).

For the Claude API or Managed Agents, see [OpenTelemetry for the Claude API](../04-API-Reference/Other/manage-claude-opentelemetry.md).


[​](#quick-start)

Quick start

Configure OpenTelemetry using environment variables:

```python
# 1. Enable telemetry
export CLAUDE_CODE_ENABLE_TELEMETRY=1

# 2. Choose exporters (both are optional - configure only what you need)
export OTEL_METRICS_EXPORTER=otlp       # Options: otlp, prometheus, console, none
export OTEL_LOGS_EXPORTER=otlp          # Options: otlp, console, none

# 3. Configure OTLP endpoint (for OTLP exporter)
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317

# 4. Set authentication (if required)
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer your-token"

# 5. For debugging: reduce export intervals, and reset them for production use
export OTEL_METRIC_EXPORT_INTERVAL=10000  # 10 seconds (default: 60000ms)
export OTEL_LOGS_EXPORT_INTERVAL=5000     # 5 seconds (default: 5000ms)

# 6. Run Claude Code
claude
```

To verify a setup that exports metrics, check your backend for the `claude_code.session.count` metric, which Claude Code emits when a session starts. To verify a logs-only setup, submit a prompt and check for the `claude_code.user_prompt` event. If nothing arrives, start Claude Code with `claude --debug-file <path>` and check the log it writes to that path. Claude Code reports failures from the exporters you configure as `[3P telemetry]` errors, where 3P means third-party. Lines prefixed `[Anthropic telemetry]` describe [Anthropic’s separate operational telemetry](data-usage.md#telemetry-services) and don’t indicate a problem with your setup. For full configuration options, see the [OpenTelemetry specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#configuration-options).


[​](#administrator-configuration)

Administrator configuration

Administrators can configure OpenTelemetry settings for all users through the [managed settings file](managed-settings.md#delivery-mechanisms). See the [settings precedence](../02-Claude-Code-CLI/settings.md#settings-precedence) for more information about how settings are applied. Example managed settings configuration:

```python
{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.example.com:4317",
    "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer example-token"
  }
}
```

In the Claude Desktop app, Code tab sessions read these managed settings from the sources that [reach each kind of Desktop session](../16-Mobile-Desktop/desktop.md#managed-settings). The OpenTelemetry form for Cowork under **Monitoring** in the admin console’s [Data and privacy settings](https://claude.ai/admin-settings/data-privacy-controls) applies to Cowork sessions only, so neither the terminal CLI nor the Code tab exports to a collector you set there. Claude Code ignores the [OpenTelemetry exporter variables](../02-Claude-Code-CLI/settings-reference.md#variables-claude-code-ignores-in-env) in a repository’s `.claude/settings.json` and `.claude/settings.local.json`, so a repository can’t use them to turn telemetry on, choose where it goes, or capture content. Set them in managed settings, or have each developer set them in their shell or `~/.claude/settings.json`. A repository can still turn a signal off by setting its exporter selector, such as `OTEL_LOGS_EXPORTER`, to `none`, unless managed settings, a `--settings` file, or the environment you start Claude Code from sets that variable. Claude Code doesn’t pass `OTEL_*` environment variables to the subprocesses it spawns, including the Bash tool, hooks, MCP servers, and language servers. An OpenTelemetry-instrumented application that you run through the Bash tool doesn’t inherit Claude Code’s exporter endpoint or headers, so set those variables directly in the command if that application needs to export its own telemetry.


[​](#how-managed-settings-lock-the-otlp-destination)

How managed settings lock the OTLP destination

When you set an `OTEL_EXPORTER_OTLP_*` variable in managed settings, Claude Code removes conflicting developer-set variables at startup and logs a warning in the debug log. What it removes depends on which variable you set:

- **Endpoints**: when you set `OTEL_EXPORTER_OTLP_ENDPOINT`, Claude Code removes every developer-set per-signal endpoint. Developers can’t point one signal at a different collector, so you don’t need to also set the per-signal endpoint variables in managed settings.
- **Protocols**: when you set `OTEL_EXPORTER_OTLP_PROTOCOL`, Claude Code removes every developer-set per-signal protocol.
- **Credentials**: when you set `OTEL_EXPORTER_OTLP_HEADERS`, `OTEL_EXPORTER_OTLP_CLIENT_KEY`, or `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE`, Claude Code removes the developer-set per-signal versions of that variable, plus every developer-set endpoint variable, generic or per-signal, since those credentials would otherwise reach a collector the managed settings didn’t choose.
- **Exporter selectors**: `OTEL_METRICS_EXPORTER`, `OTEL_LOGS_EXPORTER`, and the beta `OTEL_TRACES_EXPORTER` follow normal per-key precedence. A developer’s setting can still disable a signal or switch it to the console exporter, so set the selectors in managed settings too if you need them locked. Across [admin sources](managed-settings.md#precedence-within-the-managed-tier), `OTEL_LOGS_EXPORTER` follows the [telemetry unit](server-managed-settings.md#per-key-exceptions-across-managed-sources) while the other two selectors merge per key. Requires Claude Code v2.1.223 or later.
- **Beta tracing endpoints**: with [detailed beta tracing](#traces-beta) active, Claude Code exports logs and traces to `BETA_TRACING_ENDPOINT` instead of through the logs and traces exporters. Claude Code therefore removes a developer-set `BETA_TRACING_ENDPOINT` whenever any of these managed settings decides either signal’s destination:
  - A generic or logs/traces endpoint or credential
  - An [`otelHeadersHelper`](../02-Claude-Code-CLI/settings-reference.md#otelheadershelper)
  - A logs or traces exporter selector set to `none`, `console`, or empty, values that keep the signal off a collector
  - `CLAUDE_CODE_ENABLE_TELEMETRY` turned off

  A metrics-only endpoint or credential doesn’t remove it. Before v2.1.251, a developer-set `BETA_TRACING_ENDPOINT` redirected the logs and traces that detailed beta tracing exports even when managed settings pinned the collector.

Claude Code doesn’t remove per-signal variables that you set in managed settings itself, so you can route one signal to a different collector by setting its variable there, as the [SIEM example](#send-events-to-a-siem) does. If you set a per-signal credential there, Claude Code removes the developer-set endpoint for that signal. This removal behavior changes where telemetry is delivered, not what Claude Code collects. Before v2.1.217, every variable followed per-key settings precedence independently, so a signal-specific endpoint set in user settings or the shell redirected that signal away from the managed collector. When the desktop app or a [self-hosted environment](self-hosted-environments.md) runner launches Claude Code and names an OTLP endpoint in the environment it provides, Claude Code pins the destination the same way: the launcher’s telemetry variables remove developer-set variables exactly as managed settings do. Claude Code doesn’t remove variables that the launcher itself set. Requires Claude Code v2.1.251 or later.


[​](#configuration-details)

Configuration details


[​](#common-configuration-variables)

Common configuration variables

These variables configure exporters, endpoints, and export behavior for all deployments. If you set a per-signal endpoint or protocol variable, such as `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`, Claude Code uses it instead of the generic variable for that signal. If you set a per-signal headers variable, such as `OTEL_EXPORTER_OTLP_METRICS_HEADERS`, Claude Code merges it with the generic `OTEL_EXPORTER_OTLP_HEADERS` for that signal. On machines with managed settings, see [How managed settings lock the OTLP destination](#how-managed-settings-lock-the-otlp-destination) for what Claude Code removes.

| Environment Variable                                | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | Example Values                                                                                                                                                 |
|-----------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CLAUDE_CODE_ENABLE_TELEMETRY`                      | Enables telemetry collection (required)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `1`                                                                                                                                                            |
| `OTEL_METRICS_EXPORTER`                             | Metrics exporter types, comma-separated. Use `none` to disable                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  | `console`, `otlp`, `prometheus`, `none`                                                                                                                        |
| `OTEL_LOGS_EXPORTER`                                | Logs/events exporter types, comma-separated. Use `none` to disable                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | `console`, `otlp`, `none`                                                                                                                                      |
| `OTEL_EXPORTER_OTLP_PROTOCOL`                       | Protocol for OTLP exporter, applies to all signals. Claude Code has no default protocol, so set this or the signal-specific protocol variable for each `otlp` exporter you enable                                                                                                                                                                                                                                                                                                                                                                                                                                                               | `grpc`, `http/json`, `http/protobuf`                                                                                                                           |
| `OTEL_EXPORTER_OTLP_ENDPOINT`                       | OTLP collector endpoint for all signals                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         | `http://localhost:4317`                                                                                                                                        |
| `OTEL_EXPORTER_OTLP_METRICS_PROTOCOL`               | Protocol for metrics, overrides general setting                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `grpc`, `http/json`, `http/protobuf`                                                                                                                           |
| `OTEL_EXPORTER_OTLP_METRICS_ENDPOINT`               | OTLP metrics endpoint, overrides general setting                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `http://localhost:4318/v1/metrics`                                                                                                                             |
| `OTEL_EXPORTER_OTLP_LOGS_PROTOCOL`                  | Protocol for logs, overrides general setting                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | `grpc`, `http/json`, `http/protobuf`                                                                                                                           |
| `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT`                  | OTLP logs endpoint, overrides general setting                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `http://localhost:4318/v1/logs`                                                                                                                                |
| `OTEL_EXPORTER_OTLP_HEADERS`                        | Authentication headers for OTLP                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | `Authorization=Bearer token`                                                                                                                                   |
| `OTEL_EXPORTER_OTLP_METRICS_HEADERS`                | Authentication headers for metrics, merged with the general headers                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | `Authorization=Bearer token`                                                                                                                                   |
| `OTEL_EXPORTER_OTLP_LOGS_HEADERS`                   | Authentication headers for logs, merged with the general headers                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `Authorization=Bearer token`                                                                                                                                   |
| `OTEL_METRIC_EXPORT_INTERVAL`                       | Export interval in milliseconds (default: 60000)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                | `5000`, `60000`                                                                                                                                                |
| `OTEL_LOGS_EXPORT_INTERVAL`                         | Logs export interval in milliseconds (default: 5000)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | `1000`, `10000`                                                                                                                                                |
| `OTEL_LOG_USER_PROMPTS`                             | Enable logging of user prompt content (default: disabled)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `1` to enable                                                                                                                                                  |
| `OTEL_LOG_ASSISTANT_RESPONSES`                      | Enable logging of assistant response text on `assistant_response` events (default: disabled). When unset, falls back to the value of `OTEL_LOG_USER_PROMPTS`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | `1` to enable, `0` to keep redacted                                                                                                                            |
| `OTEL_LOG_TOOL_DETAILS`                             | Enable logging of tool parameters and input arguments in tool events and trace span attributes: Bash commands, MCP server and tool names, skill names, user-authored workflow names, and tool input. Also enables custom, plugin, and MCP command names on `user_prompt` events, and real agent, skill, plugin, and MCP server and tool names on the [cost and token counters](#cost-counter) (default: disabled). For Claude Desktop’s built-in servers, in sessions Claude Desktop owns, `mcp_server_name`/`mcp_tool_name` emit on `tool_decision`/`tool_result` even with the flag off. The exception requires Claude Code v2.1.214 or later | `1` to enable                                                                                                                                                  |
| `OTEL_LOG_TOOL_CONTENT`                             | Enable logging of tool content in the [`tool.output` span event](#tool-output-span-event) (default: disabled). Span attributes carry tool content under [their own gates](#new-context-gates). Requires [tracing](#traces-beta). Content is truncated at the content limit (60 KB by default)                                                                                                                                                                                                                                                                                                                                                   | `1` to enable                                                                                                                                                  |
| `OTEL_LOG_MANAGED_SETTINGS`                         | Add the redacted managed settings, and a SHA-256 digest of the settings before redaction, to [managed settings resolved](#managed-settings-resolved-event) events (default: disabled). A value in project or local settings doesn’t turn it on. Requires Claude Code v2.1.274 or later                                                                                                                                                                                                                                                                                                                                                          | `1` to enable                                                                                                                                                  |
| `OTEL_LOG_RAW_API_BODIES`                           | Emit the full Anthropic Messages API request and response JSON as `api_request_body` / `api_response_body` log events (default: disabled). Bodies include the entire conversation history. Enabling this implies consent to everything `OTEL_LOG_USER_PROMPTS`, `OTEL_LOG_TOOL_DETAILS`, and `OTEL_LOG_TOOL_CONTENT` would reveal                                                                                                                                                                                                                                                                                                               | `1` for inline bodies truncated at the content limit (60 KB by default), or `file:<dir>` for untruncated bodies on disk with a `body_ref` pointer in the event |
| `CLAUDE_CODE_OTEL_CONTENT_MAX_LENGTH`               | Content limit: the maximum length of content-bearing attributes such as model responses, tool content, system prompts, and raw API bodies, truncation marker included, in UTF-16 code units (default: 61440, i.e. 60 KB). The default is sized for backends that cap attribute values at 64 KB; raise it only if your backend accepts larger values, or lower it to cut telemetry volume. When an OpenTelemetry SDK attribute limit, `OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT` or one of its logrecord and span variants, is set lower, Claude Code truncates at that smaller value so the `[TRUNCATED ...]` marker stays within the SDK limit        | `262144`                                                                                                                                                       |
| `OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE` | Metrics temporality preference (default: `delta`). Set to `cumulative` if your backend expects cumulative temporality                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | `delta`, `cumulative`                                                                                                                                          |
| `CLAUDE_CODE_OTEL_HEADERS_HELPER_DEBOUNCE_MS`       | Interval for refreshing dynamic headers (default: 1740000ms / 29 minutes)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       | `900000`                                                                                                                                                       |

For the `http/protobuf` and `http/json` protocols, Claude Code sends each export request with a `Content-Length` header.


[​](#mtls-authentication)

mTLS authentication

How you configure client certificates for the OTLP exporter depends on the OTLP protocol in use for that signal, set via `OTEL_EXPORTER_OTLP_PROTOCOL` or the per-signal override. The same configuration applies to metrics, logs, and traces.

| Protocol                     | Client certificate variables                                                                                                                                                                      | Trust the collector’s CA with    |
|:-----------------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------|
| `http/protobuf`, `http/json` | `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, and optionally `CLAUDE_CODE_CLIENT_KEY_PASSPHRASE`. See [Network configuration](network-config.md#mtls-authentication)                 | `NODE_EXTRA_CA_CERTS`            |
| `grpc`                       | `OTEL_EXPORTER_OTLP_CLIENT_KEY` and `OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE`, or the per-signal variants such as `OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY` to use a different certificate per signal | `OTEL_EXPORTER_OTLP_CERTIFICATE` |

For `grpc`, the OpenTelemetry SDK reads the standard OTLP variables directly, so existing configurations that set the per-signal metrics variables continue to work. On machines with managed settings, Claude Code [may remove developer-set per-signal credentials and endpoints](#how-managed-settings-lock-the-otlp-destination) at startup.


[​](#metrics-cardinality-control)

Metrics cardinality control

The following environment variables control which attributes are included in metrics to manage cardinality:

| Environment Variable                       | Description                                                                                                                            | Default Value | Example to Disable |
|--------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|---------------|--------------------|
| `OTEL_METRICS_INCLUDE_SESSION_ID`          | Include session.id and, on cloud sessions, ccr.session.id attributes in metrics                                                        | `true`        | `false`            |
| `OTEL_METRICS_INCLUDE_VERSION`             | Include app.version attribute in metrics                                                                                               | `false`       | `true`             |
| `OTEL_METRICS_INCLUDE_ACCOUNT_UUID`        | Include user.account_uuid and user.account_id attributes in metrics                                                                    | `true`        | `false`            |
| `OTEL_METRICS_INCLUDE_ENTRYPOINT`          | Include app.entrypoint attribute in metrics                                                                                            | `false`       | `true`             |
| `OTEL_METRICS_INCLUDE_RESOURCE_ATTRIBUTES` | Include keys from `OTEL_RESOURCE_ATTRIBUTES` as attributes on metric datapoints                                                        | `true`        | `false`            |
| `OTEL_METRICS_INCLUDE_REPOSITORY`          | Include `vcs.*` [repository identity attributes](#repository-attributes) on metrics and events. Requires Claude Code v2.1.269 or later | `false`       | `true`             |

Lower cardinality generally means better performance and lower storage costs but less granular data for analysis.


[​](#traces-beta)

Traces (beta)

Distributed tracing exports spans that link each user prompt to the API requests and tool executions it triggers, so you can view a full request as a single trace in your tracing backend. Tracing is off by default. To enable it, set both `CLAUDE_CODE_ENABLE_TELEMETRY=1` and `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1`, then set `OTEL_TRACES_EXPORTER` to choose where spans are sent. Traces reuse the [common OTLP configuration](#common-configuration-variables) for endpoint, protocol, headers, and [mTLS](#mtls-authentication). On machines with managed settings, Claude Code [may remove developer-set per-signal credentials and endpoints](#how-managed-settings-lock-the-otlp-destination) at startup.

| Environment Variable                  | Description                                                                       | Example Values                       |
|---------------------------------------|-----------------------------------------------------------------------------------|--------------------------------------|
| `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA` | Enable span tracing (required). `ENABLE_ENHANCED_TELEMETRY_BETA` is also accepted | `1`                                  |
| `OTEL_TRACES_EXPORTER`                | Traces exporter types, comma-separated. Use `none` to disable                     | `console`, `otlp`, `none`            |
| `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL`  | Protocol for traces, overrides `OTEL_EXPORTER_OTLP_PROTOCOL`                      | `grpc`, `http/json`, `http/protobuf` |
| `OTEL_EXPORTER_OTLP_TRACES_ENDPOINT`  | OTLP traces endpoint, overrides `OTEL_EXPORTER_OTLP_ENDPOINT`                     | `http://localhost:4318/v1/traces`    |
| `OTEL_EXPORTER_OTLP_TRACES_HEADERS`   | Authentication headers for traces, merged with `OTEL_EXPORTER_OTLP_HEADERS`       | `Authorization=Bearer token`         |
| `OTEL_TRACES_EXPORT_INTERVAL`         | Span batch export interval in milliseconds (default: 5000)                        | `1000`, `10000`                      |

Spans redact user prompt text, tool input details, and tool content by default. Set `OTEL_LOG_USER_PROMPTS=1`, `OTEL_LOG_TOOL_DETAILS=1`, and `OTEL_LOG_TOOL_CONTENT=1` to include them. When tracing is active, Bash and PowerShell subprocesses automatically inherit a `TRACEPARENT` environment variable containing the W3C trace context of the active tool execution span. This lets any subprocess that reads `TRACEPARENT` parent its own spans under the same trace, enabling end-to-end distributed tracing through scripts and commands that Claude runs. When tracing is active and Claude Code is connected directly to the Anthropic API, each model request carries a W3C `traceparent` header set to the `claude_code.llm_request` span’s context, and the API’s `traceresponse` header is recorded as a span link. Together these connect Claude Code’s client-side spans to the server-side trace through any compliant intermediary. Outbound HTTP MCP requests carry `traceparent` the same way. The header is not sent to third-party providers. By default, the `traceparent` header on model and HTTP MCP requests is sent only when `ANTHROPIC_BASE_URL` is unset or points at the Anthropic API, since some proxies reject unrecognized headers. The subprocess `TRACEPARENT` variable is controlled by the same switch for consistency. If you run Claude Code through a custom `ANTHROPIC_BASE_URL` proxy and want trace context propagated, set `CLAUDE_CODE_PROPAGATE_TRACEPARENT=1`. In Agent SDK and non-interactive sessions started with `-p`, Claude Code also reads `TRACEPARENT` and `TRACESTATE` from its own environment when starting each interaction span. This lets an embedding process pass its active W3C trace context into the subprocess so Claude Code’s spans appear as children of the caller’s distributed trace. Interactive sessions ignore inbound `TRACEPARENT` to avoid accidentally inheriting ambient values from CI or container environments. The inbound trace context also applies to [events](#events). In Agent SDK and `-p` sessions with `TRACEPARENT` set, each OTLP event log record carries `trace_id` and `span_id` values that join it to your application’s trace, even when the traces exporter isn’t configured, so your logging backend can correlate events with the rest of the trace. A record emitted while an interaction is active carries the interaction span’s IDs, even when Claude Code emits it outside the span’s async context, such as in a permission prompt callback or for a record buffered during startup and exported later. A record emitted with no active interaction span carries the inbound `TRACEPARENT` IDs directly.


[​](#span-hierarchy)

Span hierarchy

Each user prompt starts a `claude_code.interaction` root span. API calls, tool calls, and hook executions are recorded as its children. Tool spans have two child spans of their own: one for the time spent waiting on a permission decision and one for the execution itself. When the Agent tool, or legacy Task tool, spawns a subagent, the subagent’s API and tool spans nest under the parent’s `claude_code.tool` span.

```python
claude_code.interaction
├── claude_code.llm_request
├── claude_code.hook                    (requires detailed beta tracing)
└── claude_code.tool
    ├── claude_code.tool.blocked_on_user
    ├── claude_code.tool.execution
    └── (Agent tool) subagent claude_code.llm_request / claude_code.tool spans
```

In Agent SDK and `claude -p` sessions, `claude_code.interaction` itself becomes a child of the caller’s span when `TRACEPARENT` is set in the environment. When a `PreToolUse` hook [defers a tool call](../07-Hooks/hooks.md#defer-a-tool-call-for-later), Claude Code saves the trace context of the turn that deferred it. When you resume the session and the tool re-runs, the tool’s spans join that earlier turn’s trace as children of the turn’s `claude_code.interaction` span.


[​](#span-attributes)

Span attributes

Every span carries the [standard attributes](#standard-attributes) plus a `span.type` attribute matching its name. The tables below list the additional attributes set on each span. The `llm_request`, `tool.execution`, and `hook` spans set OpenTelemetry status `ERROR` when they record a failure; the other spans always end with status `UNSET`. **`claude_code.interaction`**

| Attribute                 | Description                                                                                                                                                            | Gated by                |
|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|
| `user_prompt`             | Prompt text. Value is `<REDACTED>` unless the gate is set                                                                                                              | `OTEL_LOG_USER_PROMPTS` |
| `user_prompt_length`      | Prompt length in characters                                                                                                                                            |                         |
| `interaction.sequence`    | 1-based counter of interactions, counted per Claude Code process rather than per session, as described for [`event.sequence`](#event-correlation-attributes)           |                         |
| `parent.source`           | How the span got its trace parent: `env` when it parented under an inbound `TRACEPARENT`, `none` when it started its own trace. Requires Claude Code v2.1.268 or later |                         |
| `interaction.duration_ms` | Wall-clock duration of the turn                                                                                                                                        |                         |

**`claude_code.llm_request`**

| Attribute                        | Description                                                                                                                                                                                                                                                               | Gated by                       |
|----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------|
| `model`                          | Model identifier                                                                                                                                                                                                                                                          |                                |
| `gen_ai.system`                  | Always `anthropic`. OpenTelemetry GenAI semantic convention                                                                                                                                                                                                               |                                |
| `gen_ai.request.model`           | Same value as `model`. OpenTelemetry GenAI semantic convention                                                                                                                                                                                                            |                                |
| `query_source`                   | Subsystem that issued the request, such as `repl_main_thread` or a subagent name                                                                                                                                                                                          | `ENABLE_BETA_TRACING_DETAILED` |
| `query_source_safe`              | Bounded form of `query_source`, emitted whether or not detailed beta tracing is active, with values such as `repl_main_thread` or `agent.builtin.general-purpose`. `:` becomes `.` and user-named agents appear as `agent.custom`. Requires Claude Code v2.1.268 or later |                                |
| `agent_id`                       | Identifier of the subagent or teammate that issued the request. Absent on the main session                                                                                                                                                                                |                                |
| `parent_agent_id`                | Identifier of the agent that spawned this one. Absent for the main session and for agents spawned directly from it                                                                                                                                                        |                                |
| `workflow.run_id`                | Run identifier of the [Workflow](../02-Claude-Code-CLI/workflows.md) tool run that spawned this agent, prefixed `wf_`. Absent for agents not spawned by a workflow                                                                                                                        |                                |
| `workflow.name`                  | Name of the workflow that spawned this agent. User-authored names are replaced with `custom` unless the gate is set                                                                                                                                                       | `OTEL_LOG_TOOL_DETAILS`        |
| `speed`                          | `fast` or `normal`                                                                                                                                                                                                                                                        |                                |
| `effort`                         | [Effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) applied to the request: `low`, `medium`, `high`, `xhigh`, or `max`. Absent when Claude Code sends no effort level, for example on a model that doesn’t support effort. Requires Claude Code v2.1.274 or later   |                                |
| `llm_request.context`            | `interaction`, `tool`, or `standalone` depending on the parent span                                                                                                                                                                                                       |                                |
| `duration_ms`                    | Wall-clock duration including retries                                                                                                                                                                                                                                     |                                |
| `ttft_ms`                        | Time to first token in milliseconds                                                                                                                                                                                                                                       |                                |
| `first_content_ms`               | Time from request start to the first content block of the successful attempt, in milliseconds. Absent on requests that fell back to the non-streaming path. Requires Claude Code v2.1.268 or later                                                                        |                                |
| `input_tokens`                   | Input token count from the API usage block. Excludes tokens read from or written to the prompt cache, which are reported in `cache_read_tokens` and `cache_creation_tokens`                                                                                               |                                |
| `output_tokens`                  | Output token count                                                                                                                                                                                                                                                        |                                |
| `cache_read_tokens`              | Tokens read from prompt cache                                                                                                                                                                                                                                             |                                |
| `cache_creation_tokens`          | Tokens written to prompt cache                                                                                                                                                                                                                                            |                                |
| `request_id`                     | API request ID. Same value as the `request_id` [event correlation attribute](#event-correlation-attributes)                                                                                                                                                               |                                |
| `gen_ai.response.id`             | Same value as `request_id`. OpenTelemetry GenAI semantic convention                                                                                                                                                                                                       |                                |
| `client_request_id`              | Client-generated `x-client-request-id` of the final attempt                                                                                                                                                                                                               |                                |
| `attempt`                        | Total attempts made for this request                                                                                                                                                                                                                                      |                                |
| `success`                        | `true` or `false`                                                                                                                                                                                                                                                         |                                |
| `status_code`                    | HTTP status code when the request failed                                                                                                                                                                                                                                  |                                |
| `error`                          | Error message when the request failed                                                                                                                                                                                                                                     |                                |
| `error_class`                    | Short error class token when the request failed, such as `api_timeout` or `server_overload`. Requires Claude Code v2.1.268 or later                                                                                                                                       |                                |
| `response.has_tool_call`         | `true` when the response contained tool-use blocks                                                                                                                                                                                                                        |                                |
| `stop_reason`                    | API response `stop_reason`, such as `end_turn`, `tool_use`, `max_tokens`, `stop_sequence`, `pause_turn`, or `refusal`                                                                                                                                                     |                                |
| `gen_ai.response.finish_reasons` | Same value as `stop_reason`, wrapped in a string array. OpenTelemetry GenAI semantic convention                                                                                                                                                                           |                                |

Each retry attempt is also recorded as a `gen_ai.request.attempt` span event with `attempt` and `client_request_id` attributes. **`claude_code.tool`**

| Attribute             | Description                                                                                                                                                                                                                                                                              | Gated by                |
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|
| `tool_name`           | Tool name                                                                                                                                                                                                                                                                                |                         |
| `tool_name_safe`      | Form of `tool_name` that carries no user-chosen names. Built-in tool names pass verbatim. MCP tool names appear as `mcp_other`, except tool names matching a few fixed shapes, such as `playwright` tools named `browser_*`, which pass verbatim. Requires Claude Code v2.1.268 or later |                         |
| `bash_command_class`  | For the Bash tool: category of the command’s first program from a fixed list, such as `vcs` or `package_manager`. `other` for a program outside the list, `unparsed` when the line can’t be parsed. Requires Claude Code v2.1.268 or later                                               |                         |
| `bash_argv0`          | For the Bash tool: the command’s first program when it’s on the same fixed list, such as `git` or `npm`. `other` for any program outside the list. Requires Claude Code v2.1.268 or later                                                                                                |                         |
| `duration_ms`         | Wall-clock duration including permission wait and execution                                                                                                                                                                                                                              |                         |
| `result_tokens`       | Approximate token size of the tool result                                                                                                                                                                                                                                                |                         |
| `agent_id`            | Identifier of the subagent or teammate that ran the tool. Absent on the main session                                                                                                                                                                                                     |                         |
| `parent_agent_id`     | Identifier of the agent that spawned this one. Absent for the main session and for agents spawned directly from it                                                                                                                                                                       |                         |
| `workflow.run_id`     | Run identifier of the Workflow tool run that spawned this agent, prefixed `wf_`. Absent for agents not spawned by a workflow                                                                                                                                                             |                         |
| `workflow.name`       | Name of the workflow that spawned this agent. User-authored names are replaced with `custom` unless the gate is set                                                                                                                                                                      | `OTEL_LOG_TOOL_DETAILS` |
| `tool_use_id`         | The model’s `tool_use` block id for this call. Matches the `tool_use_id` on the [tool_result](#tool-result-event) and [tool_decision](#tool-decision-event) events and in hook payloads, so you can join the span to those records                                                       |                         |
| `gen_ai.tool.call.id` | Same value as `tool_use_id`. OpenTelemetry GenAI semantic convention                                                                                                                                                                                                                     |                         |
| `file_path`           | Target file path for Read, Edit, and Write tools                                                                                                                                                                                                                                         | `OTEL_LOG_TOOL_DETAILS` |
| `full_command`        | Command string for the Bash tool                                                                                                                                                                                                                                                         | `OTEL_LOG_TOOL_DETAILS` |
| `skill_name`          | Skill name for the Skill tool                                                                                                                                                                                                                                                            | `OTEL_LOG_TOOL_DETAILS` |
| `subagent_type`       | Subagent type for the Agent tool or legacy Task tool                                                                                                                                                                                                                                     | `OTEL_LOG_TOOL_DETAILS` |

**`tool.output` span event on `claude_code.tool`** If you set `OTEL_LOG_TOOL_CONTENT=1`, Read and Bash calls can record a `tool.output` span event on the `claude_code.tool` span. Edit and Write calls record one only when you also set `OTEL_LOG_TOOL_DETAILS=1`. That variable isn’t scoped to those two tools, so check its [row in the configuration table](#common-configuration-variables) for the arguments it adds elsewhere. MCP tools, WebFetch, and WebSearch record this event too, on Claude Code v2.1.283 or later. Claude Code writes this event from a tool call’s successful return, so a call that raises an error records nothing, whatever the tool. Among the calls that do return, it records no `tool.output` event for:

- A call to any tool other than Read, Edit, Write, Bash, WebFetch, WebSearch, and MCP tools
- A Read that returns anything other than file text, such as an image, a PDF, or a re-read of a file whose contents haven’t changed
- An Edit or Write call, unless you also set `OTEL_LOG_TOOL_DETAILS=1`
- A WebFetch or WebSearch call that Claude Code moved to the background while it ran so that a waiting message could reach Claude. The result that arrives later isn’t recorded either. To learn when Claude Code moves a call, see [When Claude Code sends what you queued](../02-Claude-Code-CLI/interactive-mode.md#when-claude-code-sends-what-you-queued) for the terminal and the [`priority` field](../05-Agent-SDK/agent-sdk-typescript.md#sdkusermessage) for Agent SDK sessions

The event carries these attributes, each truncated at the content limit (60 KB by default). `Gated by` names the variable an attribute needs on top of `OTEL_LOG_TOOL_CONTENT=1`, and for Edit and Write that variable gates the event itself rather than the attribute.

| Attribute      | Description                                                                                                                                                                                                                                                         | Gated by                                   |
|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|
| `content`      | Text the Read tool returned, or the text a Write call was asked to write                                                                                                                                                                                            | `OTEL_LOG_TOOL_DETAILS` for the Write tool |
| `output`       | For the Bash tool, the command’s combined output, with stderr interleaved into stdout. For an MCP tool, WebFetch, or WebSearch, the result the tool returned: text blocks joined by newlines, with an image or document replaced by a placeholder such as `[image]` |                                            |
| `diff`         | Structured patch the Edit tool applied                                                                                                                                                                                                                              | `OTEL_LOG_TOOL_DETAILS`                    |
| `file_path`    | Target file path for the Read, Edit, and Write tools, repeating the span attribute of the same name                                                                                                                                                                 | `OTEL_LOG_TOOL_DETAILS`                    |
| `bash_command` | Command string for the Bash tool                                                                                                                                                                                                                                    | `OTEL_LOG_TOOL_DETAILS`                    |

The parent span’s `tool_name` attribute tells you which tool an event came from. An attribute cut at the content limit is accompanied by `<attribute>_truncated` and `<attribute>_original_length`. **`claude_code.tool.blocked_on_user`**

| Attribute     | Description                                                                                                                                                                                                                                                                        | Gated by |
|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|
| `duration_ms` | Time spent waiting for the permission decision                                                                                                                                                                                                                                     |          |
| `decision`    | `accept`, `reject`, or `cancelled`, where `cancelled` means Claude Code stopped waiting for a permission decision on the tool call and didn’t run it                                                                                                                               |          |
| `source`      | For an `accept` or `reject`, where the decision came from, such as `config`, `hook`, or `user_temporary`, with the meanings given under the [Tool decision event](#tool-decision-event). For a `cancelled`, what ended the wait before any decision was made, such as `user_abort` |          |

A `cancelled` decision with `source` `user_abort` means a message needing immediate delivery interrupted the turn while the permission prompt or [auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) check was pending. For example, during an auto mode check, the user had a message queued and [sent it immediately](../02-Claude-Code-CLI/interactive-mode.md#when-claude-code-sends-what-you-queued). **`claude_code.tool.execution`**

| Attribute             | Description                                                                                                                                                                                                                                                      | Gated by                |
|-----------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|
| `duration_ms`         | Time spent running the tool body                                                                                                                                                                                                                                 |                         |
| `tool_use_id`         | Same value as on the parent `claude_code.tool` span                                                                                                                                                                                                              |                         |
| `gen_ai.tool.call.id` | Same value as `tool_use_id`. OpenTelemetry GenAI semantic convention                                                                                                                                                                                             |                         |
| `success`             | `true` or `false`                                                                                                                                                                                                                                                |                         |
| `error`               | Error category string when execution failed, such as `Error:ENOENT` or `ShellError`. Contains the full error message instead when the gate is set                                                                                                                | `OTEL_LOG_TOOL_DETAILS` |
| `error_class`         | The error category in identifier form, with characters outside letters, digits, and underscores replaced by `_`, such as `Error_ENOENT` or `ShellError`. Carries the category even when `error` carries the full message. Requires Claude Code v2.1.268 or later |                         |

**`claude_code.hook`** This span appears only when detailed beta tracing is active, which requires `ENABLE_BETA_TRACING_DETAILED=1` and `BETA_TRACING_ENDPOINT`, a pair that also [changes where your logs and traces go](../02-Claude-Code-CLI/env-vars.md#variables). Set the pair in your shell, user settings, or managed settings; both variables are ignored in [project and local settings](../02-Claude-Code-CLI/settings-reference.md#variables-claude-code-ignores-in-env). `CLAUDE_CODE_ENHANCED_TELEMETRY_BETA` alone doesn’t produce it. In interactive CLI sessions, detailed beta tracing also requires your organization to be allowlisted for the feature. Agent SDK and non-interactive `-p` sessions don’t require allowlisting.

| Attribute                | Description                                      | Gated by                |
|--------------------------|--------------------------------------------------|-------------------------|
| `hook_event`             | Hook event type, such as `PreToolUse`            |                         |
| `hook_name`              | Full hook name, such as `PreToolUse:Write`       |                         |
| `num_hooks`              | Number of matching hook commands executed        |                         |
| `hook_definitions`       | JSON-serialized hook configuration               | `OTEL_LOG_TOOL_DETAILS` |
| `duration_ms`            | Wall-clock duration of all matching hooks        |                         |
| `num_success`            | Count of hooks that completed successfully       |                         |
| `num_blocking`           | Count of hooks that returned a blocking decision |                         |
| `num_non_blocking_error` | Count of hooks that failed without blocking      |                         |
| `num_cancelled`          | Count of hooks cancelled before completion       |                         |

**Content attributes under detailed beta tracing**

Additional content-bearing attributes such as `new_context`, `system_reminders`, `system_prompt_preview`, `user_system_prompt`, `tool_input`, and `response.model_output` are emitted only when detailed beta tracing is active. They are not part of the stable span schema.

These attributes appear on the spans below, and `Gated by` names the variable an attribute needs on top of detailed beta tracing. Values longer than the content limit (60 KB by default) are truncated.

| Attribute               | Span                      | Description                                                                                                                                                                         | Gated by                |
|-------------------------|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------|
| `new_context`           | `claude_code.interaction` | The user prompt                                                                                                                                                                     | `OTEL_LOG_USER_PROMPTS` |
| `new_context`           | `claude_code.llm_request` | The new user messages and tool results sent with the request                                                                                                                        | `OTEL_LOG_USER_PROMPTS` |
| `system_reminders`      | `claude_code.llm_request` | The text of the [system reminders](../02-Claude-Code-CLI/glossary.md#system-reminder) among the request’s new messages                                                                              | `OTEL_LOG_USER_PROMPTS` |
| `system_prompt_preview` | `claude_code.llm_request` | First 500 characters of the complete system prompt sent with the request                                                                                                            | `OTEL_LOG_USER_PROMPTS` |
| `user_system_prompt`    | `claude_code.llm_request` | Only the system prompt text you provide via the `systemPrompt` SDK option or `--system-prompt` and `--append-system-prompt` flags. Emitted once per session rather than per request | `OTEL_LOG_USER_PROMPTS` |
| `response.model_output` | `claude_code.llm_request` | Text of the model’s response to the request                                                                                                                                         | `OTEL_LOG_USER_PROMPTS` |
| `new_context`           | `claude_code.tool`        | The tool call’s result, whatever the tool                                                                                                                                           | `OTEL_LOG_TOOL_CONTENT` |
| `tool_input`            | `claude_code.tool`        | The tool call’s serialized input                                                                                                                                                    | `OTEL_LOG_TOOL_DETAILS` |

Under detailed beta tracing with `OTEL_LOG_USER_PROMPTS=1`, Claude Code also emits a `claude_code.system_prompt` event that carries the complete system prompt, truncated at the content limit. It arrives the first time a session sends each distinct system prompt, and again after compaction.


[​](#dynamic-headers)

Dynamic headers

For enterprise environments that require dynamic authentication, you can configure a script to generate headers dynamically. Dynamic headers apply only to the `http/protobuf` and `http/json` protocols. With the `grpc` protocol, Claude Code uses only the static headers variables, `OTEL_EXPORTER_OTLP_HEADERS` and its per-signal variants.


[​](#settings-configuration)

Settings configuration

Add to your `.claude/settings.json`, replacing the path with your own script:

```python
{
  "otelHeadersHelper": "/path/to/generate-otel-headers.sh"
}
```

The value can be the path to an executable file, including a path that contains spaces, or a shell command line with arguments. On Windows, the value always runs through the shell, so quote a path that contains spaces inside the JSON value.


[​](#script-requirements)

Script requirements

The script must output valid JSON with string key-value pairs representing HTTP headers:

```python
#!/bin/bash
# Example: Multiple headers
echo "{\"Authorization\": \"Bearer $(get-token.sh)\", \"X-API-Key\": \"$(get-api-key.sh)\"}"
```

If the helper fails or prints output that doesn’t meet these requirements, exports fail and your telemetry backend receives nothing from the session until the helper works again. Claude Code reports the failure in:

- A warning notification in interactive sessions, [`otelHeadersHelper failed; telemetry is not being exported`](../02-Claude-Code-CLI/errors.md#otelheadershelper-failed), shown once per session when the helper first fails
- `/status` output
- The debug log, when running with [`--debug`](../02-Claude-Code-CLI/cli-reference.md#cli-flags) or after running `/debug` in the session
- stderr, in non-interactive sessions started with `-p`


[​](#refresh-behavior)

Refresh behavior

The headers helper script runs at startup and periodically thereafter to support token refresh. By default, the script runs every 29 minutes. Customize the interval with the `CLAUDE_CODE_OTEL_HEADERS_HELPER_DEBOUNCE_MS` environment variable.


[​](#multi-team-organization-support)

Multi-team organization support

Organizations with multiple teams or departments can add custom attributes to distinguish between different groups using the `OTEL_RESOURCE_ATTRIBUTES` environment variable:

```python
# Add custom attributes for team identification
export OTEL_RESOURCE_ATTRIBUTES="department=engineering,team.id=platform,cost_center=eng-123"
```

These custom attributes are included in all metrics and events, allowing you to:

- Filter metrics by team or department
- Track costs per cost center
- Create team-specific dashboards
- Set up alerts for specific teams

Claude Code attaches these values as attributes on every metric datapoint and event record, in addition to sending them in the OTLP resource block. Because most metrics backends expose datapoint attributes as queryable labels, you can group and filter metrics by your custom keys directly. Except for the `vcs.*` [repository attributes](#repository-attributes), custom keys never override the [standard attributes](#standard-attributes) such as `user.id` or `session.id`: when a key collides, Claude Code keeps the built-in value. Each custom key becomes a label on every metric series, so high-cardinality values increase storage cost in your metrics backend. To send custom attributes in the resource block only and omit them from datapoint labels, set `OTEL_METRICS_INCLUDE_RESOURCE_ATTRIBUTES=false`. See [Metrics cardinality control](#metrics-cardinality-control).

The `OTEL_RESOURCE_ATTRIBUTES` environment variable uses comma-separated key=value pairs with strict formatting requirements:

- **No spaces allowed**: values can’t contain spaces. For example, `user.organizationName=My Company` is invalid
- **Format**: must be comma-separated key=value pairs: `key1=value1,key2=value2`
- **Allowed characters**: only US-ASCII characters excluding control characters, whitespace, double quotes, commas, semicolons, and backslashes
- **Special characters**: characters outside the allowed range must be percent-encoded

For a value that would need a space, use underscores or camelCase instead. The following examples set `org.name` with each form:

```python
export OTEL_RESOURCE_ATTRIBUTES="org.name=Johns_Organization"
export OTEL_RESOURCE_ATTRIBUTES="org.name=JohnsOrganization"
```

You can percent-encode any character, not only the excluded ones. This example encodes both the space and the apostrophe:

```python
export OTEL_RESOURCE_ATTRIBUTES="org.name=John%27s%20Organization"
```

Wrapping values in quotes doesn’t escape spaces. For example, `org.name="My Company"` results in the literal value `"My Company"` with the quotes included, not `My Company`.


[​](#attribute-telemetry-to-desktop-ssh-sessions)

Attribute telemetry to Desktop SSH sessions

To see which remote machine a [Desktop SSH session](../16-Mobile-Desktop/desktop.md#ssh-sessions) ran on, name each machine in a custom attribute. Metrics and events don’t name the machine a session ran on. On each remote machine, add [`OTEL_RESOURCE_ATTRIBUTES`](#multi-team-organization-support) to the `env` block that turns telemetry on, in the [managed settings file the session reads](../16-Mobile-Desktop/desktop.md#managed-settings). Write the name out in each machine’s file. Claude Code doesn’t expand the value, so `host.name=$(hostname)` arrives as those characters. The following example names the machine `build-7`:

```python
{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "http://collector.example.com:4317",
    "OTEL_RESOURCE_ATTRIBUTES": "host.name=build-7"
  }
}
```

When nothing else sets the variable, `host.name` arrives in the resource block, in Desktop SSH sessions and in the CLI on that machine. For where else custom attributes appear, see [Multi-team organization support](#multi-team-organization-support). If the name doesn’t arrive, check for one of these causes:

- **You set it on the computer that runs Desktop**: Desktop doesn’t pass a value you set there to the SSH session
- **You exported it in a login file**: only login shells read a file such as `/etc/profile`. A value you `export` there reaches Claude Code that you start from a login shell. It doesn’t reach a Desktop SSH session, because Desktop doesn’t start Claude Code through a login shell.
- **A value has a space inside it**: Claude Code then copies none of the keys onto events or datapoints and reports no error. [Take the space out of the value](#multi-team-organization-support).
- **Something else already sets the variable**: in a session that the desktop app starts, a variable already set in the launch environment [takes precedence over settings files](../02-Claude-Code-CLI/settings-reference.md#how-env-values-interact-with-your-shell). The [debug log](../02-Claude-Code-CLI/debug-your-config.md) names each ignored variable. When a third-party Desktop deployment [names an OTLP endpoint](#how-managed-settings-lock-the-otlp-destination) in the environment it provides, that environment carries Desktop’s own `OTEL_RESOURCE_ATTRIBUTES`.


[​](#example-configurations)

Example configurations

Set these environment variables before running `claude`. Each scenario below shows a complete configuration, and each variable is described under [Common configuration variables](#common-configuration-variables). To confirm a configuration took effect, check your backend for the `claude_code.session.count` metric after starting a session; the [Quick start](#quick-start) covers logs-only verification and what to check when nothing arrives. For console debugging with a 1-second export interval:

```python
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=console
export OTEL_METRIC_EXPORT_INTERVAL=1000
```

For OTLP over gRPC:

```python
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
```

For Prometheus, scraped from `http://localhost:9464/metrics`:

```python
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=prometheus
```

On a [self-hosted environment](self-hosted-environments-reference.md#pass-through-session-child-metrics), the session binds port 9464 only at the runner’s default capacity of one. At higher capacity, the runner re-exposes session counters and gauges on its own `/metrics` endpoint instead. To send metrics to multiple exporters:

```python
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=console,otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=http/json
```

To send metrics and logs to different endpoints or backends:

```python
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_METRICS_PROTOCOL=http/protobuf
export OTEL_EXPORTER_OTLP_METRICS_ENDPOINT=http://metrics.example.com:4318
export OTEL_EXPORTER_OTLP_LOGS_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_LOGS_ENDPOINT=http://logs.example.com:4317
```

To export metrics only, without events or logs:

```python
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_METRICS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
```

To export events and logs only, without metrics:

```python
export CLAUDE_CODE_ENABLE_TELEMETRY=1
export OTEL_LOGS_EXPORTER=otlp
export OTEL_EXPORTER_OTLP_PROTOCOL=grpc
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
```


[​](#telemetry-from-cloud-sessions-and-claude-tag)

Telemetry from cloud sessions and Claude Tag

[Cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md), including [Claude Tag](https://claude.com/docs/claude-tag/overview) channel sessions, run in [cloud environments](../02-Claude-Code-CLI/cloud-environments.md) rather than on your users’ devices, so a managed settings file or shell profile on those devices doesn’t configure their telemetry. For sessions in Anthropic-hosted environments, this section covers where to set the telemetry variables, how to make your collector reachable from the environment, and how to tell cloud and Claude Tag sessions apart in the exported data. To export telemetry from those sessions, set `CLAUDE_CODE_ENABLE_TELEMETRY` and the `OTEL_*` variables, using the same keys as the [administrator configuration](#administrator-configuration) example, in one of two places:

- **Server-managed settings**: add them to the `env` block of your organization’s [server-managed settings](server-managed-settings.md). Claude Code fetches those settings at startup wherever [server-managed settings apply](../02-Claude-Code-CLI/model-config.md#surface-coverage), which includes your users’ machines and cloud sessions other than Claude Tag channel sessions. Claude Tag sessions don’t receive your server-managed settings, so this route doesn’t configure them.
- **The environment’s variables**: add them to a cloud environment’s [environment variables](../02-Claude-Code-CLI/cloud-environments.md#set-environment-variables) to configure only the sessions that run in that environment. This is the route that reaches Claude Tag sessions.

Anyone who uses an environment can read its variables, so don’t put a credential there, such as a collector token in `OTEL_EXPORTER_OTLP_HEADERS`. A [network secret](../02-Claude-Code-CLI/cloud-environments.md#add-network-secrets) on the environment doesn’t help either, because Claude Code’s own telemetry export is one of the [requests that never get the secret](../02-Claude-Code-CLI/cloud-environments.md#requests-that-never-get-the-credential). If your collector requires a credential, configure the whole export through server-managed settings instead, because when you set a credential there, [Claude Code removes endpoint variables set outside managed settings](#how-managed-settings-lock-the-otlp-destination). Keep these constraints in mind when you configure telemetry for cloud sessions:

- **Let sessions reach the collector**: Claude Code sends the export through the session’s network, so whether it reaches the host in your `OTEL_EXPORTER_OTLP_ENDPOINT` depends on the environment’s [network access level](../02-Claude-Code-CLI/cloud-environments.md#access-levels). If sessions can’t reach the collector’s domain at the level you chose, [add the domain to the environment’s allowlist](../02-Claude-Code-CLI/cloud-environments.md#allow-specific-domains), because no server-managed setting adds domains to an environment’s network allowlist.
- **Claude Tag channels use organization-level environments**: channel sessions run in organization-level environments rather than members’ personal ones, so make the allowlist and any environment-variable changes on the [shared environment](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) set as your organization’s default or pinned to the channel.
- **Cowork is configured separately**: Cowork sessions don’t receive server-managed settings, as the [surface coverage table](../02-Claude-Code-CLI/model-config.md#surface-coverage) shows, so the server-managed `env` block doesn’t configure their telemetry.


[​](#attribute-telemetry-to-cloud-sessions)

Attribute telemetry to cloud sessions

By default, metrics and events from a cloud session carry the [standard attributes](#standard-attributes), including `session.id`, `ccr.session.id`, and `organization.id`, so you can filter by session or organization without extra configuration. The `ccr.session.id` value is the session’s `CLAUDE_CODE_REMOTE_SESSION_ID`. To turn it into the session’s transcript URL, see [Link output back to the session](../02-Claude-Code-CLI/cloud-environments.md#link-output-back-to-the-session). To tell cloud sessions apart, group or filter on `ccr.session.id`, not `session.id`. To attribute telemetry in more detail, use these options:

- **Identify Claude Tag sessions**: set `OTEL_METRICS_INCLUDE_ENTRYPOINT=true`, as described under [Metrics cardinality control](#metrics-cardinality-control). Metrics then carry `app.entrypoint`, whose value is `claude-in-slack` for Claude Tag sessions.
- **Add custom attributes**: set [`OTEL_RESOURCE_ATTRIBUTES`](#multi-team-organization-support) in the same place you set the other `OTEL_*` variables for those sessions. If you `export` it in the environment’s [setup script](../02-Claude-Code-CLI/cloud-environments.md#setup-scripts) instead, the value doesn’t reach Claude Code: the setup script is a separate Bash script that runs before Claude Code launches, and variables it exports end with it.

In Claude Tag channel sessions, Claude works as your organization’s [shared identity](../02-Claude-Code-CLI/cloud-environments.md#set-the-environment-a-claude-tag-channel-uses) rather than as any member, so don’t rely on the `user.*` attributes to identify who tagged Claude.


[​](#available-metrics-and-events)

Available metrics and events


[​](#standard-attributes)

Standard attributes

All metrics and events share these standard attributes:

| Attribute                                                                               | Description                                                                                                                                                                                                                          | Controlled By                                                                              |
|-----------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------|
| `session.id`                                                                            | Claude Code’s own session ID, which `/clear` replaces                                                                                                                                                                                | `OTEL_METRICS_INCLUDE_SESSION_ID` (default: true)                                          |
| `ccr.session.id`                                                                        | Cloud session identifier, the value of `CLAUDE_CODE_REMOTE_SESSION_ID`, on sessions that run in a [cloud environment](../02-Claude-Code-CLI/cloud-environments.md)                                                                                   | `OTEL_METRICS_INCLUDE_SESSION_ID` (default: true)                                          |
| `app.version`                                                                           | Current Claude Code version                                                                                                                                                                                                          | `OTEL_METRICS_INCLUDE_VERSION` (default: false)                                            |
| `app.entrypoint`                                                                        | How the session was launched, such as `cli`, `sdk-cli`, `sdk-ts`, `sdk-py`, `claude-vscode`, or `claude-in-slack` for Claude Tag sessions                                                                                            | `OTEL_METRICS_INCLUDE_ENTRYPOINT` (default: false)                                         |
| `organization.id`                                                                       | Organization UUID (when authenticated)                                                                                                                                                                                               | Always included when available                                                             |
| `user.account_uuid`                                                                     | Account UUID (when authenticated)                                                                                                                                                                                                    | `OTEL_METRICS_INCLUDE_ACCOUNT_UUID` (default: true)                                        |
| `user.account_id`                                                                       | Account ID in tagged format matching Anthropic admin APIs (when authenticated), such as `user_01BWBeN28...`                                                                                                                          | `OTEL_METRICS_INCLUDE_ACCOUNT_UUID` (default: true)                                        |
| `user.id`                                                                               | Random anonymous identifier generated on first run and persisted in `~/.claude.json`. It contains no personal information and is not derived from your Claude account. Deleting the file produces a new unrelated value on next run. | Always included                                                                            |
| `user.email`                                                                            | User email address, from your sign-in or, in a [cloud session](../02-Claude-Code-CLI/claude-code-on-the-web.md), from the session’s own credentials                                                                                                  | Always included when available                                                             |
| `terminal.type`                                                                         | Terminal type, such as `iTerm.app`, `vscode`, `cursor`, or `tmux`                                                                                                                                                                    | Always included when detected                                                              |
| Keys from `OTEL_RESOURCE_ATTRIBUTES`                                                    | Custom attributes you set, such as `department` or `team.id`. See [Multi-team organization support](#multi-team-organization-support)                                                                                                | `OTEL_METRICS_INCLUDE_RESOURCE_ATTRIBUTES` (default: true)                                 |
| `vcs.repository.url.full`, `vcs.owner.name`, `vcs.repository.name`, `vcs.provider.name` | The session repository’s identity, derived from its `origin` remote. See [Repository attributes](#repository-attributes)                                                                                                             | `OTEL_METRICS_INCLUDE_REPOSITORY` (default: false). Requires Claude Code v2.1.269 or later |

In sessions signed in to a [Claude apps gateway](claude-apps-gateway.md) through `/login`, the CLI stamps exports with the authenticated identity: `user.id` is the IdP subject, `user.email` is the signed-in email, and `user.groups` carries IdP group membership as a comma-separated string. Each export also carries `identity.source: gateway-oidc`. The gateway identity is applied last, so `user.*` and `identity.*` keys set through `OTEL_RESOURCE_ATTRIBUTES` are ignored on those sessions.

Events that Claude Code logs before a developer signs in don’t carry the gateway identity. When Claude Code opens a session signed out of the gateway, for example after [the gateway ends the sign-in](../02-Claude-Code-CLI/errors.md#cloud-gateway-session-expired), the startup events logged before sign-in carry the anonymous `user.id` and no `identity.source`. These include [`managed_settings_resolved`](#managed-settings-resolved-event), [`plugin_loaded`](#plugin-loaded-event), and [`mcp_server_connection`](#mcp-server-connection-event).

For the identity attributes on Claude Desktop and Cowork sessions that connect through a gateway, see the [gateway `telemetry` reference](claude-apps-gateway-config.md#telemetry). Events additionally include the following attributes. These are never attached to metrics because they would cause unbounded cardinality:

- `prompt.id`: UUID correlating a user prompt with all subsequent events until the next prompt. See [Event correlation attributes](#event-correlation-attributes).
- `workspace.host_paths`: host workspace directories selected in the desktop app, as a string array
- `workflow.run_id`: run identifier, prefixed `wf_`, on the API and tool events emitted by agents that belong to a [Workflow](../02-Claude-Code-CLI/workflows.md) tool run. Filtering events by one `workflow.run_id` reconstructs that run’s API requests and tool results. The identifier covers the agents the workflow script spawns and any agents those spawn in turn, such as skill invocations. It matches the run identifier reported in the Workflow tool result. Absent on all other events.
- `workflow.name`: name of the workflow, its script’s `meta.name`, emitted alongside `workflow.run_id`. Built-in workflow names appear verbatim when the run executes the unmodified built-in script. User-authored names, including edited copies of built-in scripts, are replaced with `custom` unless `OTEL_LOG_TOOL_DETAILS=1` is set.


[​](#repository-attributes)

Repository attributes

Set `OTEL_METRICS_INCLUDE_REPOSITORY=true` to tag metrics and events with the identity of the session’s repository, so a shared collector can attribute usage per repository. Requires Claude Code v2.1.269 or later. Claude Code derives these attributes once per session from the repository’s `origin` remote. When the HTTPS and SSH remotes of a repository name the same host and the same path, as they do on GitHub, GitLab, and Bitbucket Cloud, both produce identical values:

| Attribute                 | Value                                                                                                                                               |
|---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------|
| `vcs.repository.url.full` | The repository’s browser URL without `.git`, such as `https://github.com/example-org/example-repo`                                                  |
| `vcs.owner.name`          | The owner or group path, such as `example-org`; omitted when the remote path has a single segment                                                   |
| `vcs.repository.name`     | The bare repository name, such as `example-repo`                                                                                                    |
| `vcs.provider.name`       | `github`, `gitlab`, `bitbucket`, or `gitea` when Claude Code recognizes the remote’s host or URL shape as one of those providers; omitted otherwise |

Values are lowercased, and credentials, query strings, and fragments from the remote URL never appear in them. The attributes are omitted when the session has no `origin` remote, when the remote isn’t URL-shaped, or when the only enclosing repository is your home directory. To get these attributes from a [cloud session](../02-Claude-Code-CLI/claude-code-on-the-web.md), set the telemetry variables, including `OTEL_METRICS_INCLUDE_REPOSITORY`, on its [cloud environment](../02-Claude-Code-CLI/cloud-environments.md#set-environment-variables). Also allow your collector’s domain in the environment’s [network access](../02-Claude-Code-CLI/cloud-environments.md#network-access). A `vcs.*` key you declare in [`OTEL_RESOURCE_ATTRIBUTES`](#multi-team-organization-support) replaces the derived value for that key. If you declare `vcs.repository.url.full`, Claude Code never reads the remote and reports only the keys you declare. If HTTPS and SSH clones of one repository report different values, such as on a self-hosted install whose HTTPS clone URL carries a path prefix the SSH URL lacks, declare `vcs.repository.url.full` in `OTEL_RESOURCE_ATTRIBUTES` along with every other `vcs.*` key you want reported. Every clone then reports the identity you declare. The attributes flow only to your own exporters; Anthropic’s telemetry drops every `vcs.*` key.


[​](#metrics)

Metrics

Claude Code exports the following metrics. The Unit column shows the OpenTelemetry unit string attached to each metric; count metrics carry none.

| Metric Name                           | Description                                     | Unit   |
|---------------------------------------|-------------------------------------------------|--------|
| `claude_code.session.count`           | Count of CLI sessions started                   | none   |
| `claude_code.lines_of_code.count`     | Count of lines of code modified                 | none   |
| `claude_code.pull_request.count`      | Number of pull requests created                 | none   |
| `claude_code.commit.count`            | Number of git commits created                   | none   |
| `claude_code.cost.usage`              | Cost of the Claude Code session                 | USD    |
| `claude_code.token.usage`             | Number of tokens used                           | tokens |
| `claude_code.code_edit_tool.decision` | Count of code editing tool permission decisions | none   |
| `claude_code.active_time.total`       | Total active time                               | s      |

When `prometheus` is the only exporter listed in `OTEL_METRICS_EXPORTER`, Claude Code omits the `USD`, `tokens`, and `s` units from the exported metrics so the scrape stays valid Prometheus text format. Metric names don’t change, and configurations that combine exporters, such as `otlp,prometheus`, keep the units. Before v2.1.216, the Prometheus scrape included OpenMetrics-only `# UNIT` lines that some scrapers rejected.


[​](#metric-details)

Metric details

Each metric includes the standard attributes listed above. Metrics with additional context-specific attributes are noted below.


[​](#session-counter)

Session counter

Incremented at the start of each session. **Attributes**:

- All [standard attributes](#standard-attributes)
- `start_type`: How the session was started. One of `"fresh"`, `"resume"`, `"continue"`, or `"agents_view"`. The `"agents_view"` value identifies the `claude agents` dashboard process, a user-launched local UI rather than a conversational session. Filter on this value to separate UI process launches from conversational sessions in your dashboards.


[​](#lines-of-code-counter)

Lines of code counter

Incremented when code is added or removed. **Attributes**:

- All [standard attributes](#standard-attributes)
- `type`: (`"added"`, `"removed"`)
- `model`: Model identifier for the model that made the change (for example, “claude-sonnet-5”)


[​](#pull-request-counter)

Pull request counter

Incremented when Claude Code creates a pull request or merge request through a shell command or an MCP tool. **Attributes**:

- All [standard attributes](#standard-attributes)


[​](#commit-counter)

Commit counter

Incremented when creating git commits via Claude Code. **Attributes**:

- All [standard attributes](#standard-attributes)


[​](#cost-counter)

Cost counter

Incremented after each API request. The `agent.name`, `skill.name`, `plugin.name`, `mcp_server.name`, and `mcp_tool.name` attributes each redact some names to a `"custom"` or `"third-party"` placeholder by default. If you set `OTEL_LOG_TOOL_DETAILS=1`, they carry the real names instead. Before v2.1.273, the cost and token counters and the `api_request`, `api_error`, and `api_refusal` events carried the redacted values even with `OTEL_LOG_TOOL_DETAILS=1` set. **Attributes**:

- All [standard attributes](#standard-attributes)
- `model`: Model identifier (for example, “claude-sonnet-5”)
- `query_source`: Category of the subsystem that issued the request. One of `"main"`, `"subagent"`, or `"auxiliary"`
- `speed`: `"fast"` when the request used fast mode. Absent otherwise
- `effort`: [Effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) applied to the request: `"low"`, `"medium"`, `"high"`, `"xhigh"`, or `"max"`. Absent when Claude Code sends no effort level, for example on a model that doesn’t support effort.
- `agent.name`: Subagent type that issued the request. Built-in agent names and agents from official-marketplace plugins appear verbatim. Other user-defined agent names are replaced with `"custom"`. Absent when the request was not issued by a named subagent type.
- `skill.name`: Skill active for the request, set by the Skill tool or a `/` command, or inherited by a spawned subagent. Built-in, bundled, user-defined, and official-marketplace plugin skill names appear verbatim. Third-party plugin skill names are replaced with `"third-party"`. Absent when no skill is active.
- `plugin.name`: Owning plugin when the active skill or subagent is provided by a plugin. Official-marketplace plugin names appear verbatim. Third-party plugin names are replaced with `"third-party"`. Absent when neither the skill nor the subagent has an owning plugin.
- `marketplace.name`: Marketplace the owning plugin was installed from. Only emitted for official-marketplace plugins, even with `OTEL_LOG_TOOL_DETAILS=1` set. Absent otherwise.
- `mcp_server.name`: MCP server whose tool result this request consumed. Built-in, claude.ai-proxied, and official-registry server names appear verbatim. User-configured server names are replaced with `"custom"`. Absent when the request consumed no MCP tool result. Before v2.1.222, Claude Code set this attribute on every request after an MCP tool call, not only on requests that consumed a tool result, so dashboards that aggregate it show a step down after you upgrade.
- `mcp_tool.name`: MCP tool whose result this request consumed, with the same redaction and version behavior as `mcp_server.name`. Absent when the request consumed no MCP tool result.


[​](#token-counter)

Token counter

Incremented after each API request. **Attributes**:

- All [standard attributes](#standard-attributes)
- `type`: (`"input"`, `"output"`, `"cacheRead"`, `"cacheCreation"`). The `"input"` type excludes tokens read from or written to the prompt cache, which are counted under `"cacheRead"` and `"cacheCreation"`. The `"cacheCreation"` type counts cache writes with the five-minute and one-hour [cache lifetimes](../02-Claude-Code-CLI/prompt-caching.md#cache-lifetime) together.
- `model`: Model identifier (for example, “claude-sonnet-5”)
- `query_source`: Category of the subsystem that issued the request. One of `"main"`, `"subagent"`, or `"auxiliary"`
- `speed`: `"fast"` when the request used fast mode. Absent otherwise
- `effort`: [Effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) applied to the request. See [Cost counter](#cost-counter) for details.
- `agent.name`, `skill.name`, `plugin.name`, `marketplace.name`, `mcp_server.name`, `mcp_tool.name`: Skill, plugin, agent, and MCP attribution for the request. See [Cost counter](#cost-counter) for definitions and redaction behavior.


[​](#code-edit-tool-decision-counter)

Code edit tool decision counter

Incremented when user accepts or rejects Edit, Write, or NotebookEdit tool usage. **Attributes**:

- All [standard attributes](#standard-attributes)
- `tool_name`: Tool name (`"Edit"`, `"Write"`, `"NotebookEdit"`)
- `decision`: User decision (`"accept"`, `"reject"`)
- `source`: Where the decision came from. One of `"config"`, `"hook"`, `"user_permanent"`, `"user_temporary"`, `"user_abort"`, or `"user_reject"`. See the [Tool decision event](#tool-decision-event) for what each value means.
- `language`: Programming language of the edited file, such as `"TypeScript"`, `"Python"`, `"JavaScript"`, or `"Markdown"`. Returns `"unknown"` for unrecognized file extensions.


[​](#active-time-counter)

Active time counter

Tracks actual time spent actively using Claude Code, excluding idle time. This metric is incremented during user interactions, such as typing and reading responses, and during CLI processing, such as tool execution and AI response generation. **Attributes**:

- All [standard attributes](#standard-attributes)
- `type`: `"user"` for keyboard interactions, `"cli"` for tool execution and AI responses


[​](#events)

Events

Claude Code exports the following events via OpenTelemetry logs/events (when `OTEL_LOGS_EXPORTER` is configured):


[​](#event-correlation-attributes)

Event correlation attributes

When a user submits a prompt, Claude Code may make multiple API calls and run several tools. The `prompt.id` attribute lets you tie all of those events back to the single prompt that triggered them.

| Attribute           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `prompt.id`         | UUID v4 identifier linking all events produced while processing a single user prompt                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| `event.sequence`    | 0-based counter for ordering events, counted per Claude Code process rather than per session                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `message.uuid`      | UUID of the message as persisted in the session transcript, the `~/.claude/projects/*/*.jsonl` files. Present on `assistant_response`, on `api_response_body`, and on `user_prompt` except for command dispatches, which can produce zero or many messages. On `assistant_response` and `api_response_body`, this is the response’s final transcript entry, which the next turn’s `parentUuid` chains from. Requires Claude Code v2.1.274 or later on `api_response_body`                                                                      |
| `request_id`        | Server-assigned ID of the API request, read from the `request-id` response header, such as `req_011...`. On a response with no `request-id` header, as on [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md), the value comes from the `x-amzn-requestid` header instead. Present on `api_request`, `api_error`, `api_refusal`, `assistant_response`, and `api_response_body` when the response carries either header. Matches the same attribute on the `llm_request` trace span. The `x-amzn-requestid` source requires Claude Code v2.1.282 or later |
| `client_request_id` | Client-generated UUID sent as the `x-client-request-id` request header. Present on `api_request` and `api_error` on first-party API connections; absent on third-party provider backends and when the request was retried through the non-streaming fallback. Pairs a request with its response and remains available for failures such as timeouts that never produced a server `request_id`. Matches the same attribute on the `llm_request` trace span                                                                                      |

To trace all activity triggered by a single prompt, filter your events by a specific `prompt.id` value. This returns the user_prompt event, any api_request events, and any tool_result events that occurred while processing that prompt. `event.sequence` starts at 0 each time a Claude Code process starts and counts up for the life of that process. It keeps counting across `/clear`, which assigns a new `session.id`. If you [resume a session without forking](../02-Claude-Code-CLI/how-claude-code-works.md#resume-or-fork-sessions), the session keeps its `session.id` but takes its `event.sequence` values from the process that resumed it, so within one session a later event can carry a lower value than an earlier one, or repeat one. To order a session’s events, sort by `event.timestamp` and use `event.sequence` to order events that share a timestamp. For message-level reconstruction, each event class carries a key that matches a field in the session transcript. The transcript entry format is [internal to Claude Code](../02-Claude-Code-CLI/sessions.md#where-transcripts-are-stored) and changes between versions, so a pipeline that joins on these fields can break on any release; treat the joins as version-specific rather than a stable contract:

- `message.uuid` on `user_prompt`, `assistant_response`, and `api_response_body`
- `request_id` on the API events, persisted as `requestId` on the transcript’s assistant entries
- `tool_use_id` on `tool_result` and `tool_decision` events, and on `safety_event` events except those for the [end of a turn](#safety-event)


[​](#user-prompt-event)

User prompt event

Logged when a prompt is submitted, including on turns Claude Code starts on its own. **Event Name**: `claude_code.user_prompt` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"user_prompt"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `prompt_length`: Length of the prompt
- `prompt`: Prompt content. Redacted by default. Set `OTEL_LOG_USER_PROMPTS=1` to include it
- `prompt_text`: Same value as `prompt`, redacted under the same gate. A backend that stores dotted attribute names as nested objects reads `prompt.id` as `id` inside an object named `prompt` and can lose the prompt string. Read `prompt_text` there instead. Requires Claude Code v2.1.287 or later
- `message.uuid`: UUID of the resulting user message, matching the persisted transcript entry. Absent on command dispatches, which can produce zero or many messages.
- `command_name`: Command name when the prompt invokes one. Built-in and bundled command names such as `compact` or `debug` are emitted as-is; aliases such as `reset` emit as typed rather than the canonical name. Custom, plugin, and MCP command names collapse to `custom` or `mcp` unless `OTEL_LOG_TOOL_DETAILS=1` is set
- `command_source`: Origin of the command when present: `builtin`, `custom`, or `mcp`. Plugin-provided commands report as `custom`


[​](#assistant-response-event)

Assistant response event

Logged after each API request that returns text content from the model. Only the response’s text blocks are included; thinking blocks and tool-use blocks are excluded. **Event Name**: `claude_code.assistant_response` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"assistant_response"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `response_length`: Length of the response text in characters
- `response`: Response text, truncated at the content limit (60 KB by default). Redacted to `<REDACTED>` by default. Set `OTEL_LOG_ASSISTANT_RESPONSES=1` to include it. When `OTEL_LOG_ASSISTANT_RESPONSES` is unset, `OTEL_LOG_USER_PROMPTS` controls it instead, so set `OTEL_LOG_ASSISTANT_RESPONSES=0` to keep responses redacted while prompt logging is on
- `model`: Model identifier (for example, “claude-sonnet-5”)
- `request_id`: API request ID, described under [Event correlation attributes](#event-correlation-attributes)
- `message.uuid`: UUID of the response’s final transcript entry. An API response is persisted as one transcript entry per content block; this is the last one, which the next turn’s `parentUuid` chains from.
- `query_source`: Subsystem that issued the request, such as `"repl_main_thread"`, `"compact"`, or a subagent name


[​](#tool-result-event)

Tool result event

Logged when a tool completes execution. Not emitted if the tool call was rejected; see the [Tool decision event](#tool-decision-event) for rejections. **Event Name**: `claude_code.tool_result` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"tool_result"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `tool_name`: Name of the tool
- `tool_use_id`: Unique identifier for this tool invocation. Matches the `tool_use_id` passed to hooks, allowing correlation between OTel events and hook-captured data.
- `success`: `"true"` or `"false"`
- `duration_ms`: Execution time in milliseconds
- `error_type`: Error category string when the tool failed, such as `"Error:ENOENT"` or `"ShellError"`
- `error` (when `OTEL_LOG_TOOL_DETAILS=1`): Full error message when the tool failed
- `decision_type`: Always `"accept"`, since this event is only emitted after the tool runs. Rejected calls don’t produce a tool result
- `decision_source`: Where the permission decision came from. One of `"config"`, `"hook"`, `"user_permanent"`, or `"user_temporary"`. See the [Tool decision event](#tool-decision-event) for what each value means. The reject-only sources `"user_abort"` and `"user_reject"` never appear on this event.
- `tool_input_size_bytes`: Size of the JSON-serialized tool input in bytes
- `tool_result_size_bytes`: Size of the tool result in bytes
- `mcp_server_scope`: MCP server scope identifier (for MCP tools)
- `vcs.ref.head.revision`, `vcs.ref.head.name`, `vcs.ref.head.type` (when `OTEL_LOG_TOOL_DETAILS=1`): the commit identity of a successful `git commit` run by the Bash or PowerShell tool. `vcs.ref.head.revision` is the commit SHA, `vcs.ref.head.name` is the branch it was committed on, and `vcs.ref.head.type` is `branch`. The name and type are omitted when the commit was made on a detached HEAD. Requires Claude Code v2.1.269 or later
- `tool_parameters` (when `OTEL_LOG_TOOL_DETAILS=1`): JSON string containing tool-specific parameters. For Claude Desktop’s built-in servers, in sessions Claude Desktop owns, the `mcp_server_name`/`mcp_tool_name` pair is included even with the flag off, the same host-authored exception as the [Tool decision event](#tool-decision-event). The parameters vary by tool:
  - For Bash tool: includes `bash_command`, `full_command`, `timeout`, `description`, and `dangerouslyDisableSandbox`, plus `git_commit_id` and `git_branch` when a `git commit` command succeeds. `git_commit_id` is the full commit SHA when the commit is the HEAD of the session’s working directory, and git’s abbreviated SHA otherwise. `git_branch` is the branch it was committed on, omitted on a detached HEAD
  - For the desktop app’s workspace Bash tool, which also reports `tool_name` as `Bash`: includes only `bash_command`, `full_command`, and `timeout`
  - For MCP tools: includes `mcp_server_name`, `mcp_tool_name`
  - For Skill tool: includes `skill_name`
  - For Agent tool or legacy Task tool: includes `subagent_type`
- `tool_input` (when `OTEL_LOG_TOOL_DETAILS=1`): JSON-serialized tool arguments. Individual values over 512 characters are truncated, and the full payload is bounded to ~4 K characters. Applies to all tools including MCP tools.


[​](#api-request-event)

API request event

Logged for each API request to Claude. **Event Name**: `claude_code.api_request` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"api_request"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `model`: Model used (for example, “claude-sonnet-5”)
- `cost_usd`: Estimated cost in USD
- `cost_usd_micros`: Estimated cost in millionths of a US dollar, emitted as an integer
- `duration_ms`: Request duration in milliseconds
- `input_tokens`: Number of input tokens, excluding tokens read from or written to the prompt cache
- `output_tokens`: Number of output tokens
- `cache_read_tokens`: Number of tokens read from cache
- `cache_creation_tokens`: Number of tokens used for cache creation
- `request_id`: API request ID, such as `"req_011..."`, described under [Event correlation attributes](#event-correlation-attributes).
- `client_request_id`: Client-generated UUID sent as the `x-client-request-id` request header; see the [event correlation attributes](#event-correlation-attributes) table for when it’s present
- `speed`: `"fast"` or `"normal"`, indicating whether fast mode was active
- `query_source`: Subsystem that issued the request, such as `"repl_main_thread"`, `"compact"`, or a subagent name
- `effort`: [Effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) applied to the request: `"low"`, `"medium"`, `"high"`, `"xhigh"`, or `"max"`. Absent when Claude Code sends no effort level, for example on a model that doesn’t support effort.
- `agent.name`, `skill.name`, `plugin.name`, `marketplace.name`, `mcp_server.name`, `mcp_tool.name`: Skill, plugin, agent, and MCP attribution for the request. See [Cost counter](#cost-counter) for definitions and redaction behavior.


[​](#api-error-event)

API error event

Logged when an API request to Claude fails. **Event Name**: `claude_code.api_error` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"api_error"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `model`: Model used (for example, “claude-sonnet-5”)
- `error`: Error message
- `status_code`: HTTP status code as a number. Absent for non-HTTP errors such as connection failures.
- `duration_ms`: Request duration in milliseconds
- `attempt`: Number of attempts made, including the initial request. [Detect retry exhaustion](#detect-retry-exhaustion) says when the count starts again
- `request_id`: API request ID, such as `"req_011..."`, described under [Event correlation attributes](#event-correlation-attributes).
- `client_request_id`: Client-generated UUID sent as the `x-client-request-id` request header. Available even when a failure such as a timeout or connection error never produced a server `request_id`; see the [event correlation attributes](#event-correlation-attributes) table for when it’s present.
- `speed`: `"fast"` or `"normal"`, indicating whether fast mode was active
- `query_source`: Subsystem that issued the request, such as `"repl_main_thread"`, `"compact"`, or a subagent name
- `effort`: [Effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) applied to the request. Absent when Claude Code sends no effort level, for example on a model that doesn’t support effort.
- `agent.name`, `skill.name`, `plugin.name`, `marketplace.name`, `mcp_server.name`, `mcp_tool.name`: Skill, plugin, agent, and MCP attribution for the request. See [Cost counter](#cost-counter) for definitions and redaction behavior.


[​](#api-refusal-event)

API refusal event

Logged when an API request returns `stop_reason: "refusal"`. Refusals arrive on a successful response stream rather than as an HTTP error, so the `api_error` event doesn’t fire for them. This event lets you track refusal frequency and group refusals by the same attributes as `api_request` and `api_error`. **Event Name**: `claude_code.api_refusal` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"api_refusal"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `model`: Model identifier from the request
- `request_id`: API request ID, such as `"req_011..."`, described under [Event correlation attributes](#event-correlation-attributes).
- `query_source`: Subsystem that issued the request, such as `"repl_main_thread"`, `"compact"`, or a subagent name. See [`api_request`](#api-request-event) for definitions.
- `speed`: Either `"fast"` when [Fast mode](../02-Claude-Code-CLI/fast-mode.md) is active, or `"normal"`
- `attempt`: Retry attempt number. The first attempt is `1`.
- `effort`: [Effort level](../02-Claude-Code-CLI/model-config.md#adjust-effort-level) applied to the request. Absent when Claude Code sends no effort level, for example on a model that doesn’t support effort.
- `server_fallback_hop`: `true` when the API’s server-side model fallback already retried this refusal on a different model, so the user did not see this particular refusal. `false` when the request ended in a refusal. A single turn can emit both a `true` hop event and a later `false` final event when the fallback model also refuses.
- `has_category`: `true` when the API response carried a `stop_details.category` of `"cyber"`, `"bio"`, `"frontier_llm"`, or `"reasoning_extraction"`. `false` when the response carried no category or a value outside that set. Absent when `server_fallback_hop` is `true`, because hop blocks don’t carry `stop_details`.
- `has_explanation`: `true` when the API response carried a `stop_details.explanation`, otherwise `false`. Absent when `server_fallback_hop` is `true`.
- `category`: The `stop_details.category` value from the API response. One of `"cyber"`, `"bio"`, `"frontier_llm"`, or `"reasoning_extraction"`. Only present when `OTEL_LOG_TOOL_DETAILS=1` is set and `has_category` is `true`.
- `agent.name`, `skill.name`, `plugin.name`, `marketplace.name`, `mcp_server.name`, `mcp_tool.name`: Skill, plugin, agent, and MCP attribution for the request. See [Cost counter](#cost-counter) for definitions and redaction behavior.


[​](#api-request-body-event)

API request body event

Logged for each API request attempt when `OTEL_LOG_RAW_API_BODIES` is set. One event is emitted per attempt, so retries with adjusted parameters each produce their own event. **Event Name**: `claude_code.api_request_body` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"api_request_body"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `body`: JSON-serialized Messages API request parameters, such as the system prompt, messages, and tools, truncated at the content limit (60 KB by default). Extended-thinking content in prior assistant turns is redacted. Emitted only in inline mode (`OTEL_LOG_RAW_API_BODIES=1`).
- `body_ref`: Absolute path to a `<dir>/<uuid>.request.json` file containing the untruncated body. Emitted only in file mode (`OTEL_LOG_RAW_API_BODIES=file:<dir>`).
- `body_length`: Untruncated body length. UTF-8 bytes when `OTEL_LOG_RAW_API_BODIES=file:<dir>`, or UTF-16 code units when `=1`
- `body_truncated`: `"true"` when inline truncation occurred. Absent in file mode and when no truncation occurred.
- `model`: Model identifier from the request parameters
- `query_source`: Subsystem that issued the request (for example, `"compact"`)
- `request_body_id`: UUID that identifies this attempt’s request body. The [`api_response_body` event](#api-response-body-event) for the attempt that succeeds carries the same value, so you can pair a response with the exact request that produced it. Requires Claude Code v2.1.274 or later


[​](#api-response-body-event)

API response body event

Logged for each successful API response when `OTEL_LOG_RAW_API_BODIES` is set. In file mode (`OTEL_LOG_RAW_API_BODIES=file:<dir>`), Claude Code also appends one JSON line to `<dir>/index.jsonl` for each successful response, with the fields `timestamp`, `session_id`, `query_source`, `model`, `request_id`, `message_id`, `message_uuid`, `request_file`, and `response_file`. Read it to find the request and response files behind a given transcript message without querying your telemetry backend. The index file requires Claude Code v2.1.274 or later. **Event Name**: `claude_code.api_response_body` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"api_response_body"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `body`: JSON-serialized Messages API response, including the id, content blocks, usage, and stop reason, truncated at the content limit (60 KB by default). Extended-thinking content is redacted. Emitted only in inline mode (`OTEL_LOG_RAW_API_BODIES=1`).
- `body_ref`: Absolute path to a `<dir>/<request_id>.response.json` file containing the untruncated body. Emitted only in file mode (`OTEL_LOG_RAW_API_BODIES=file:<dir>`).
- `body_length`: Untruncated body length. UTF-8 bytes when `OTEL_LOG_RAW_API_BODIES=file:<dir>`, or UTF-16 code units when `=1`
- `body_truncated`: `"true"` when inline truncation occurred. Absent in file mode and when no truncation occurred.
- `model`: Model identifier
- `query_source`: Subsystem that issued the request
- `request_id`: API request ID, such as `"req_011..."`, described under [Event correlation attributes](#event-correlation-attributes).
- `request_body_id`: The `request_body_id` of the [`api_request_body` event](#api-request-body-event) that this response answers. Requires Claude Code v2.1.274 or later
- `message.id`: Message ID the API assigned to the response, the `id` field of the response body. Requires Claude Code v2.1.274 or later
- `message.uuid`: UUID of the response’s final transcript entry. Together with `request_body_id`, it links a transcript message to the request and response bodies behind it. Requires Claude Code v2.1.274 or later


[​](#tool-decision-event)

Tool decision event

Logged when a tool permission decision is made (accept/reject). **Event Name**: `claude_code.tool_decision` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"tool_decision"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `tool_name`: Name of the tool (for example, “Read”, “Edit”, “Write”, “NotebookEdit”)
- `tool_use_id`: Unique identifier for this tool invocation. Matches the `tool_use_id` passed to hooks, allowing correlation between OTel events and hook-captured data.
- `decision`: Either `"accept"` or `"reject"`
- `tool_source`: Always present. The tool’s provenance, as a closed set of CLI-authored values:
  - `"builtin"`: the CLI’s own tools
  - `"mcp"`: MCP servers generally
  - `"sdk_host_builtin_mcp"`: an in-process server built into Claude Desktop itself, in a session Claude Desktop owns. Claude Desktop owns a session it started from one of its own entrypoints, `claude-desktop`, `claude-desktop-3p`, or `local-agent`, when that session isn’t a nested child; nested sessions, including sessions Claude Code itself spawns, report these servers as `"mcp"`
- `source`: Where the decision came from:
  - `"config"`: Decided automatically without prompting, by one of these sources:
    - Project settings
    - Allow or deny rules in the user’s personal settings
    - Enterprise managed policy
    - `--allowedTools` or `--disallowedTools` flags
    - The active permission mode
    - The auto mode classifier, whose verdict the same event records in `classifier_verdict`
    - A grant for the rest of the session, given at an earlier permission prompt in the same interactive CLI session
    - The tool being inherently safe

    The `source` value doesn’t indicate which of these sources matched. To find which one refused a call, look for the [Safety event](#safety-event) with the same `tool_use_id` and read its `category` and `rule_source` attributes. Some refused calls [have no safety event](#calls-without-a-safety-event). Claude Code also reports `"config"` when the permission prompt request itself fails. For example, that request fails when the Agent SDK’s [`canUseTool`](../05-Agent-SDK/agent-sdk-typescript.md#canusetool) callback or the [`--permission-prompt-tool`](../02-Claude-Code-CLI/cli-reference.md#cli-flags) tool returns an invalid result, or when the input stream closes while the request is pending. Before v2.1.216, Claude Code reported these failures as `"user_reject"`.
  - `"hook"`: A `PreToolUse` or `PermissionRequest` hook returned the decision.
  - `"user_permanent"`: Emitted when the user chose “Yes, and don’t ask again for …” at a permission prompt, which saves an allow rule to their personal settings. In the interactive CLI this is emitted only for that choice itself; later calls that match the saved rule emit `"config"` instead. In Agent SDK or non-interactive `-p` sessions, both the initial choice and later rule matches emit `"user_permanent"`. Treated as an accept.
  - `"user_temporary"`: Emitted when the user chose “Yes” at a permission prompt for a one-time approval, or chose an option that grants access for the rest of the session on a file edit or read prompt. In the interactive CLI this is emitted only for the choice itself; later calls allowed by that session-scoped grant emit `"config"` instead. In Agent SDK or non-interactive `-p` sessions, both the choice and later matches emit `"user_temporary"`. Treated as an accept.
  - `"user_abort"`: Emitted when the user dismissed the permission prompt without answering. In Agent SDK and non-interactive `-p` sessions, this includes interrupting the turn while a `canUseTool` or `--permission-prompt-tool` permission request is pending; before v2.1.216, Claude Code reported that interrupt as `"user_reject"`. Treated as a reject.
  - `"user_reject"`: Emitted when the user chose “No” when prompted. In the interactive CLI this is emitted only for that choice itself; calls that match a deny rule in the user’s personal settings emit `"config"` instead. In Agent SDK or non-interactive `-p` sessions, calls that match a deny rule in personal settings emit `"user_reject"`. Treated as a reject.
- `classifier_verdict`: The auto mode [classifier](../02-Claude-Code-CLI/permission-modes.md#how-the-classifier-evaluates-actions) verdict for the call, or why Claude Code denied the call when none came. Requires Claude Code v2.1.301 or later
  - `"allow"`: the classifier approved the call
  - `"block"`: the classifier blocked the call. A classifier check that fails partway through can also end as `"block"`, so a partial classifier outage can appear as more blocks rather than as `"unavailable"`
  - `"no_verdict"`: the check ran but gave [no usable verdict](../02-Claude-Code-CLI/errors.md#auto-mode-cannot-determine-the-safety-of-an-action), so Claude Code denied the call. The denial doesn’t mean the classifier found the call unsafe
  - `"unavailable"`: the request to the classifier failed, for example on a rate limit, timeout, or connection error, so Claude Code denied the call. With [server-side classifier review](../02-Claude-Code-CLI/permission-modes.md#server-side-classifier-review), it also marks a call denied because [the server returned no safety verdict](../02-Claude-Code-CLI/errors.md#the-server-returned-no-safety-verdict)

  The attribute also appears on plan mode calls when [the classifier reviews commands in plan mode](../02-Claude-Code-CLI/permission-modes.md#analyze-before-you-edit-with-plan-mode). The attribute is absent in these cases:
  - An allow or deny [permission rule](../02-Claude-Code-CLI/permissions.md#manage-permissions) decided the call
  - Claude Code approved the call in auto mode without asking the classifier
  - The user answered the prompt Claude Code showed after [repeated blocks](../02-Claude-Code-CLI/permission-modes.md#repeated-block-thresholds)
  - In a `-p` run without `--permission-prompt-tool`, Claude Code denied the call after repeated blocks because there was nobody to answer a prompt. That event reports `decision` as `"reject"` and `source` as `"config"`
- `classifier_category` (when `OTEL_LOG_TOOL_DETAILS=1`): The name of the [built-in rule](../02-Claude-Code-CLI/auto-mode-config.md#inspect-the-defaults-and-your-effective-config) the classifier named when it blocked the call, in lowercase with underscores, such as `"data_exfiltration"` for the Data Exfiltration rule. Requires Claude Code v2.1.301 or later. The attribute is present when both of these hold:
  - `classifier_verdict` is `"block"`
  - The classifier named exactly one rule, and that rule is a built-in one rather than one you wrote

  Before v2.1.302, the attribute was also absent on any block that [server-side classifier review](../02-Claude-Code-CLI/permission-modes.md#server-side-classifier-review) decided.
- `permission_mode`: [Permission mode](../02-Claude-Code-CLI/permission-modes.md) active when this call’s permission check began: `"default"`, `"plan"`, `"acceptEdits"`, `"bypassPermissions"`, `"dontAsk"`, or `"auto"`. Manual mode appears as `"default"`. A [subagent](../09-Agents-Patterns/sub-agents.md#permission-modes) call reports the subagent’s permission mode rather than the session’s. Requires Claude Code v2.1.301 or later
- `tool_parameters` (when `OTEL_LOG_TOOL_DETAILS=1`): JSON string containing tool-specific parameters. Same shape as the [Tool result event](#tool-result-event), minus post-execution fields such as `git_commit_id`. Values may differ from `tool_result` for an accepted call if the permission decision rewrites the tool input via `updatedInput`. Use this attribute to see which command was rejected when `decision` is `"reject"`.
  - For `"sdk_host_builtin_mcp"` tools: `mcp_server_name` and `mcp_tool_name` are included even when `OTEL_LOG_TOOL_DETAILS` is off, because the host application defines these names; without them, a rejected call to one of these built-in servers would be unattributable on the default stream. For user-configured MCP servers, the event’s `tool_name` is always the literal `"mcp_tool"`, and the server and tool names appear only in `tool_parameters` with the flag on; argument content requires the flag everywhere.
  - For Bash tool: includes `bash_command`, `full_command`, `timeout`, `description`, `dangerouslyDisableSandbox`. The desktop app’s workspace bash tool also reports `tool_name` as `Bash`, but includes only `bash_command`, `full_command`, and `timeout`
  - For MCP tools: includes `mcp_server_name`, `mcp_tool_name`
  - For Skill tool: includes `skill_name`
  - For Agent tool or legacy Task tool: includes `subagent_type`

If you lower the OpenTelemetry SDK’s `OTEL_LOGRECORD_ATTRIBUTE_COUNT_LIMIT` below the number of attributes on a `tool_decision` event, the SDK drops the attributes that Claude Code set last. The SDK drops `permission_mode` first, `classifier_category` second, and `classifier_verdict` third, and drops the event’s other attributes only after those three. This ordering requires Claude Code v2.1.302 or later.


[​](#safety-event)

Safety event

Logged when a check refuses a tool call or requires a permission prompt, except for [calls without a safety event](#calls-without-a-safety-event). Also logged when the [sandbox’s network proxy](../02-Claude-Code-CLI/sandboxing.md#network-isolation) refuses a connection or request, and when a hook blocks a result. Requires Claude Code v2.1.307 or later. The event exports with the other events and has no separate setting. The event’s own attributes hold no command text, file paths, host names, or MCP server and tool names. The record also carries the [standard attributes](#standard-attributes) every event has, which can include `workspace.host_paths` and the [repository attributes](#repository-attributes). To see the command or MCP tool behind an event, set `OTEL_LOG_TOOL_DETAILS=1` and read `tool_parameters` on the matching `tool_decision` event. **Event Name**: `claude_code.safety_event` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"safety_event"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `category`: The check or outcome, a [category value](#safety-event-categories)
- `decision`: `"accept"`, `"reject"`, or `"sent_back"` when a hook blocked a result or turn end
- `decision_source`: Who or what decided, a [`tool_decision`](#tool-decision-event) `source` value or `"other"`
- `reason_code`: The check’s reason, such as `"rule:policySettings"`, `"mode:dontAsk"`, `"hook:PreToolUse"`, `"classifier:auto-mode"`, `"bashMiss:too-complex"`, or `"unknown"`
- `reason_type`: The check’s reason type, such as `"rule"`, `"subcommandResults"`, `"mode"`, `"hook"`, `"classifier"`, or `"asyncAgent"`
- `rule_source`: Settings source of the rule involved, such as `"policySettings"`, `"userSettings"`, `"projectSettings"`, `"localSettings"`, `"flagSettings"`, `"cliArg"`, or `"session"`
- `grant_scope`: `"once"` or `"session"` for a prompt approved without saving a rule to a settings file
- `circuit_breaker`: The built-in safety check, such as `"dangerousRemoval"`, `"outsideReadsBlocked"`, or `"claudeSettingsFile"`
- `classifier_category`: With `OTEL_LOG_TOOL_DETAILS=1`, the [built-in rule](../02-Claude-Code-CLI/auto-mode-config.md#inspect-the-defaults-and-your-effective-config) behind an auto mode block, such as `"data_exfiltration"`
- `hook_event`: `"pre_tool_use"` for a `PreToolUse` hook, or `"post_tool_use"`, `"post_tool_use_failure"`, `"stop"`, or `"subagent_stop"` on `"hook_review"` events
- `tool_class`: `"bash"` for Bash and [Monitor](../02-Claude-Code-CLI/tools-reference.md#monitor-tool), `"powershell"`, `"file"` for Read, Edit, Write, NotebookEdit, Glob, and Grep, `"web"` for WebFetch and WebSearch, `"mcp"`, or `"other"`
- `tool_name`: The tool’s name, such as `"Bash"` for a built-in tool, or `"mcp_tool"`, `"skill_tool"`, `"dynamic_tool"`, or `"other_tool"`
- `tool_source`: `"builtin"`, `"mcp"`, or `"sdk_host_builtin_mcp"`, as on [`tool_decision`](#tool-decision-event)
- `permission_mode`: The call’s [permission mode](../02-Claude-Code-CLI/permission-modes.md) when checked: `"default"`, `"plan"`, `"acceptEdits"`, `"bypassPermissions"`, `"dontAsk"`, or `"auto"`
- `tool_use_id`: The call’s ID, as on `tool_decision` and `tool_result`
- `sandbox_violation_count`: How many distinct connections or requests from the call’s command the sandbox’s network proxy refused, `"0"` to `"20"` as a string. Refusals are counted for a sandboxed command that finished in the foreground
- `sandbox_enabled`: `"true"` if [sandboxing](../02-Claude-Code-CLI/sandboxing.md) is on for the session, else `"false"`

These are the event’s own attributes when a managed-settings deny rule refuses a Bash command:

```python
{
  "event.name": "safety_event",
  "category": "managed_policy",
  "decision": "reject",
  "decision_source": "config",
  "reason_code": "rule:policySettings",
  "rule_source": "policySettings",
  "tool_class": "bash",
  "tool_name": "Bash",
  "tool_source": "builtin",
  "permission_mode": "default",
  "tool_use_id": "toolu_01ABC123...",
  "sandbox_violation_count": "0",
  "sandbox_enabled": "false"
}
```

##### Safety event categories

| Value                    | Check or outcome                                                                                                                                                                                                                                                                                                                                                                 | Detail attribute             |
|--------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------|
| `"permission_rule"`      | A deny or ask [permission rule](../02-Claude-Code-CLI/permissions.md#manage-permissions) outside managed settings                                                                                                                                                                                                                                                                                | `rule_source`                |
| `"managed_policy"`       | A deny or ask rule from [managed settings](../02-Claude-Code-CLI/permissions.md#managed-settings)                                                                                                                                                                                                                                                                                                | `rule_source`                |
| `"permission_mode"`      | [`dontAsk`](../02-Claude-Code-CLI/permission-modes.md#allow-only-pre-approved-tools-with-dontask-mode) or [plan](../02-Claude-Code-CLI/permission-modes.md#analyze-before-you-edit-with-plan-mode) mode, or a prompt with [no category](#prompts-with-no-category) in a session that can’t show prompts, as with [`--permission-prompts none`](../02-Claude-Code-CLI/headless.md#turn-off-permission-prompts-in-unattended-runs) | `reason_code`, `reason_type` |
| `"hook"`                 | A hook, such as [`PreToolUse`](../07-Hooks/hooks.md#pretooluse) or [`PermissionRequest`](../07-Hooks/hooks.md#permissionrequest)                                                                                                                                                                                                                                                             | `hook_event`, `reason_code`  |
| `"auto_mode_block"`      | The auto mode [classifier](../02-Claude-Code-CLI/permission-modes.md#how-the-classifier-evaluates-actions) blocked the call                                                                                                                                                                                                                                                                      | `classifier_category`        |
| `"auto_mode_no_verdict"` | No usable [classifier verdict](../02-Claude-Code-CLI/errors.md#auto-mode-cannot-determine-the-safety-of-an-action), such as after a failed request                                                                                                                                                                                                                                               | `reason_code`                |
| `"safety_check"`         | A built-in safety check, such as for Read, Edit, or Write outside your [working directories](../02-Claude-Code-CLI/permissions.md#working-directories)                                                                                                                                                                                                                                           | `circuit_breaker`            |
| `"static_analysis"`      | How a Bash or Monitor command is written, such as one too complex to analyze                                                                                                                                                                                                                                                                                                     | `reason_code`                |
| `"sandbox_override"`     | The prompt to [run a command outside the sandbox](../02-Claude-Code-CLI/sandboxing.md#the-unsandboxed-retry-escape-hatch)                                                                                                                                                                                                                                                                        | `reason_code`                |
| `"user_rejected"`        | Someone declined or dismissed a prompt that had [no category](#prompts-with-no-category) or that only a hook required, or a `canUseTool` callback or `--permission-prompt-tool` tool denied one                                                                                                                                                                                  | `decision_source`            |
| `"sandbox_network"`      | The call ran and the [sandbox’s network proxy](../02-Claude-Code-CLI/sandboxing.md#network-isolation) refused a connection or request                                                                                                                                                                                                                                                            | `sandbox_violation_count`    |
| `"sandbox_other"`        | Like `"sandbox_network"`, but no refused target reads as a host name or IP address                                                                                                                                                                                                                                                                                               | `sandbox_violation_count`    |
| `"hook_review"`          | A hook [blocked the call’s result](../07-Hooks/hooks.md#posttooluse-decision-control) or [the end of a turn](../07-Hooks/hooks.md#stop-decision-control)                                                                                                                                                                                                                                     | `hook_event`                 |
| `"other"`                | A refusal no other value describes                                                                                                                                                                                                                                                                                                                                               | `reason_code`                |

To find proxy refusals under any `category`, filter for a `sandbox_violation_count` that is present and isn’t `"0"`.

##### Calls without a safety event

To find calls refused because nobody could answer a [prompt with no category](#prompts-with-no-category), look for [`tool_decision`](#tool-decision-event) events reporting `"reject"` with `source` `"config"` and no safety event sharing their `tool_use_id`. Such refusals happen in a `-p` run with neither [`--permission-prompt-tool`](../02-Claude-Code-CLI/cli-reference.md#cli-flags) nor `--permission-prompts none`. Check calls such as these for a `tool_decision` event, since they have no safety event except [`"sandbox_network"`, `"sandbox_other"`, or `"hook_review"` events](#safety-event-categories) sent after they run:

- A call approved at a prompt with no category
- A call that a `canUseTool` callback or `--permission-prompt-tool` tool approved at a prompt only a `PreToolUse` hook required
- An [`AskUserQuestion`](../02-Claude-Code-CLI/tools-reference.md#askuserquestion-tool-behavior) or `ExitPlanMode` call, declined or not

##### Prompts with no category

A prompt has no category when no [`category` value](#safety-event-categories) names its check, such as the `default` mode prompt for an edit inside your working directory.


[​](#permission-mode-changed-event)

Permission mode changed event

Logged when the permission mode changes, for example from `Shift+Tab` cycling, exiting plan mode, or an auto mode gate check. To group tool calls by the permission mode each was checked in, use `permission_mode` on the [Tool decision event](#tool-decision-event). **Event Name**: `claude_code.permission_mode_changed` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"permission_mode_changed"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `from_mode`: The previous permission mode, for example `"default"`, `"plan"`, `"acceptEdits"`, `"auto"`, or `"bypassPermissions"`
- `to_mode`: The new permission mode
- `trigger`: What caused the change, for example `"shift_tab"`, `"exit_plan_mode"`, or `"auto_gate_denied"`. Absent for some changes, such as when the SDK sets the permission mode through [`setPermissionMode()`](../05-Agent-SDK/agent-sdk-typescript.md#query-object)


[​](#auth-event)

Auth event

Logged when `/login` or `/logout` completes. **Event Name**: `claude_code.auth` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"auth"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `action`: `"login"` or `"logout"`
- `success`: `"true"` or `"false"`
- `auth_method`: Authentication method, such as `"oauth"`
- `error_category`: Categorical error kind when the action failed. The raw error message is never included
- `status_code`: HTTP status code as a string when the action failed with an HTTP error


[​](#mcp-server-connection-event)

MCP server connection event

Logged when an MCP server connects, disconnects, or fails to connect. **Event Name**: `claude_code.mcp_server_connection` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"mcp_server_connection"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `status`: `"connected"`, `"failed"`, or `"disconnected"`
- `transport_type`: Server transport, such as `"stdio"`, `"sse"`, or `"http"`
- `server_scope`: Scope the server is configured at, such as `"user"`, `"project"`, or `"local"`
- `duration_ms`: Connection attempt duration in milliseconds
- `error_code`: Error code when the connection failed
- `is_plugin`: `true` when the server is provided by a plugin, `false` otherwise
- `plugin_id_hash` (when `is_plugin` is `true`): Stable hash of the plugin name and marketplace, for grouping events by plugin without exposing the name. Claude Code computes it as described under the [plugin loaded event](#plugin-loaded-event)
- `plugin.name` (when `is_plugin` is `true`): Name of the plugin that provides the server. For third-party plugins this is the literal string `"third-party"` unless `OTEL_LOG_TOOL_DETAILS=1`; this protects third-party plugin names from appearing in logs by default. Plugins from official Anthropic sources are always identified by name. The `plugin_id_hash` and `plugin.name` attributes flow to your own monitoring backend and are not sent to Anthropic
- `server_name` (when `OTEL_LOG_TOOL_DETAILS=1`): Configured server name
- `error` (when `OTEL_LOG_TOOL_DETAILS=1`): Full error message when the connection failed


[​](#internal-error-event)

Internal error event

Logged when Claude Code catches an unexpected internal error. Only the error class name and an errno-style code are recorded. The error message and stack trace are never included. This event is not emitted when running against Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry, or when `DISABLE_ERROR_REPORTING` is set. **Event Name**: `claude_code.internal_error` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"internal_error"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `error_name`: Error class name, such as `"TypeError"` or `"SyntaxError"`
- `error_code`: Node.js errno code such as `"ENOENT"` when present on the error


[​](#plugin-installed-event)

Plugin installed event

Logged when a plugin finishes installing, from both the `claude plugin install` CLI command and the interactive `/plugin` UI. **Event Name**: `claude_code.plugin_installed` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"plugin_installed"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `marketplace.is_official`: `"true"` if the marketplace is an official Anthropic marketplace, `"false"` otherwise
- `install.trigger`: `"cli"` or `"ui"`
- `plugin.name`: Name of the installed plugin. For third-party marketplaces this is included only when `OTEL_LOG_TOOL_DETAILS=1`
- `plugin.version`: Plugin version when declared in the marketplace entry. For third-party marketplaces this is included only when `OTEL_LOG_TOOL_DETAILS=1`
- `marketplace.name`: Marketplace the plugin was installed from. For third-party marketplaces this is included only when `OTEL_LOG_TOOL_DETAILS=1`


[​](#plugin-loaded-event)

Plugin loaded event

Logged once per enabled plugin at session start. Use this event to inventory which plugins are active across your fleet, as a complement to `plugin_installed` which records the install action itself. **Event Name**: `claude_code.plugin_loaded` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"plugin_loaded"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `plugin.name`: name of the plugin. For plugins outside the official marketplace and built-in bundle the value is `"third-party"` unless `OTEL_LOG_TOOL_DETAILS=1`
- `marketplace.name`: marketplace the plugin was installed from, when known. Redacted to `"third-party"` under the same condition as `plugin.name`
- `plugin.version`: version from the plugin manifest. Included only when the name is not redacted and the manifest declares a version
- `plugin.scope`: provenance category for the plugin: `"official"`, `"community"`, `"org"`, `"user-local"`, or `"default-bundle"`
- `enabled_via`: how the plugin came to be enabled: `"default-enable"`, `"org-policy"`, `"admin-install"`, `"seed-mount"`, or `"user-install"`. The `"admin-install"` value means the plugin is set to required or auto-install for your organization in [**Organization settings \> Plugins & skills**](https://claude.ai/admin-settings/skills?tab=inventory). Before v2.1.246, Claude Code reported these plugins as `"user-install"` or `"seed-mount"`
- `plugin_id_hash`: deterministic hash of the plugin name and marketplace, sent only to your configured exporter. Lets you count the distinct third-party plugins loaded across your fleet without recording their names. For [plugins synced from claude.ai](../08-Plugins-Skills/plugins-loading.md#synced-plugins), Claude Code hashes the plugin name with the marketplace name that claude.ai reports for the plugin, or with `synced` otherwise. Before v2.1.246, Claude Code didn’t use the marketplace name claude.ai reports in the hash
- `has_hooks`: whether the plugin contributes hooks, through [hook configuration](../08-Plugins-Skills/plugins-components.md#hooks) or as a [mod](../08-Plugins-Skills/plugins-mods-overview.md#how-a-mod-works)
- `has_mcp`: whether the plugin contributes MCP servers
- `host_owned_mcp`: `true` when the SDK host manages this plugin’s MCP connections and Claude Code skipped reading the plugin’s MCP server configuration, `false` otherwise
- `skill_path_count`: number of skill directories the plugin declares
- `command_path_count`: number of command directories the plugin declares
- `agent_path_count`: number of agent directories the plugin declares
- `safe_mode`: `"true"` when the session was started with [`--safe-mode`](../02-Claude-Code-CLI/cli-reference.md), `"false"` otherwise. In safe mode this event reports configured inventory only; the plugin’s commands, skills, hooks, and MCP servers don’t load. Requires Claude Code v2.1.169 or later


[​](#skill-activated-event)

Skill activated event

Logged when a skill is invoked, whether Claude calls it through the Skill tool or you run it as a `/` command. **Event Name**: `claude_code.skill_activated` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"skill_activated"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `skill.name`: Name of the skill. For user-defined and third-party plugin skills the value is the placeholder `"custom_skill"` unless `OTEL_LOG_TOOL_DETAILS=1`
- `invocation_trigger`: How the skill was triggered (`"user-slash"`, `"claude-proactive"`, or `"nested-skill"`)
- `skill.source`: Where the skill was loaded from (for example, `"bundled"`, `"userSettings"`, `"projectSettings"`, `"plugin"`)
- `skill.kind`: `"workflow"` when the skill is a workflow skill. Absent otherwise
- `plugin.name` (when `OTEL_LOG_TOOL_DETAILS=1` or the plugin is from an official marketplace): Name of the owning plugin when the skill is provided by a plugin
- `marketplace.name` (when `OTEL_LOG_TOOL_DETAILS=1` or the plugin is from an official marketplace): Marketplace the owning plugin was installed from, when the skill is provided by a plugin


[​](#at-mention-event)

At mention event

Logged when Claude Code resolves an `@`-mention in a prompt. Not every mention emits an event: early-exit paths such as permission denials, oversized files, PDF reference attachments, and directory listing failures return without logging. Each time Claude Code reads a prompt, it logs at most 100 events with a `mention_type` of `"agent"` and 100 with `"mcp_resource"`. Mentions past either limit still resolve but emit no event. **Event Name**: `claude_code.at_mention` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"at_mention"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `mention_type`: Type of mention (`"file"`, `"directory"`, `"agent"`, `"mcp_resource"`, `"peer"`). The `"peer"` value means you mentioned [one of your other Claude Code sessions](../02-Claude-Code-CLI/cross-session-messaging.md). Requires Claude Code v2.1.232 or later
- `success`: Whether the mention resolved successfully (`"true"` or `"false"`)


[​](#api-retries-exhausted-event)

API retries exhausted event

Logged once when an API request fails after more than one attempt. Emitted alongside the final `api_error` event. **Event Name**: `claude_code.api_retries_exhausted` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"api_retries_exhausted"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `model`: Model used
- `error`: Final error message
- `status_code`: HTTP status code as a number. Absent for non-HTTP errors.
- `total_attempts`: Total number of attempts made
- `total_retry_duration_ms`: Total wall-clock time across all attempts
- `speed`: `"fast"` or `"normal"`


[​](#hook-registered-event)

Hook registered event

Logged once per configured hook at session start. Use this event to inventory which hooks are active across your fleet, as a complement to the per-execution `hook_execution_start` and `hook_execution_complete` events. **Event Name**: `claude_code.hook_registered` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"hook_registered"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `hook_event`: hook event type, such as `"PreToolUse"` or `"PostToolUse"`
- `hook_type`: hook implementation type: `"command"`, `"prompt"`, `"mcp_tool"`, `"http"`, or `"agent"`
- `hook_source`: where the hook is defined: `"userSettings"`, `"projectSettings"`, `"localSettings"`, `"flagSettings"`, `"policySettings"`, or `"pluginHook"`
- `safe_mode`: `"true"` when the session was started with [`--safe-mode`](../02-Claude-Code-CLI/cli-reference.md), `"false"` otherwise. Requires Claude Code v2.1.169 or later
- `hook_matcher` (when `OTEL_LOG_TOOL_DETAILS=1`): the matcher string from the hook configuration, when one is set
- `plugin.name` (when `hook_source` is `"pluginHook"`): name of the contributing plugin. For plugins outside the official marketplace and built-in bundle the value is `"third-party"` unless `OTEL_LOG_TOOL_DETAILS=1`
- `plugin_id_hash` (when `hook_source` is `"pluginHook"`): deterministic hash of the plugin name and marketplace, sent only to your configured exporter. Lets you count distinct contributing plugins without recording their names. Claude Code computes it as described under the [plugin loaded event](#plugin-loaded-event)


[​](#hook-execution-start-event)

Hook execution start event

Logged when one or more hooks begin executing for a hook event. **Event Name**: `claude_code.hook_execution_start` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"hook_execution_start"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `hook_event`: Hook event type, such as `"PreToolUse"` or `"PostToolUse"`
- `hook_name`: Full hook name including matcher, such as `"PreToolUse:Write"`
- `num_hooks`: Number of matching hook commands
- `managed_only`: `"true"` when only managed-policy hooks are permitted
- `hook_source`: `"policySettings"` or `"merged"`
- `safe_mode`: `"true"` when the session was started with [`--safe-mode`](../02-Claude-Code-CLI/cli-reference.md), `"false"` otherwise. Requires Claude Code v2.1.169 or later
- `hook_definitions`: JSON-serialized hook configuration. Included only when both detailed beta tracing and `OTEL_LOG_TOOL_DETAILS=1` are enabled


[​](#hook-execution-complete-event)

Hook execution complete event

Logged when all hooks for a hook event have finished. **Event Name**: `claude_code.hook_execution_complete` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"hook_execution_complete"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `hook_event`: Hook event type
- `hook_name`: Full hook name including matcher
- `num_hooks`: Number of matching hook commands
- `num_success`: Count that completed successfully
- `num_blocking`: Count that returned a blocking decision
- `num_non_blocking_error`: Count that failed without blocking
- `num_cancelled`: Count cancelled before completion
- `total_duration_ms`: Wall-clock duration of all matching hooks
- `stdout_chars`: Total characters of stdout across the matching hooks that succeeded. Requires Claude Code v2.1.280 or later
- `additional_context_chars`: Total characters of `additionalContext` returned by the matching hooks. Requires Claude Code v2.1.280 or later
- `system_message_chars`: Total characters of `systemMessage` returned by the matching hooks. Requires Claude Code v2.1.280 or later
- `initial_user_message_chars`: Total characters of `initialUserMessage` returned by the matching hooks. Requires Claude Code v2.1.280 or later
- `num_outputs_persisted`: Number of hook outputs over the [10,000-character cap](../07-Hooks/hooks.md#json-output) that Claude Code saved to a file. Requires Claude Code v2.1.280 or later
- `managed_only`: `"true"` when only managed-policy hooks are permitted
- `hook_source`: `"policySettings"` or `"merged"`
- `safe_mode`: `"true"` when the session was started with [`--safe-mode`](../02-Claude-Code-CLI/cli-reference.md), `"false"` otherwise. Requires Claude Code v2.1.169 or later
- `hook_definitions`: JSON-serialized hook configuration. Included only when both detailed beta tracing and `OTEL_LOG_TOOL_DETAILS=1` are enabled


[​](#hook-plugin-metrics-event)

Hook plugin metrics event

Logged when an official-marketplace plugin hook emits per-invocation metrics. Only plugins installed from an official Anthropic marketplace can emit these. Third-party marketplace plugins and user-configured hooks don’t emit to this event. Use this event to monitor plugin behavior such as finding rates, costs, and durations from your own observability stack. **Event Name**: `claude_code.hook_plugin_metrics` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"hook_plugin_metrics"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `plugin_id`: plugin identifier in `<name>@<marketplace>` form
- `hook_event`: hook event type that emitted the metrics
- Up to 20 plugin-emitted metric keys. Names match `^[a-z][a-z0-9_]{0,39}$`. Values are Boolean or number.


[​](#compaction-event)

Compaction event

Logged when conversation compaction completes. **Event Name**: `claude_code.compaction` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"compaction"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `trigger`: `"auto"` or `"manual"`
- `success`: `"true"` or `"false"`
- `duration_ms`: Compaction duration
- `pre_tokens`: Approximate token count before compaction
- `post_tokens`: Approximate token count after compaction
- `error`: Error message when compaction failed
- `precompute_reuse`: Only set when `trigger` is `"manual"` or for an [idle compaction](../02-Claude-Code-CLI/settings-reference.md#idlecompaction), which reports `trigger` as `"auto"`. Auto-compaction can prepare a summary in the background before the context window fills, and this attribute records whether the compaction reused that prepared summary. `"hit"` means it was reused; `"miss_custom_instructions"`, `"miss_hook"`, and `"miss_not_ready"` give the reason a fresh summary was computed instead


[​](#subagent-completed-event)

Subagent completed event

Logged when a [subagent](../09-Agents-Patterns/sub-agents.md) finishes and returns its result to the conversation that started it. Use it to roll up tool use and run time by subagent type; for token or cost rollups, use the [token counter](#token-counter) and [cost counter](#cost-counter) filtered to `query_source` `"subagent"`, since this event’s `total_tokens` covers only the final request. The `"subagent"` category also counts requests from agent-based hooks, which emit no subagent event. **Event Name**: `claude_code.subagent_completed` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"subagent_completed"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `agent_type`: The subagent type. Built-in agent names and agents from official-marketplace plugins appear verbatim; other agent names are replaced with `"custom"` unless `OTEL_LOG_TOOL_DETAILS=1` is set
- `agent.source`: Where the agent definition came from: `built-in`, `plugin`, or the settings source that defined a custom agent, such as `userSettings` or `projectSettings`
- `is_built_in`: Whether the subagent is a built-in agent type
- `is_async`: Whether the subagent ran in the [background](../09-Agents-Patterns/sub-agents.md#run-subagents-in-foreground-or-background)
- `total_tokens`: The token footprint of the subagent’s final API request: that one request’s input, cache creation, cache read, and output tokens, roughly the subagent’s context size at completion. Not a sum across the run
- `total_tool_uses`: Number of tool calls the subagent made across the whole run
- `duration_ms`: Run time in milliseconds
- `model`: The model the subagent was resolved to run
- `final_model`: The model that produced the subagent’s final response, which differs from `model` after a mid-run switch such as a fallback
- `model_swapped`: Whether more than one model served the subagent’s requests
- `plugin_id_hash`, `plugin.name`: Present for plugin-provided agents. Official-marketplace plugin names appear verbatim; other plugin names are replaced with `"third-party"` unless `OTEL_LOG_TOOL_DETAILS=1` is set


[​](#feedback-survey-event)

Feedback survey event

Logged when a session quality survey is shown or answered. See [Session quality surveys](data-usage.md#session-quality-surveys) for what the surveys collect and how to control them. **Event Name**: `claude_code.feedback_survey` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"feedback_survey"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `event_type`: Survey lifecycle event, for example `"appeared"`, `"responded"`, or `"transcript_prompt_appeared"`
- `appearance_id`: Unique ID linking the events emitted for one survey instance
- `survey_type`: Which survey produced the event. `"session"` is the “How is Claude doing?” rating prompt
- `response`: The user’s selection on `responded` events
- `enabled_via_override`: `true` when [`CLAUDE_CODE_ENABLE_FEEDBACK_SURVEY_FOR_OTEL`](../02-Claude-Code-CLI/env-vars.md) is set. Emitted as a Boolean, not a string. Present on `session` survey events. Filter on this attribute to confirm the override is applied across a fleet


[​](#retention-sweep-event)

Retention sweep event

Logged once per run of the retention cleanup sweep, which deletes [session transcripts and other application data](../02-Claude-Code-CLI/claude-directory.md#cleaned-up-automatically) older than the [`cleanupPeriodDays`](../02-Claude-Code-CLI/settings-reference.md#cleanupperioddays) setting. Claude Code runs the sweep in the background at most once per session, and a run that deletes nothing still emits the event. If Claude Code ran the sweep in any session on the same machine in the last 24 hours, it delays this session’s sweep by at least 10 minutes, so a session that exits sooner emits nothing. When you run `claude -p` with `--bare`, Claude Code doesn’t run the sweep and emits nothing. Like every OTel event on this page, it goes only to the telemetry backend you configure. Requires Claude Code v2.1.227 or later. When Claude Code can’t safely determine the retention period, it pauses the sweep and emits the event with `result` set to `"skipped"` and a `skip_reason`. When [managed settings](server-managed-settings.md) set `cleanupPeriodDays`, the managed value pins the retention period and the sweep runs even when a settings file in a lower-priority scope is broken or invalid. When `managed-settings.json` itself can’t be read, Claude Code still pauses the sweep unless the [managed tier](managed-settings.md#how-claude-code-combines-managed-sources) supplies `cleanupPeriodDays` from elsewhere, such as server-managed settings or a `managed-settings.d/` drop-in beside the broken file. The deletion counter attributes are present only when `result` is `"complete"`. **Event Name**: `claude_code.retention_sweep` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"retention_sweep"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `result`: `"complete"` when the sweep ran, `"skipped"` when Claude Code paused it
- `period_days`: The `cleanupPeriodDays` value from merged settings, in days, or `30` when no source sets it. On skipped events, the value the sweep would have used, computed from the settings sources Claude Code could read
- `used_default`: `"true"` when no readable settings source sets `cleanupPeriodDays`, `"false"` otherwise. On complete events, `"true"` means the 30-day default applied
- `skip_reason`: Why Claude Code paused the sweep. Present only when `result` is `"skipped"`:
  - `"user_source_disabled"`: User settings are excluded, for example by the [`--setting-sources`](../02-Claude-Code-CLI/cli-reference.md#cli-flags) flag or the SDK’s [`settingSources`](../05-Agent-SDK/agent-sdk-typescript.md#options) option, and no enabled source provides `cleanupPeriodDays`
  - `"settings_unknowable"`: A settings file couldn’t be read or parsed, so `cleanupPeriodDays` or `desktopSessionCleanupPeriodDays` may be set to a value Claude Code can’t see
  - `"settings_invalid_key_set"`: Settings have validation errors and `cleanupPeriodDays` or `desktopSessionCleanupPeriodDays` is explicitly set, so falling back to the default could delete or keep files against that setting
- `transcripts_deleted`: Number of session transcripts, the top-level `~/.claude/projects/*/*.jsonl` files, that the sweep deleted
- `transcripts_exempted_desktop`: Number of transcripts past the retention period that the sweep kept under the [Claude Desktop and Cowork rule](../02-Claude-Code-CLI/claude-directory.md#cleaned-up-automatically). These don’t count toward `files_past_cutoff`. Requires Claude Code v2.1.248 or later
- `session_files_deleted`: Number of artifacts the session-files sweep deleted: transcripts plus per-session companion files such as sidecars, recordings, and tool results
- `artifacts_deleted`: Total items the sweep deleted across the data directories it covers, including the session files. Some sweeps count a whole removed directory tree as one item and a few cleanup passes don’t contribute to the counter, so treat the value as a floor rather than an exact file count
- `files_retained_fresh`: Files inspected and left in place because they’re still within the retention period. Only per-file sweeps count these, so the value is a floor; a nonzero value is the normal steady state
- `files_past_cutoff`: Files older than the retention period that the sweep failed to delete, for example because of a permission error or a file held open. The count also includes each stale folder the sweep finds under `skills/synced/` or `plugins/synced/`, whether or not it moves the folder to the trash. Apart from those folders, a value above zero means files outlived the configured retention period; zero isn’t proof that none did, because a failed removal of a whole directory counts toward `error_count` instead
- `error_count`: Number of errors the sweep encountered while listing or deleting files


[​](#managed-settings-resolved-event)

Managed settings resolved event

Logged with the [managed settings](managed-settings.md) a session resolved: once at session start, again when either the managed settings or the [policy helper](managed-settings.md#compute-the-policy-with-a-helper-program)’s state changes during the session, and when Claude Code refuses to start or ends the session for one of the reasons the `error.type` attribute lists. Use this event to find machines running on an unexpected managed source, machines whose policy helper is failing, and the reason a machine refused to start. Requires Claude Code v2.1.274 or later. By default, the event carries the managed sources and the policy helper’s state but not the settings themselves. To add the redacted `managed_settings.settings` attribute and the `managed_settings.resolved_sha256` digest, set `OTEL_LOG_MANAGED_SETTINGS=1`:

- Set it in the `env` block of managed settings, user settings, or `--settings`, or in the environment you launch Claude Code with. A value in project or local settings doesn’t turn it on, because a cloned repository can write them.
- Server-managed settings can set it without showing the [security approval dialog](server-managed-settings.md#security-approval-dialogs), because the variable only adds your organization’s own redacted policy to an event your organization already receives.

In an interactive session in a folder you haven’t [trusted](../02-Claude-Code-CLI/permissions.md#what-runs-before-you-trust-a-folder), Claude Code doesn’t export the refusal event. **Event Name**: `claude_code.managed_settings_resolved` **Attributes**:

- All [standard attributes](#standard-attributes)
- `event.name`: `"managed_settings_resolved"`
- `event.timestamp`: ISO 8601 timestamp
- `event.sequence`: per-process counter for ordering events, described under [Event correlation attributes](#event-correlation-attributes)
- `managed_settings.trigger`: `"startup"` for the session-start event, `"change"` when the managed settings or the policy helper’s state changed later in the session, or `"refused"` when a managed settings policy stopped the session. Claude Code sends a `change` event only when an attribute differs from the last event it sent, and a changed setting value counts even when `OTEL_LOG_MANAGED_SETTINGS` is off
- `error.type`: why Claude Code stopped the session. Present only on `refused` events:
  - `"helper_failed"`: a [policy helper run failed](../02-Claude-Code-CLI/settings-reference.md#helper-failures)
  - `"policy_invalid"`: the managed settings contain an error that stops Claude Code from starting, or an admin source failed to load for a reason other than a denied read, so Claude Code can’t check organization login or provider enforcement
  - `"provider_not_allowed"`: the session would use an API provider, or send a provider’s traffic to a host, that the managed [`allowedProviders`](../02-Claude-Code-CLI/settings-reference.md#allowedproviders) list doesn’t allow. Requires Claude Code v2.1.285 or later
  - `"consent_rejected"`: the user rejected the [security approval dialog](server-managed-settings.md#security-approval-dialogs) for server-managed settings
  - `"force_refresh_failed"`: the settings fetch that [`forceRemoteSettingsRefresh`](../02-Claude-Code-CLI/settings-reference.md#forceremotesettingsrefresh) requires failed
  - `"gateway_rejected"`: a [Claude apps gateway](claude-apps-gateway.md) answered the managed settings load with HTTP 403
  - `"version_below_minimum"`: this version of Claude Code is below [`requiredMinimumVersion`](../02-Claude-Code-CLI/settings-reference.md#requiredminimumversion) or above [`requiredMaximumVersion`](../02-Claude-Code-CLI/settings-reference.md#requiredmaximumversion)
  - `"_OTHER"`: the Claude apps gateway managed settings load failed for another reason
- `managed_settings.sources`: every managed source that delivers at least one [policy key](managed-settings.md#how-claude-code-combines-managed-sources), highest priority first, including sources whose keys don’t take effect under `first-wins`. Values are `"remote"`, `"plist"` or `"hklm"` for the MDM or OS-level policy, `"file"` for managed settings files and drop-ins, `"parent"` when an [embedding host](managed-settings.md#let-an-embedding-host-add-policy) supplies settings, and `"hkcu"` for the [Windows HKCU registry value](managed-settings.md#where-each-mechanism-stores-the-policy) when Claude Code [reads it](managed-settings.md#how-claude-code-combines-managed-sources). A source that carries only control keys, or that Claude Code couldn’t read, isn’t listed. Emitted as an array of strings, empty when no managed source delivers a policy key
- `managed_settings.source_behavior`: the [`managedSourcesBehavior`](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior) value Claude Code read, `"first-wins"` or `"merge"`. `"first-wins"` when no source sets the key
- `managed_settings.helper.state`: state of the policy helper that the selected MDM or file source configures:
  - `"ok"`: the helper’s output serves as the managed settings
  - `"bad_path"`, `"not_a_file"`, `"exit_nonzero"`, `"timed_out"`, `"oversize"`, `"parse_failed"`, `"envelope_invalid"`, or `"schema_rejected"`: the helper’s last run failed. [Helper failures](../02-Claude-Code-CLI/settings-reference.md#helper-failures) describes the cases
  - `"none"`: no helper is configured, or the source that configures it isn’t an MDM policy or managed settings file
- `managed_settings.helper.applied`: `"output"` while the helper’s own output serves as the managed settings, `"none"` when it doesn’t
- `managed_settings.helper.entry`: `"policyHelper"` when Claude Code selected a [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper). Absent when it selected no helper
- `managed_settings.helper.path`: the helper’s configured [`path`](../02-Claude-Code-CLI/settings-reference.md#policyhelper-path). Present whenever Claude Code selected a helper, whether or not `OTEL_LOG_MANAGED_SETTINGS` is set
- `managed_settings.resolved_sha256` (when `OTEL_LOG_MANAGED_SETTINGS=1`): SHA-256 of the resolved managed settings before redaction, serialized as JSON with keys sorted recursively and no whitespace. Machines with the same digest run the same policy. Claude Code sends the digest only with the opt-in because a short policy can be recovered by hashing guesses. Absent when no managed settings resolved, and on `refused` events
- `managed_settings.settings` (when `OTEL_LOG_MANAGED_SETTINGS=1`): the names and shape of the resolved managed settings as a JSON string, with the values redacted. Absent on `refused` events. Claude Code builds it from its settings schema:
  - A setting name the schema declares is exported, and a key it doesn’t declare is left out
  - Booleans, numbers, and string values the schema restricts to a fixed set of options, such as `permissions.defaultMode`, are exported as-is. `sandbox.network.httpProxyPort` and `sandbox.network.socksProxyPort` are exported as `"[REDACTED]"`
  - Every other string, such as `model`, `apiKeyHelper`, every `env` value, every URL, and every command, is exported as `"[REDACTED]"`
  - The entry names of maps, such as `env` variable names and plugin IDs, are exported as-is. A setting whose entries the schema doesn’t type, such as `vimInsertModeRemaps`, is exported as a single `"[REDACTED]"`, and `sandbox.ignoreViolations` is exported as a list of its path lists without the command patterns
  - A list keeps its length, with each entry redacted by the same rules
  - A `permissions.allow`, `permissions.deny`, or `permissions.ask` rule is exported as its tool name with the content redacted, such as `Read([REDACTED])`, when the tool is built into this version of Claude Code or is an `mcp__` reference such as `mcp__jira__create_issue`. Any other rule is exported as `"[REDACTED]"`
  - Hooks follow the same rules, so fixed-option and numeric fields such as `type` and `timeout` show, while each command, URL, `matcher`, and `if` condition is exported as `"[REDACTED]"`

  For example, managed settings with `apiKeyHelper`, two `env` variables, and a deny rule are exported as `{"apiKeyHelper":"[REDACTED]","env":{"HTTPS_PROXY":"[REDACTED]","CLAUDE_CODE_ENABLE_TELEMETRY":"[REDACTED]"},"permissions":{"deny":["Read([REDACTED])"]}}`. Claude Code cuts the value at 8 KB of UTF-8, and the cut value isn’t valid JSON
- `managed_settings.settings_truncated` (when `managed_settings.settings` is present): `true` when Claude Code cut `managed_settings.settings` at 8 KB, `false` otherwise. Emitted as a Boolean, not a string


[​](#interpret-metrics-and-events-data)

Interpret metrics and events data

The exported metrics and events support a range of analyses:


[​](#usage-monitoring)

Usage monitoring

| Metric                                                        | Analysis Opportunity                                                                                          |
|---------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------|
| `claude_code.token.usage`                                     | Break down by token [`type`](#token-counter), user, team, model, `skill.name`, `plugin.name`, or `agent.name` |
| `claude_code.session.count`                                   | Track adoption and engagement over time                                                                       |
| `claude_code.lines_of_code.count`                             | Measure productivity by tracking code additions and removals, broken down by model                            |
| `claude_code.commit.count` & `claude_code.pull_request.count` | Understand impact on development workflows                                                                    |


[​](#cost-monitoring)

Cost monitoring

The `claude_code.cost.usage` metric helps with:

- Tracking usage trends across teams or individuals
- Identifying high-usage sessions for optimization
- Attributing spend to specific skills, plugins, or subagent types via the `skill.name`, `plugin.name`, and `agent.name` attributes

Cost metrics are approximations. For official billing data, refer to your API provider (Claude Console, Amazon Bedrock, or Google Cloud’s Agent Platform).

Claude Code counts each streaming response toward the cost and token metrics exactly once, including when a gateway or proxy behind `ANTHROPIC_BASE_URL` streams usage progressively across multiple frames.


[​](#alerting-and-segmentation)

Alerting and segmentation

Common alerts to consider:

- Cost spikes
- Unusual token consumption
- High session volume from specific users

All metrics can be segmented by the [standard attributes](#standard-attributes). The `model` attribute is available on `claude_code.token.usage`, `claude_code.cost.usage`, and `claude_code.lines_of_code.count`. Per-model breakdowns of commits can only be approximated by joining against the token or cost metrics on `session.id`, since one session can span multiple models. Filter the token or cost side to rows where `query_source` is `"main"` so auxiliary and subagent requests don’t attribute the session’s commits to a model that didn’t make them.


[​](#detect-retry-exhaustion)

Detect retry exhaustion

Claude Code retries failed API requests internally and emits a single `claude_code.api_error` event only after it gives up, so the event itself is the terminal signal for that request. Intermediate retry attempts are not logged as separate events. The `attempt` attribute on the event records the number of attempts. `CLAUDE_CODE_MAX_RETRIES` defaults to 10 and is capped at 15; with [`CLAUDE_CODE_RETRY_WATCHDOG`](../02-Claude-Code-CLI/env-vars.md) set, the default is higher and there is no cap. When the request exhausts all retries on a transient error, `attempt` is at most one more than that effective limit: 11 by default. A lower value can still mean the retries ran out: `attempt` starts again from `1` each time Claude Code re-issues the request after a streaming failure. To distinguish a session that recovered from one that stalled, group events by `session.id` and check whether a later `api_request` event exists after the error.


[​](#event-analysis)

Event analysis

The event data describes each Claude Code interaction in detail: **Tool usage patterns**: analyze tool result events to identify:

- Most frequently used tools
- Tool success rates
- Average tool execution times
- Error patterns by tool type

**Performance monitoring**: track API request durations and tool execution times to identify performance bottlenecks.


[​](#map-input-tokens-to-opentelemetry-genai-semantic-conventions)

Map input tokens to OpenTelemetry GenAI semantic conventions

Claude Code exports input token counts as they appear in the API response’s usage block, so these values exclude tokens read from or written to the [prompt cache](../02-Claude-Code-CLI/prompt-caching.md):

- `input_tokens` on the [`claude_code.llm_request`](#span-attributes) span and the [`api_request`](#api-request-event) event
- The `"input"` type of the [`claude_code.token.usage`](#token-counter) metric

Claude Code doesn’t set `gen_ai.usage.*` attributes. The [OpenTelemetry GenAI semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai) say `gen_ai.usage.input_tokens` should include tokens read from and written to the cache. To compute that total:

- From the span or event: add `input_tokens`, `cache_read_tokens`, and `cache_creation_tokens`
- From the `claude_code.token.usage` metric: add its `"input"`, `"cacheRead"`, and `"cacheCreation"` types

The conventions also define separate attributes for cache reads and cache writes:

- `cache_read_tokens` maps to `gen_ai.usage.cache_read.input_tokens`
- `cache_creation_tokens` maps to `gen_ai.usage.cache_write.input_tokens`. Older versions of the conventions name the cache write attribute `gen_ai.usage.cache_creation.input_tokens`, so use the name your backend expects.


[​](#audit-security-events)

Audit security events

OpenTelemetry events are the audit data source for Claude Code activity. Every event carries identity attributes that tie tool calls, MCP activity, and permission decisions back to the user who triggered them. The OTLP logs exporter can deliver these events to any Security Information and Event Management (SIEM) platform with an OTLP receiver, or to an OpenTelemetry Collector that forwards to your SIEM.


[​](#attribute-actions-to-users)

Attribute actions to users

The [standard attributes](#standard-attributes) on each event include the authenticated user’s identity: `user.email`, `user.account_uuid`, `user.account_id`, and `organization.id` when signed in with a Claude account or, in a [cloud session](../02-Claude-Code-CLI/claude-code-on-the-web.md), when the session’s own credentials carry them, plus `user.id` and the per-session `session.id`. `user.id` is an installation-scoped identifier, except in sessions signed in to a [Claude apps gateway](claude-apps-gateway.md) through `/login`, where it is the IdP subject from the gateway-issued token. In a session a developer starts, MCP tool calls, Bash commands, and file edits are therefore attributed to that developer. Claude Code doesn’t act under a separate service account there; the identity recorded on each event is the developer’s own Claude account, or the developer’s IdP identity on a [Claude apps gateway](claude-apps-gateway.md) session. In Claude Tag channel sessions, Claude works as your organization’s [shared identity](../02-Claude-Code-CLI/cloud-environments.md#set-the-environment-a-claude-tag-channel-uses) instead. When Claude Code authenticates with a direct API key, or against Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry, there is no Claude account in the session and only `user.id` and `session.id` are populated. In these deployments, attach user identity yourself with `OTEL_RESOURCE_ATTRIBUTES`, set per user through the [managed settings](#administrator-configuration) file or a launch wrapper. Claude apps gateway sessions need none of this: see [Standard attributes](#standard-attributes) for the identity their exports carry.

```python
export OTEL_RESOURCE_ATTRIBUTES="enduser.id=jdoe@example.com,enduser.directory_id=S-1-5-21-..."
```


[​](#audit-mcp-activity)

Audit MCP activity

To capture MCP server activity with full call detail, enable the logs exporter and set `OTEL_LOG_TOOL_DETAILS=1`. Each MCP operation then produces structured events that carry the server name, tool name, and call arguments alongside the standard identity attributes:

| Event                   | What it records for MCP                                                                                                                                                                            |
|-------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `mcp_server_connection` | Server connect, disconnect, and connection failure with `server_name`, `transport_type`, `server_scope`, and error detail                                                                          |
| `tool_result`           | Each MCP tool call with `tool_name` and `mcp_server_scope`, a `tool_parameters` payload containing `mcp_server_name` and `mcp_tool_name`, and a `tool_input` payload containing the call arguments |
| `tool_decision`         | Whether the call was allowed or denied, whether the decision came from config, a hook, or the user, and a `tool_parameters` payload containing `mcp_server_name` and `mcp_tool_name`               |

Without `OTEL_LOG_TOOL_DETAILS`, these events drop the identifying detail:

- `tool_result`: keeps `mcp_server_scope` and a `tool_name` redacted to the literal `"mcp_tool"` for user-configured servers, omits argument content. For Claude Desktop’s built-in servers, in sessions Claude Desktop owns, it also keeps the `mcp_server_name`/`mcp_tool_name` pair inside `tool_parameters`, the same host-authored exception as `tool_decision`
- `tool_decision`: keeps `tool_source` and a `tool_name` redacted to the literal `"mcp_tool"` for user-configured servers, omits argument content. For Claude Desktop’s built-in servers, in sessions Claude Desktop owns, it also keeps the `mcp_server_name`/`mcp_tool_name` pair inside `tool_parameters`
- `mcp_server_connection`: omits `server_name` and the error message, but keeps `is_plugin`, `plugin_id_hash`, and `plugin.name`, with non-Anthropic plugin names redacted to the literal `"third-party"`, so plugin-provided servers remain distinguishable without detailed logging


[​](#map-security-questions-to-events)

Map security questions to events

When building detection rules, look up the signal you want to monitor and query your backend for the corresponding event and attributes:

| Signal                                                                                                                     | Event                                                                                                                                                                                                                                                                      | Key attributes                                                                                                                                                                                                                                   |
|----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Tool call allowed or denied, and where the decision came from, such as configuration, a hook, or the user                  | [`tool_decision`](#tool-decision-event)                                                                                                                                                                                                                                    | `decision`, `source`, `classifier_verdict`, `permission_mode`, `tool_name`; `tool_parameters` when you set `OTEL_LOG_TOOL_DETAILS=1`                                                                                                             |
| Which check refused a tool call or required a permission prompt for it                                                     | [`safety_event`](#safety-event) where `category` names the check, such as `"managed_policy"`, `"permission_rule"`, `"hook"`, or `"auto_mode_block"`. `decision` is `"reject"` for a call that didn’t run. Some calls [have no safety event](#calls-without-a-safety-event) | `category`, `decision`, `reason_code`, `rule_source`, `tool_name`, `tool_use_id`                                                                                                                                                                 |
| Auto mode classifier allowed or blocked a tool call, or Claude Code denied the call without a classifier verdict           | [`tool_decision`](#tool-decision-event) where `classifier_verdict` is present. For a denial without a verdict, `classifier_verdict` is `"no_verdict"` or `"unavailable"`                                                                                                   | `classifier_verdict`, `permission_mode`, `tool_name`; `classifier_category` when you set `OTEL_LOG_TOOL_DETAILS=1`                                                                                                                               |
| Permission mode escalation                                                                                                 | `permission_mode_changed`                                                                                                                                                                                                                                                  | `from_mode`, `to_mode`, `trigger`                                                                                                                                                                                                                |
| Which permission mode each tool call was checked in, including calls subagents make                                        | [`tool_decision`](#tool-decision-event)                                                                                                                                                                                                                                    | `permission_mode`, `session.id`                                                                                                                                                                                                                  |
| Policy hook blocked an action                                                                                              | `hook_execution_complete`                                                                                                                                                                                                                                                  | `hook_event`, `num_blocking`                                                                                                                                                                                                                     |
| Which tool call a hook refused, and which result or turn end a hook blocked                                                | [`safety_event`](#safety-event) where `category` is `"hook"` and `decision` is `"reject"` for a refused call, or `category` is `"hook_review"` for a blocked result or turn end                                                                                            | `category`, `decision`, `hook_event`; `tool_use_id` for a tool call                                                                                                                                                                              |
| Sandbox’s network proxy refused a connection or request from a Bash or PowerShell command                                  | [`safety_event`](#safety-event) where `sandbox_violation_count` is present and isn’t `"0"`                                                                                                                                                                                 | `sandbox_violation_count`, `category`, `sandbox_enabled`, `tool_use_id`                                                                                                                                                                          |
| Login, logout, and authentication failure                                                                                  | `auth`                                                                                                                                                                                                                                                                     | `action`, `success`, `error_category`                                                                                                                                                                                                            |
| MCP server connect or failure                                                                                              | `mcp_server_connection`                                                                                                                                                                                                                                                    | `status`, `server_name`, `is_plugin`, `error_code`                                                                                                                                                                                               |
| Plugin installed and its source                                                                                            | `plugin_installed`                                                                                                                                                                                                                                                         | `plugin.name`, `marketplace.name`, `marketplace.is_official`                                                                                                                                                                                     |
| Commands run and files touched                                                                                             | `tool_result` (executed) or `tool_decision` (rejected) with `OTEL_LOG_TOOL_DETAILS=1`                                                                                                                                                                                      | `tool_parameters`; `tool_input` (`tool_result` only)                                                                                                                                                                                             |
| Which managed settings sources a machine runs on, whether its policy helper is healthy, and why a machine refused to start | `managed_settings_resolved`                                                                                                                                                                                                                                                | `managed_settings.trigger`, `managed_settings.sources`, `managed_settings.source_behavior`, `managed_settings.helper.state`, `error.type`; `managed_settings.settings` and `managed_settings.resolved_sha256` with `OTEL_LOG_MANAGED_SETTINGS=1` |

Claude Code emits the raw event stream only. Anomaly detection, baselining, correlation across sessions, and alerting are the responsibility of your SIEM or observability backend.


[​](#map-egress-paths-to-managed-controls-and-events)

Map egress paths to managed controls and events

The table pairs paths that can carry session content off a machine, plus local retention, with the [managed settings](managed-settings.md) keys that restrict them and the events that record them. For what Claude Code itself sends to Anthropic, such as `/feedback` reports, see [Data usage](data-usage.md). The names link to their reference entries, which give the values and defaults.

| Path                                             | Managed controls                                                                                                                                                                                                                                                                                                                                                                                                                                                             | Events                                                                                                                                                          |
|--------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Bash and PowerShell commands                     | [`sandbox.enabled`](../02-Claude-Code-CLI/settings-reference.md#sandbox-enabled), [`sandbox.failIfUnavailable`](../02-Claude-Code-CLI/settings-reference.md#sandbox-failifunavailable), [`sandbox.allowUnsandboxedCommands`](../02-Claude-Code-CLI/settings-reference.md#sandbox-allowunsandboxedcommands), [`sandbox.network.allowManagedDomainsOnly`](../02-Claude-Code-CLI/settings-reference.md#sandbox-network-allowmanageddomainsonly), [`sandbox.network.allowedDomains`](../02-Claude-Code-CLI/settings-reference.md#sandbox-network-alloweddomains) | [`tool_decision`](#tool-decision-event), [`tool_result`](#tool-result-event)                                                                                    |
| MCP servers                                      | [`allowedMcpServers`](../02-Claude-Code-CLI/settings-reference.md#allowedmcpservers), [`allowManagedMcpServersOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedmcpserversonly), [`deniedMcpServers`](../02-Claude-Code-CLI/settings-reference.md#deniedmcpservers), [`managed-mcp.json`](../06-MCP-Tools/General/managed-mcp.md)                                                                                                                                                                                                 | [`mcp_server_connection`](#mcp-server-connection-event), `tool_decision`, `tool_result`                                                                         |
| Hooks                                            | [`allowManagedHooksOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedhooksonly), [`allowedHttpHookUrls`](../02-Claude-Code-CLI/settings-reference.md#allowedhttphookurls)                                                                                                                                                                                                                                                                                                                       | [`hook_registered`](#hook-registered-event), [`hook_execution_start`](#hook-execution-start-event), [`hook_execution_complete`](#hook-execution-complete-event) |
| Plugins                                          | [`strictKnownMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#strictknownmarketplaces), [`disableSideloadFlags`](../02-Claude-Code-CLI/settings-reference.md#disablesideloadflags), [`syncClaudeAiPlugins`](../02-Claude-Code-CLI/settings-reference.md#syncclaudeaiplugins), [`syncClaudeAiSkills`](../02-Claude-Code-CLI/settings-reference.md#syncclaudeaiskills)                                                                                                                                                               | [`plugin_installed`](#plugin-installed-event), [`plugin_loaded`](#plugin-loaded-event)                                                                          |
| [WebFetch](../02-Claude-Code-CLI/permissions.md#webfetch)        | [`permissions.deny`](../02-Claude-Code-CLI/settings-reference.md#permissions-deny), [`allowManagedPermissionRulesOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedpermissionrulesonly)                                                                                                                                                                                                                                                                                                         | `tool_decision`, `tool_result`                                                                                                                                  |
| Tools that upload to claude.ai, such as Artifact | `permissions.deny`, [`enableArtifact`](../02-Claude-Code-CLI/settings-reference.md#enableartifact)                                                                                                                                                                                                                                                                                                                                                                                           | `tool_decision`, `tool_result`                                                                                                                                  |
| Remote Control                                   | [`disableRemoteControl`](../02-Claude-Code-CLI/settings-reference.md#disableremotecontrol)                                                                                                                                                                                                                                                                                                                                                                                                   | No dedicated event                                                                                                                                              |
| Local transcript retention                       | [`cleanupPeriodDays`](../02-Claude-Code-CLI/settings-reference.md#cleanupperioddays)                                                                                                                                                                                                                                                                                                                                                                                                         | [`retention_sweep`](#retention-sweep-event)                                                                                                                     |

The `allowedHttpHookUrls`, `managed-mcp.json`, and hook event entries need more than the table shows:

- **`allowedHttpHookUrls`**: entries merge across settings files, so a developer can add to an empty managed list. `allowManagedHooksOnly` decides which hooks run
- **`managed-mcp.json`**: to turn MCP off, see [Disable MCP entirely](../06-MCP-Tools/General/managed-mcp.md#disable-mcp-entirely). To confirm Claude Code reads the file, see [Validate the configuration](../06-MCP-Tools/General/managed-mcp.md#validate-the-configuration)
- **Hook events**: Claude Code logs `hook_execution_start` and `hook_execution_complete` once per hook event, covering every matching hook. `OTEL_LOG_TOOL_DETAILS=1` on its own doesn’t record an HTTP hook’s URL. The hook configuration appears only in `hook_definitions`, which also needs detailed beta tracing

`OTEL_LOG_TOOL_DETAILS=1` adds command strings, server and tool names, and tool input to these events. That detail can contain the same sensitive content as the session itself, so enable it only when your collector is approved to hold that content.


[​](#check-the-retention-sweep)

Check the retention sweep

To give every machine the same retention period, set [`cleanupPeriodDays`](../02-Claude-Code-CLI/settings-reference.md#cleanupperioddays) in [managed settings](managed-settings.md). To check that machines run the sweep with that value, collect the [`retention_sweep`](#retention-sweep-event) event. `period_days` and the counters are strings, so cast them to numbers before you compare them.

| What a machine reports                                                       | What it means                                                                                                                               |
|------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| `result` is `"skipped"`                                                      | Claude Code paused the sweep. `skip_reason` gives the cause                                                                                 |
| `used_default` is `"true"`, or `period_days` differs from your managed value | The machine isn’t applying your managed `cleanupPeriodDays`                                                                                 |
| `error_count` is above zero                                                  | The sweep hit errors while it listed or deleted files, so data past the retention period can remain                                         |
| `files_past_cutoff` is above zero                                            | The sweep failed to delete files past the retention period, or it found stale synced skills and plugins folders. Read it with `error_count` |
| No event                                                                     | Not a failure on its own                                                                                                                    |

A machine that works as intended can go without an event for reasons such as these:

- **Nobody starts Claude Code**: no sweep runs, and the machine keeps its data until the next launch
- **A session stays open**: Claude Code runs the sweep at most once per session
- **A session ends early**: a sweep that hasn’t finished when the session exits emits nothing, and neither does one that stops on an unexpected error

The sweep doesn’t cover every path. [Kept until you delete them](../02-Claude-Code-CLI/claude-directory.md#kept-until-you-delete-them) lists what stays, and [Clear local data](../02-Claude-Code-CLI/claude-directory.md#clear-local-data) shows how to remove it.


[​](#send-events-to-a-siem)

Send events to a SIEM

Point `OTEL_EXPORTER_OTLP_LOGS_ENDPOINT` at your SIEM’s OTLP receiver, or at an OpenTelemetry Collector that forwards to your SIEM’s native ingest API. The following managed-settings example exports events only, with full tool detail enabled for MCP and Bash auditing:

```python
{
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_LOG_TOOL_DETAILS": "1",
    "OTEL_EXPORTER_OTLP_LOGS_PROTOCOL": "http/protobuf",
    "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT": "https://siem.example.com:4318/v1/logs",
    "OTEL_EXPORTER_OTLP_HEADERS": "Authorization=Bearer your-siem-token"
  }
}
```

To confirm events arrive, submit a prompt in a session running under this configuration and check your SIEM for the `claude_code.user_prompt` event. If nothing arrives, start Claude Code with `claude --debug-file <path>` and check that log for `[3P telemetry]` export errors.


[​](#backend-considerations)

Backend considerations

Your choice of metrics, logs, and traces backends determines the types of analyses you can perform:


[​](#backends-for-metrics)

Backends for metrics

- **Time series databases**: Rate calculations, aggregated metrics
- **Columnar stores**: Complex queries, unique user analysis
- **Full-featured observability platforms**: Advanced querying, visualization, alerting


[​](#backends-for-events-and-logs)

Backends for events and logs

- **Log aggregation systems**: Full-text search, log analysis
- **Columnar stores**: Structured event analysis
- **Full-featured observability platforms**: Correlation between metrics and events


[​](#backends-for-traces)

Backends for traces

Choose a backend that supports distributed trace storage and span correlation:

- **Distributed tracing systems**: Span visualization, request waterfalls, latency analysis
- **Full-featured observability platforms**: Trace search and correlation with metrics and logs

For organizations requiring Daily/Weekly/Monthly Active User (DAU/WAU/MAU) metrics, consider backends that support efficient unique value queries.


[​](#service-information)

Service information

All metrics and events are exported with the following resource attributes:

- `service.name`: `claude-code` for terminal sessions, `claude-code-desktop` for local sessions started from the Code tab in the [Claude Desktop app](../16-Mobile-Desktop/desktop.md)
- `service.version`: Current Claude Code version, or the Desktop app version for local Code tab sessions
- `os.type`: Operating system type (for example, `linux`, `darwin`, `windows`)
- `os.version`: Operating system version string
- `host.arch`: Host architecture (for example, `amd64`, `arm64`)
- `wsl.version`: WSL version number (only present when running on Windows Subsystem for Linux)
- Meter Name: `com.anthropic.claude_code`

If your collector pipelines or dashboards filter on `service.name = claude-code`, add `claude-code-desktop` to the filter to also capture telemetry from local Code tab sessions.


[​](#roi-measurement-resources)

ROI measurement resources

For a comprehensive guide on measuring return on investment for Claude Code, including telemetry setup, cost analysis, productivity metrics, and automated reporting, see the [Claude Code ROI Measurement Guide](https://github.com/anthropics/claude-code-monitoring-guide). This repository provides ready-to-use Docker Compose configurations, Prometheus and OpenTelemetry setups, and templates for generating productivity reports integrated with tools like Linear.


[​](#security-and-privacy)

Security and privacy

- OpenTelemetry export to your backend is opt-in and requires explicit configuration. For Anthropic’s separate operational telemetry and how to disable it, see [Data usage](data-usage.md#telemetry-services)
- Raw file contents and code snippets are not included in metrics or events. Trace spans are a separate data path: see the `OTEL_LOG_TOOL_CONTENT` bullet below
- When authenticated via OAuth, `user.email` is included in telemetry attributes, sent only to the OTel endpoint you configure, never to Anthropic. If this is a concern for your organization, work with your telemetry backend to filter or redact this field
- User prompt content is not collected by default. Only prompt length is recorded. To include prompt content, set `OTEL_LOG_USER_PROMPTS=1`. When enabled:
  - `user_prompt` events carry the prompt text in two attributes, `prompt` and [`prompt_text`](#user-prompt-event). If you drop or mask the event’s prompt text by attribute name in your collector, name both attributes in the rule This OpenTelemetry Collector `attributes` processor deletes both attributes in the pipelines that list it:

    ``` shiki
    processors:
      attributes/drop-prompt-text:
        actions:
          - key: prompt
            action: delete
          - key: prompt_text
            action: delete
    ```
  - With [tracing](#traces-beta) on, the `claude_code.interaction` span carries the prompt text in its `user_prompt` attribute
  - Under detailed beta tracing, spans also carry the new user messages, tool results, and system reminders sent with each request, system prompt text, and model output. [Content attributes under detailed beta tracing](#new-context-gates) lists each attribute. The `claude_code.system_prompt` event carries the complete system prompt
- Assistant response text is not collected by default. Only response length is recorded. To include response text, set `OTEL_LOG_ASSISTANT_RESPONSES=1`. Like all OpenTelemetry data from Claude Code, the response text is sent only to the OTel endpoint you configure, never to Anthropic. When this variable is unset, `OTEL_LOG_USER_PROMPTS` is used as a fallback, so set `OTEL_LOG_ASSISTANT_RESPONSES=0` if you want prompt content without response content in events. Under detailed beta tracing, the `claude_code.llm_request` span still carries model output in [`response.model_output`](#new-context-gates), which follows `OTEL_LOG_USER_PROMPTS` rather than this variable
- Tool input arguments and parameters are not logged by default. To include them, set `OTEL_LOG_TOOL_DETAILS=1`. For Claude Desktop’s built-in servers, in sessions Claude Desktop owns, `tool_decision` and `tool_result` carry the `mcp_server_name`/`mcp_tool_name` pair, host-authored names rather than argument content, even with the flag off. This data is sent only to the OTEL endpoint you configure, never to Anthropic. Arguments may still contain sensitive values, so configure your telemetry backend to filter or redact these attributes as needed. When enabled:
  - `tool_result` and `tool_decision` events include a `tool_parameters` attribute with Bash commands, MCP server and tool names, and skill names. Fields such as `full_command` are emitted untruncated
  - `tool_result` events additionally include a `tool_input` attribute with file paths, URLs, search patterns, and other arguments. Individual values over 512 characters are truncated and the total is bounded to ~4 K characters
  - `user_prompt` events include the verbatim `command_name` for custom, plugin, and MCP commands
  - The [cost and token counters](#cost-counter) and the `api_request`, `api_error`, and `api_refusal` events carry real agent, skill, plugin, and MCP server and tool names in their attribution attributes
  - The `claude_code.tool` span carries input-derived attributes such as `file_path`. Under detailed beta tracing it also carries a [`tool_input`](#new-context-gates) attribute
- Tool content is not logged in trace spans by default. To include it, set `OTEL_LOG_TOOL_CONTENT=1`. The `claude_code.tool` span then carries a [`tool.output` span event](#tool-output-span-event) with raw file contents, Bash command output, and what MCP tools, WebFetch, and WebSearch return, truncated at the content limit (60 KB by default) per attribute. Results from MCP tools, WebFetch, and WebSearch require Claude Code v2.1.283 or later. Tool content also reaches spans through [`new_context`, whose gate differs per span](#new-context-gates). Configure your telemetry backend to filter or redact these attributes as needed
- Raw Anthropic Messages API request and response bodies are not logged by default. To include them, set `OTEL_LOG_RAW_API_BODIES` in your shell, user settings, or managed settings. It’s ignored in [project and local settings](../02-Claude-Code-CLI/settings-reference.md#variables-claude-code-ignores-in-env). The bodies contain the full conversation history, including the system prompt, every prior user and assistant turn, and tool results, so enabling this implies consent to everything the other `OTEL_LOG_*` content flags would reveal. Claude Code always redacts Claude’s extended-thinking content from these bodies, regardless of other settings. The value you set determines how Claude Code delivers the bodies:
  - With `=1`, Claude Code emits `api_request_body` and `api_response_body` log events for each API call. The events’ `body` attribute carries the JSON-serialized payload, truncated at the content limit (60 KB by default)
  - With `=file:<dir>`, Claude Code writes untruncated bodies to `.request.json` and `.response.json` files under that directory, and the events carry a `body_ref` path instead of the inline body. Ship the directory with a log collector or sidecar rather than through the telemetry stream. For each successful response, Claude Code also appends one line to `index.jsonl` in that directory, linking the response file to the request file that produced it and to the transcript message it became. Each line holds no message content, and the [API response body event](#api-response-body-event) section lists its fields. The index file requires Claude Code v2.1.274 or later


[​](#monitor-claude-code-on-amazon-bedrock)

Monitor Claude Code on Amazon Bedrock

For detailed Claude Code usage monitoring guidance for Amazon Bedrock, see [Claude Code Monitoring Implementation (Amazon Bedrock)](https://github.com/aws-solutions-library-samples/guidance-for-claude-code-with-amazon-bedrock/blob/main/assets/docs/MONITORING.md).

---

## Enterprise network configuration - Claude Code Docs

Source: https://code.claude.com/docs/en/network-config
Path: 13-Enterprise-Admin/network-config.md

## On this page

- [Proxy configuration](#proxy-configuration)
  - [Environment variables](#environment-variables)
  - [Basic authentication](#basic-authentication)
- [CA certificate store](#ca-certificate-store)
- [Custom CA certificates](#custom-ca-certificates)
- [mTLS authentication](#mtls-authentication)
- [Verify your configuration](#verify-your-configuration)
- [Apply network settings to background agents](#apply-network-settings-to-background-agents)
  - [Set network variables in settings, not the shell](#set-network-variables-in-settings-not-the-shell)
  - [Configure a corporate launcher as a setting](#configure-a-corporate-launcher-as-a-setting)
- [Streaming idle watchdogs](#streaming-idle-watchdogs)
- [Network access requirements](#network-access-requirements)
  - [Organization IP allowlists and proxy egress](#organization-ip-allowlists-and-proxy-egress)
  - [GitHub allow lists and firewalls](#github-allow-lists-and-firewalls)
  - [Desktop and claude.ai](#desktop-and-claude-ai)
  - [Third-party hosts for artifact fonts and libraries](#third-party-hosts-for-artifact-fonts-and-libraries)
- [Additional resources](#additional-resources)

Deployment

# Enterprise network configuration

Copy pageCopy page

Configure Claude Code for enterprise environments with proxy servers, custom Certificate Authorities (CA), and mutual Transport Layer Security (mTLS) authentication.

Copy pageCopy page

Claude Code supports various enterprise network and security configurations through environment variables. This includes routing traffic through corporate proxy servers, trusting custom Certificate Authorities (CA), and authenticating with mutual Transport Layer Security (mTLS) certificates for enhanced security. Set these environment variables before you launch Claude Code. Variables exported in your shell are read once at startup, so a running session doesn’t pick up later changes to your shell environment.

All environment variables shown on this page can also be configured in [`settings.json`](../02-Claude-Code-CLI/settings.md).


[​](#proxy-configuration)

Proxy configuration


[​](#environment-variables)

Environment variables

Claude Code respects standard proxy environment variables. In Claude Desktop sessions where the app manages the provider connection, Claude Code reads them only from managed settings and `~/.claude/settings.json`; see [mTLS authentication](#mtls-authentication) for the scope rules.

```python
# HTTPS proxy (recommended)
export HTTPS_PROXY=https://proxy.example.com:8080

# HTTP proxy (if HTTPS not available)
export HTTP_PROXY=http://proxy.example.com:8080

# Bypass proxy for specific requests - space-separated format
export NO_PROXY="localhost 192.168.1.1 example.com .example.com"
# Bypass proxy for specific requests - comma-separated format
export NO_PROXY="localhost,192.168.1.1,example.com,.example.com"
# Bypass proxy for all requests
export NO_PROXY="*"
```

Lowercase variants also work, and Claude Code uses the first one that’s set in the order `https_proxy`, `HTTPS_PROXY`, `http_proxy`, `HTTP_PROXY`. Claude Code never sends its WebSocket connections to `localhost`, `::1`, or `127.0.0.0/8` through the proxy, so you don’t need a loopback entry in `NO_PROXY` for them.

Claude Code does not support SOCKS proxies.


[​](#basic-authentication)

Basic authentication

If your proxy requires basic authentication, include credentials in the proxy URL:

```python
export HTTPS_PROXY=http://username:password@proxy.example.com:8080
```

Avoid hardcoding passwords in scripts. Use environment variables or secure credential storage instead.

For proxies requiring advanced authentication (NTLM, Kerberos, etc.), consider using an LLM Gateway service that supports your authentication method.


[​](#ca-certificate-store)

CA certificate store

By default, Claude Code trusts both its bundled Mozilla CA certificates and your operating system’s certificate store. Reading the OS store requires a runtime with `tls.getCACertificates`: the native installer always has it, and npm installs need Node 22.15 or later. On older Node versions, only the bundled set and `NODE_EXTRA_CA_CERTS` apply. Enterprise TLS-inspection proxies work without additional configuration when their root certificate is installed in the OS trust store and the runtime can read it. If such a proxy mishandles gzip-compressed request bodies, set [`CLAUDE_CODE_GZIP_REQUEST_BODIES=0`](../02-Claude-Code-CLI/env-vars.md) to turn off compression for Claude API, telemetry, and artifact publish requests. `CLAUDE_CODE_CERT_STORE` accepts a comma-separated list of sources. Recognized values are `bundled` for the Mozilla CA set shipped with Claude Code and `system` for the operating system trust store. The default is `bundled,system`. To trust only the bundled Mozilla CA set:

```python
export CLAUDE_CODE_CERT_STORE=bundled
```

To trust only the OS certificate store:

```python
export CLAUDE_CODE_CERT_STORE=system
```

`CLAUDE_CODE_CERT_STORE` has no dedicated `settings.json` schema key. Set it via the `env` block in `~/.claude/settings.json` or directly in the process environment.


[​](#custom-ca-certificates)

Custom CA certificates

If your enterprise environment uses a custom CA, configure Claude Code to trust it directly:

```python
export NODE_EXTRA_CA_CERTS=/path/to/ca-cert.pem
```


[​](#mtls-authentication)

mTLS authentication

For enterprise environments requiring client certificate authentication:

```python
# Client certificate for authentication
export CLAUDE_CODE_CLIENT_CERT=/path/to/client-cert.pem

# Client private key
export CLAUDE_CODE_CLIENT_KEY=/path/to/client-key.pem

# Optional: Passphrase for encrypted private key
export CLAUDE_CODE_CLIENT_KEY_PASSPHRASE="your-passphrase"
```

Claude Code reads the certificate and key files at startup and re-reads them each time it applies settings, such as when your organization changes the `env` block in [managed settings](server-managed-settings.md) mid-session. To rotate the certificate and key, replace the files at the same paths. Claude Code picks up the replacement in a running session without a restart. When an API request fails with a connection-level error, such as a connection reset or a TLS handshake error, it re-reads both files and retries the request with the new pair. Before v2.1.232, Claude Code didn’t re-read on connection errors, so it kept the pair it had already loaded until it next applied settings or you restarted. Claude Code re-reads the files in response to failed requests, not by watching them for changes:

- **Timing**: Claude Code does nothing at the moment you replace the files. It presents the new pair on the retry after a qualifying failure, or on the next request after it applies settings, whichever comes first.
- **Gateway rejections**: Claude Code re-reads when your gateway resets the connection or rejects the TLS handshake after it stops accepting the old pair. It doesn’t re-read when the gateway completes the handshake and answers with an HTTP error. In that case, Claude Code loads the new pair when it next applies settings or when you restart it.
- **Half-written rotations**: when Claude Code re-reads while your rotation is mid-write, such as reading a certificate and key that don’t match each other, it keeps the previous pair and re-reads on the next failure.
- **OTLP telemetry exporters**: Claude Code keeps the certificate the [exporters](monitoring-usage.md#mtls-authentication) loaded at first use, so restart Claude Code for a rotated certificate to reach your telemetry collector.
- **Turn the reload off**: set [`CLAUDE_CODE_DISABLE_MTLS_RELOAD_ON_STALE_CONNECTION=1`](../02-Claude-Code-CLI/env-vars.md#variables) to turn off the connection-error re-read. Claude Code then picks up rotated files only when it next applies settings or at the next startup.

To confirm Claude Code picked up a rotation, [start the session with debug logging](#verify-your-configuration) and look for `Stale connection — reloaded rotated mTLS client material` in the log. Claude Code doesn’t log this line when it picks up the rotation while applying settings instead, so a missing line alone doesn’t mean the rotation failed. Replace the files before the current pair expires so Claude Code doesn’t load an already-expired pair at the next startup. In [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md), the hosting environment manages the connection to the API, so Claude Code ignores the following variables when they come from a settings file `env` block:

- `CLAUDE_CODE_CLIENT_CERT`
- `CLAUDE_CODE_CLIENT_KEY`
- `CLAUDE_CODE_CLIENT_KEY_PASSPHRASE`
- `NODE_EXTRA_CA_CERTS`
- `NODE_TLS_REJECT_UNAUTHORIZED`
- `CLAUDE_CODE_OAUTH_SCOPES`

Claude Code notes each ignored key in the session’s debug log. In [Claude Desktop](../16-Mobile-Desktop/desktop.md) sessions where the app manages the provider connection, such as the Code tab on a [third-party provider](../02-Claude-Code-CLI/third-party-integrations.md) and Cowork sessions, Claude Code reads these variables and the proxy variables `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` only from [managed settings](managed-settings.md) and `~/.claude/settings.json`: it ignores them in a repository’s own settings files, so a checked-out repository can’t redirect the TLS or proxy path of a session whose credentials come from the app. In a local, SSH, or WSL Code tab session signed in through claude.ai, the app doesn’t manage the connection, and Claude Code reads these variables from every settings scope, like any terminal session; [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) follow the cloud-session rules above wherever you start them. Before v2.1.217, Claude Code ignored these variables in every settings file when the app managed the connection.


[​](#verify-your-configuration)

Verify your configuration

You usually find out about a wrong proxy address or a bad certificate path from a [connection or certificate error](../02-Claude-Code-CLI/errors.md#network-and-connection-errors) on a later request, since Claude Code doesn’t validate most of these settings when it reads them. The one setting it checks at startup is the proxy URL: when it can’t parse the value, such as one missing the `http://` scheme, Claude Code stops launch with an error naming the variable to fix. To confirm your configuration loaded before you send a request, start Claude Code with debug logging:

```python
claude --debug
```

Debug output goes to `~/.claude/debug/<session-id>.txt` rather than the terminal, or to a path you set with `--debug-file <path>`. In the log, look for the lines that confirm each file loaded:

```python
CA certs: Appended extra certificates from NODE_EXTRA_CA_CERTS (/etc/ssl/certs/corp-ca.pem)
mTLS: Loaded client certificate from CLAUDE_CODE_CLIENT_CERT
mTLS: Loaded client key from CLAUDE_CODE_CLIENT_KEY
```

If Claude Code can’t read one of these files, the log shows a `Failed to read` or `Failed to load` line with the reason instead. You can also run `/status` in an interactive session and check these rows:

- **Proxy**: shows the active proxy URL, and marks a value it can’t parse as invalid and ignored.
- **mTLS client cert** and **mTLS client key**: appear only when the files loaded, so a missing row means the load failed and the debug log has the reason.
- **Additional CA cert(s)**: shows the `NODE_EXTRA_CA_CERTS` path without checking that the file loaded, so confirm this one in the debug log.


[​](#apply-network-settings-to-background-agents)

Apply network settings to background agents

[Background agents](../09-Agents-Patterns/agent-view.md) don’t run inside the terminal that dispatched them. A per-user supervisor process starts on demand, outlives your shell, and hosts every `claude agents`, `--bg`, and `/background` session. See [How background sessions are hosted](../09-Agents-Patterns/agent-view.md#how-background-sessions-are-hosted). This changes how the configuration on this page reaches those sessions.


[​](#set-network-variables-in-settings-not-the-shell)

Set network variables in settings, not the shell

The supervisor is one process shared by every terminal. It inherits the environment of whichever shell starts it first. If you export a proxy, CA path, or mTLS variable only in your shell, it reaches background agents when that shell happened to cold-start the supervisor, and silently doesn’t when a different shell did. Put the same variables in the `env` block of `~/.claude/settings.json` or [managed settings](../02-Claude-Code-CLI/settings.md) instead. Every variable on this page can be set there, and settings are the only configuration that reaches every background session on every machine.


[​](#configure-a-corporate-launcher-as-a-setting)

Configure a corporate launcher as a setting

Some organizations require every Claude Code process to start through a corporate launcher that applies sandboxing, network controls, or credential injection. The supervisor and its workers start Claude Code from a fixed path rather than by looking up `claude` on `PATH`, so every background agent bypasses a wrapper you place earlier on `PATH`. Set the [`processWrapper`](../02-Claude-Code-CLI/settings-reference.md#processwrapper) setting to prefix the supervisor, its workers, and the other background processes listed under [What the launcher covers](corporate-launcher.md#what-the-launcher-covers) with your launcher. The equivalent [`CLAUDE_CODE_PROCESS_WRAPPER`](../02-Claude-Code-CLI/env-vars.md) environment variable takes precedence when both are set, and it is subject to the same rule: deliver it through managed settings or `~/.claude/settings.json`, not a shell export. [Run Claude Code behind a corporate launcher](corporate-launcher.md) covers the contract the launcher must satisfy, what it does and doesn’t reach, and how to roll it out.

An already-running supervisor keeps the launch configuration it started with. After deploying the launcher setting, run [`claude daemon stop --any`](../09-Agents-Patterns/agent-view.md#the-supervisor-process) so the next `claude agents` or `--bg` starts a supervisor that honors it.


[​](#streaming-idle-watchdogs)

Streaming idle watchdogs

Claude Code runs four independent timers that abort a streaming model response when it goes quiet, so a dead connection fails and retries instead of hanging. A request that gets no response headers at all ends in one of two ways:

- **Where the first-byte deadline runs**: the first attempt ends no later than that deadline. To change it, set `CLAUDE_STREAM_FIRST_BYTE_TIMEOUT_MS`. For the retry’s wait, see [No response from API](../02-Claude-Code-CLI/errors.md#no-response-from-api).
- **On the other connections**: the request ends after 5 to 6 minutes, when the body idle timeout fires, unless you turned that timeout off. To end the request sooner, lower `API_TIMEOUT_MS`. In a [Claude apps gateway](claude-apps-gateway.md) session, you can instead end the first attempt sooner by [turning on the first-byte deadline](claude-apps-gateway-deploy.md#retry-an-unanswered-request-sooner-with-the-first-byte-deadline).

| Timer                | Aborts when                                                                                                                                                                                                                                                                                                                                                                                                                                            | Runs on                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        | Default timeout                                                                                                                                                                                                |
|:---------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| First-byte deadline  | No response headers arrive after Claude Code sends the request                                                                                                                                                                                                                                                                                                                                                                                         | Direct Anthropic API and [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md), including through an HTTPS proxy, but not when `ANTHROPIC_BASE_URL` or `ANTHROPIC_AWS_BASE_URL` routes them through a [gateway](gateways.md). Opt-in for a [Claude apps gateway session](claude-apps-gateway-deploy.md#retry-an-unanswered-request-sooner-with-the-first-byte-deadline), and on Amazon Bedrock with `CLAUDE_ENABLE_BYTE_WATCHDOG_BEDROCK=1`; doesn’t run on Google Cloud’s Agent Platform or Microsoft Foundry | 180 seconds on the direct Anthropic API and 300 seconds on Claude Platform on AWS or Amazon Bedrock, plus one second per 32KB of request body                                                                  |
| Event-level watchdog | No response events parse. Where the byte-level watchdog runs on a connection other than Amazon Bedrock, arriving bytes, including keep-alive pings, also reset this watchdog, for a limited time without a parsed event. That time is about five minutes when bytes arrive at least every 10 seconds. On a Claude apps gateway’s 15-second pings it is about 7.5 minutes, so a pings-only stream ends after about 12.5 minutes at the default timeout. | Every provider                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 | 300 seconds                                                                                                                                                                                                    |
| Byte-level watchdog  | No bytes arrive on the wire, including SSE keep-alive pings                                                                                                                                                                                                                                                                                                                                                                                            | Direct Anthropic API, [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md), and [gateway](gateways.md) connections, including a custom `ANTHROPIC_BASE_URL`. Opt-in on Amazon Bedrock `vnd.amazon.eventstream` responses with `CLAUDE_ENABLE_BYTE_WATCHDOG_BEDROCK=1`; doesn’t run on Google Cloud’s Agent Platform or Microsoft Foundry                                                                                                                                                                            | 180 seconds on the direct Anthropic API. 300 seconds on Claude Platform on AWS, on Amazon Bedrock, in a [Claude apps gateway](claude-apps-gateway.md) session, and through a custom `ANTHROPIC_BASE_URL` |
| Body idle timeout    | No bytes arrive for 5 minutes, counting the wait for response headers                                                                                                                                                                                                                                                                                                                                                                                  | Providers other than the direct Anthropic API, Claude Platform on AWS, and Amazon Bedrock with `CLAUDE_ENABLE_BYTE_WATCHDOG_BEDROCK=1` set, unless [`API_FORCE_IDLE_TIMEOUT`](../02-Claude-Code-CLI/env-vars.md) changes that                                                                                                                                                                                                                                                                                                                  | 5 minutes                                                                                                                                                                                                      |

If you set `CLAUDE_ENABLE_BYTE_WATCHDOG_BEDROCK=1`, the byte-level watchdog replaces the body idle timeout on Bedrock rather than running alongside it. `CLAUDE_STREAM_IDLE_TIMEOUT_MS` then also governs how long a Bedrock stream may stay silent before Claude Code treats the connection as dead, within the limits listed below. Arriving bytes still don’t reset the event-level watchdog on Bedrock. With debug logging on, each Bedrock stream then logs a debug message that starts with `wire-heartbeat: _chunkTimes absent`. Configure the timers with these variables, each detailed in the [environment variables reference](../02-Claude-Code-CLI/env-vars.md):

- `CLAUDE_ENABLE_STREAM_WATCHDOG` and `CLAUDE_ENABLE_BYTE_WATCHDOG` force the corresponding watchdog on with `1` or off with `0`, within the connections the table lists; neither variable extends a watchdog to a connection type it doesn’t cover. `CLAUDE_ENABLE_BYTE_WATCHDOG` set to `0` also turns off the first-byte deadline.
- `CLAUDE_STREAM_IDLE_TIMEOUT_MS` sets both watchdogs’ timeout. Claude Code raises values below 5 minutes to 5 minutes, and caps the value at 30 minutes for the byte-level watchdog.
- `CLAUDE_BYTE_STREAM_IDLE_TIMEOUT_MS` sets the byte-level watchdog’s timeout without changing the event-level watchdog’s, clamped to between 10 seconds and 30 minutes, and takes precedence over `CLAUDE_STREAM_IDLE_TIMEOUT_MS` for that watchdog.
- `CLAUDE_STREAM_FIRST_BYTE_TIMEOUT_MS` sets the first-byte deadline. In a Claude apps gateway session, set this variable to [turn the deadline on](claude-apps-gateway-deploy.md#retry-an-unanswered-request-sooner-with-the-first-byte-deadline). If you leave the variable unset on another connection where the deadline runs, Claude Code uses the byte-level watchdog’s timeout, so `CLAUDE_STREAM_IDLE_TIMEOUT_MS` and `CLAUDE_BYTE_STREAM_IDLE_TIMEOUT_MS` change the deadline too. For the limits on the value, see [No response from API](../02-Claude-Code-CLI/errors.md#no-response-from-api).
- `API_TIMEOUT_MS` limits the wait for response headers, not how long a response streams after them. Where the first-byte deadline doesn’t run, set `API_TIMEOUT_MS` to a positive value under `300000` to retry an unanswered request in less than 5 minutes. Keep `API_TIMEOUT_MS` above the longest wait for headers, and the longest [non-streaming request](../02-Claude-Code-CLI/errors.md#streaming-response-ended-before-any-complete-data-was-received), that you expect: a request that needs longer times out again on each retry.
- `API_FORCE_IDLE_TIMEOUT` set to `0` turns the body idle timeout off, and set to `1` turns it on for every provider. The watchdogs run independently of it, so to let a stream pause longer than their thresholds, also raise or disable them.

When a watchdog aborts a stalled stream, Claude Code treats the abort as a mid-stream failure, and what you see depends on how far the response had got. Claude Code retries the request or ends the turn with an error, keeps the completed output and shows an [incomplete-response notice](../02-Claude-Code-CLI/errors.md#the-response-above-may-be-incomplete), or ends the turn normally. [Automatic retries](../02-Claude-Code-CLI/errors.md#automatic-retries) says where each outcome applies. In a [non-interactive session](../02-Claude-Code-CLI/headless.md), and for a subagent’s response in any session, Claude Code may first prompt Claude to continue the cut-off response; [that notice’s entry](../02-Claude-Code-CLI/errors.md#the-response-above-may-be-incomplete) says when it does and when you still see the notice. When the first-byte deadline fires, no response has started, so there is no partial output to keep. For how Claude Code re-sends the request and when the turn ends instead, see [No response from API](../02-Claude-Code-CLI/errors.md#no-response-from-api).


[​](#network-access-requirements)

Network access requirements

Claude Code requires access to the following URLs. Allowlist these in your proxy configuration and firewall rules, especially in containerized or restricted network environments. The first-run setup connectivity check points here when it can’t reach `api.anthropic.com` or `platform.claude.com`; see [Unable to connect to Anthropic services](../02-Claude-Code-CLI/errors.md#unable-to-connect-to-anthropic-services) for the check’s messages and recovery steps.

| URL                                  | Required for                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
|--------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `api.anthropic.com`                  | Claude API requests, including the WebFetch [domain safety check](data-usage.md#webfetch-domain-safety-check), feature flag fetches, and telemetry event logging                                                                                                                                                                                                                                                                                                                                                                                    |
| `claude.ai`                          | claude.ai account authentication                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `claude.com`                         | claude.ai account sign-in opens a `claude.com` page in the browser, which redirects to `claude.ai`; pre-approved WebFetch documentation lookups also reach this host from the CLI                                                                                                                                                                                                                                                                                                                                                                         |
| `platform.claude.com`                | Anthropic Console account authentication. OAuth token exchange, refresh, and revocation also go to this host for claude.ai accounts, so both Console and claude.ai sign-ins require it                                                                                                                                                                                                                                                                                                                                                                    |
| `sso.claude.com`                     | Part of the browser sign-in flow for organizations that use single sign-on (SSO). If a proxy blocks this host, those sign-ins can fail                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `mcp-proxy.anthropic.com`            | [MCP connectors from claude.ai](../06-MCP-Tools/General/mcp.md#use-mcp-servers-from-claude-ai), including connectors an organization administrator configures. Connector traffic routes through this proxy; connectors are enabled by default for claude.ai-authenticated users. To stop Claude Code from fetching them, set [`ENABLE_CLAUDEAI_MCP_SERVERS=false`](../02-Claude-Code-CLI/env-vars.md) or the [`disableClaudeAiConnectors`](../02-Claude-Code-CLI/settings-reference.md#disableclaudeaiconnectors) setting                                                                                   |
| `downloads.claude.ai`                | Plugin executable downloads; native installer, native auto-updater, and update version checks                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `storage.googleapis.com`             | Plugin install counts and metadata shown in `/plugin`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `storage.googleapis.com`             | Native installer and native auto-updater on versions prior to 2.1.116                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `registry.npmjs.org`                 | Plugin installs (fetching npm-source plugin packages and installing plugins’ Node.js package dependencies), `npx`-launched MCP servers, and the package registry for npm and bun installs of Claude Code itself                                                                                                                                                                                                                                                                                                                                           |
| `bridge.claudeusercontent.com`       | [Claude in Chrome](../03-IDE-Integrations/chrome.md) extension WebSocket bridge                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `*.frame.claudeusercontent.com`      | [Artifact](../02-Claude-Code-CLI/artifacts.md) content reads. The CLI fetches an artifact’s files from this host when Claude opens one, and only when the Artifact tool is [available](../02-Claude-Code-CLI/artifacts.md#availability) for your account. To turn the tool off and drop this requirement, set [`"enableArtifact": false`](../02-Claude-Code-CLI/settings-reference.md#enableartifact) or [`CLAUDE_CODE_DISABLE_ARTIFACT=1`](../02-Claude-Code-CLI/env-vars.md)                                                                                                                                            |
| `github.com`                         | Cloning GitHub-hosted [plugin marketplaces](../08-Plugins-Skills/plugins.md) and plugins, including the official Anthropic marketplace, over HTTPS or SSH. To clone GitHub `owner/repo` sources over HTTPS only, set [`CLAUDE_CODE_PLUGIN_PREFER_HTTPS=1`](../02-Claude-Code-CLI/env-vars.md)                                                                                                                                                                                                                                                                                   |
| `raw.githubusercontent.com`          | Changelog feed for [`/release-notes`](../02-Claude-Code-CLI/commands.md). In interactive sessions, Claude Code also fetches it in the background at startup when its cached changelog doesn’t yet cover the running version, such as the first start after an update; non-interactive and cloud sessions never fetch it                                                                                                                                                                                                                                                   |
| `*-review.googlesource.com`          | Gerrit change lookup on `googlesource.com` checkouts. When a Claude Desktop Code tab session starts or resumes on a [trusted](../02-Claude-Code-CLI/permissions.md#project-allow-rules-and-workspace-trust) checkout whose `origin` is a `googlesource.com` host, Claude Code asks that host’s `-review` server anonymously for the open change matching HEAD’s `Change-Id`, once per start or resume. Other session types skip the lookup, and no other Gerrit host is contacted. Optional: disable with [`CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`](../02-Claude-Code-CLI/env-vars.md) |
| `http-intake.logs.us5.datadoghq.com` | Operational telemetry events, sent only when the CLI uses the Anthropic API directly, never for Amazon Bedrock, Google Cloud’s Agent Platform, or Microsoft Foundry. Optional: disable with [`DISABLE_TELEMETRY`](data-usage.md#telemetry-services) or `DO_NOT_TRACK`                                                                                                                                                                                                                                                                               |
| `browser-intake-us5-datadoghq.com`   | Operational error reports, sent when the CLI uses the Anthropic API directly and a server-side rollout gate enables them. Optional: disable with `DISABLE_ERROR_REPORTING` or `DISABLE_TELEMETRY`; see [Telemetry services](data-usage.md#telemetry-services)                                                                                                                                                                                                                                                                                       |
| `formulae.brew.sh`                   | Update version checks on Homebrew installs. Other install methods don’t contact this host                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `code.claude.com`                    | Claude Code documentation lookups by the built-in claude-code-guide agent and pre-approved WebFetch requests. Blocking this host only affects documentation lookups                                                                                                                                                                                                                                                                                                                                                                                       |

If you install Claude Code through npm or manage your own binary distribution, end users don’t need the native installer and auto-updater uses of `downloads.claude.ai`, but npm and bun installs need their package registry, `registry.npmjs.org`, unless your organization mirrors it. The other uses in the table apply regardless of install method. The two Datadog intake hosts carry only optional operational telemetry, and setting [`CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`](../02-Claude-Code-CLI/env-vars.md) disables both. Sessions on third-party providers never send to these hosts, even when a platform sets [`CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST`](../02-Claude-Code-CLI/env-vars.md) and telemetry metrics default on. See [Telemetry services](data-usage.md#telemetry-services) for everything Claude Code sends and how to disable it before finalizing your allowlist. When using [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md), [Google Cloud’s Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md), [Microsoft Foundry](../02-Claude-Code-CLI/microsoft-foundry.md), or a signed-in [Claude apps gateway](claude-apps-gateway.md) session, model traffic and authentication go to your provider or gateway instead of `api.anthropic.com`, `claude.ai`, or `platform.claude.com`. The WebFetch tool still calls `api.anthropic.com` for its [domain safety check](data-usage.md#webfetch-domain-safety-check) unless you set `skipWebFetchPreflight: true` in [settings](../02-Claude-Code-CLI/settings.md). When routing through an [LLM gateway](llm-gateway.md) with [`ANTHROPIC_BASE_URL`](llm-gateway-connect.md#set-the-base-url-and-credential), the [fast mode](../02-Claude-Code-CLI/fast-mode.md) availability check still calls `api.anthropic.com` rather than the gateway base URL. The check does honor a configured HTTP proxy, so where a network block is the cause, an allowlist entry for `api.anthropic.com` in the proxy is the fix. A network block fails the check only where the host is unreachable even through the proxy, and fast mode then reports a connectivity error. The same connectivity error appears when the check presents a gateway-issued credential that Anthropic rejects; allowlisting doesn’t help there, since nothing is blocked. See [use fast mode behind proxies and LLM gateways](../02-Claude-Code-CLI/fast-mode.md#use-fast-mode-behind-proxies-and-llm-gateways) for the variables that restore it.


[​](#organization-ip-allowlists-and-proxy-egress)

Organization IP allowlists and proxy egress

If your organization has [IP allowlisting](restrict-access-to-claude-with-ip-allowlisting.md) enabled for Claude, route `bridge.claudeusercontent.com` through the same proxy egress as `claude.ai` and `api.anthropic.com`, for example by placing it in the same Zscaler app segment or Netskope steering policy. If you can’t route it that way, add the egress address your proxy uses for that host to your organization’s IP allowlist, but only when that address is dedicated to your organization: a shared proxy egress range also admits the proxy vendor’s other customers. Anthropic checks connections to `bridge.claudeusercontent.com` against your organization’s IP allowlist using the address they arrive from. If your proxy sends traffic for that host out through an address that isn’t on that allowlist, Claude Code can’t connect to the [Claude in Chrome](../03-IDE-Integrations/chrome.md) extension even though the rest of Claude Code works.


[​](#github-allow-lists-and-firewalls)


[Cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) in Anthropic-hosted environments and [Code Review](../02-Claude-Code-CLI/code-review.md) connect to your repositories from Anthropic-managed infrastructure; sessions in a [self-hosted environment](self-hosted-environments.md) connect from inside your network, unless the runner opts into the [Anthropic git proxy](self-hosted-environments-deploy.md#use-the-anthropic-git-proxy), which fetches from Anthropic’s side. If your GitHub Enterprise Cloud organization restricts access by IP address, enable [IP allow list inheritance for installed GitHub Apps](https://docs.github.com/en/enterprise-cloud@latest/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization#allowing-access-by-github-apps) and also [add an allow list entry](https://docs.github.com/en/enterprise-cloud@latest/organizations/keeping-your-organization-secure/managing-security-settings-for-your-organization/managing-allowed-ip-addresses-for-your-organization#adding-an-allowed-ip-address) for Anthropic’s [outbound IP addresses](../04-API-Reference/Endpoints/ip-addresses.md#outbound-ip-addresses). Inheritance covers only the requests the Claude GitHub App makes as an installation, not the requests it makes on your users’ behalf. For other firewalls, see the [Anthropic API IP addresses](../04-API-Reference/Endpoints/ip-addresses.md). For self-hosted [GitHub Enterprise Server](../02-Claude-Code-CLI/github-enterprise-server.md) instances behind a firewall, allowlist Anthropic’s [outbound IP addresses](../04-API-Reference/Endpoints/ip-addresses.md#outbound-ip-addresses) so Anthropic infrastructure can reach your GHES host to clone repositories and post review comments. Sessions in a [self-hosted environment](self-hosted-environments-deploy.md#configure-git) reach your GHES host from inside your network instead, so that exposure applies only to Anthropic-hosted sessions, to hosted pre-session flows such as the repository picker, and to self-hosted runners that opt into the [Anthropic git proxy](self-hosted-environments-deploy.md#use-the-anthropic-git-proxy), which fetches from Anthropic’s side. The [SCM connector](self-hosted-environments-reference.md#scm-connector-flags) isn’t available, so the hosted pre-session flows can’t reach a GHES host that’s only routable inside your network.


[​](#desktop-and-claude-ai)

Desktop and claude.ai

The preceding table covers the standalone CLI. The Claude Desktop app and claude.ai in a browser load their application code and user content from additional Anthropic CDN hosts, including `assets-proxy.anthropic.com` and the other `*.claudeusercontent.com` origins that serve [artifacts](../02-Claude-Code-CLI/artifacts.md) in those apps. Allowing `claude.ai` while blocking those hosts produces a blank page rather than an error. See [network access requirements](../16-Mobile-Desktop/desktop.md#network-access-requirements) on the Desktop page. Claude Desktop and claude.ai also render some tool results inside a conversation as interactive widgets, such as the [MCP Apps](https://claude.com/docs/connectors/building/mcp-apps/getting-started) some connectors provide. Those widgets load from generated subdomains of `claudemcpcontent.com`, so allow `*.claudemcpcontent.com` with the wildcard intact. If you block it, the rest of the app keeps working, but those widgets don’t load.


[​](#third-party-hosts-for-artifact-fonts-and-libraries)

Third-party hosts for artifact fonts and libraries

An [artifact](../02-Claude-Code-CLI/artifacts.md) that loads a typeface from [Google Fonts](../02-Claude-Code-CLI/artifacts.md#improve-the-visual-design) also requests `fonts.googleapis.com` and `fonts.gstatic.com`. Both hosts are optional. If you block them, artifacts render in fallback typefaces. Block with a fast rejection rather than a silent drop so the font request fails immediately instead of delaying the page’s first render. Artifacts can also load JavaScript libraries, such as React or a charting package, from `cdnjs.cloudflare.com`, `cdn.jsdelivr.net`, `cdn.tailwindcss.com`, `code.jquery.com`, and `unpkg.com`, and from no other external host. If you block those hosts, the parts of an artifact that depend on a library don’t work, and unlike a blocked font, a blocked library has no fallback. Block with a fast rejection here too, so a blocked library request fails at once rather than hanging until it times out.


[​](#additional-resources)

Additional resources

- [Settings files and precedence](../02-Claude-Code-CLI/settings.md)
- [Environment variables reference](../02-Claude-Code-CLI/env-vars.md)
- [Troubleshooting guide](../02-Claude-Code-CLI/troubleshooting.md)

---

## Okta SSO/SCIM email mismatch | Claude Help Center

Source: https://support.claude.com/en/articles/13917840-okta-sso-scim-email-mismatch
Path: 13-Enterprise-Admin/okta-sso-scim-email-mismatch.md

# Okta SSO/SCIM email mismatch

March 24, 2026


Claude uses email as the primary identifier to match SSO logins to provisioned seats. In Okta, SCIM provisioning and SSO are configured separately and can pull email from different user profile fields. This guide explains how to identify and resolve the mismatch.

**Applies to:** Enterprise plans and Console organizations using SCIM provisioning. Team plans don't have SCIM provisioning, so this mismatch scenario doesn't apply—see **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195)** for what's available on each plan.

------------------------------------------------------------------------

## Symptoms

People may experience one or more of the following when attempting to access your organization via SSO:

- **"Account creation is blocked"** — SSO authentication succeeds but Claude cannot locate a matching provisioned account. If your organization has organization creation restricted (recommended), the person is blocked entirely.

- **Landing on a free personal account** — If organization creation is not restricted, the person bypasses your organization entirely and creates or lands on a free personal account instead of their provisioned seat.

- **"Please confirm your email" mismatch** — The SSO callback shows a different email than the one the person entered at login.

- **Claude Code authentication failure** — The Claude Code CLI shows an email mismatch error during the authentication flow.

------------------------------------------------------------------------

## How this happens

Okta user profiles contain multiple fields that represent identity. SCIM provisioning (under **Provisioning → To App**) and SAML/OIDC SSO (under **Sign On**) are configured independently.

[TABLE]

A common mismatch: SCIM uses `user.login` while SAML sends `user.email`. Claude requires an exact string match.

**Common confusion:** Okta's SCIM attribute mappings and SAML attribute statements live in two different tabs — **Provisioning → To App** for SCIM, and **Sign On** for SSO.

------------------------------------------------------------------------

## Diagnostic steps

### Step 1 — Confirm the mismatch

1.  **Check SCIM email:** Navigate to **Applications → \[Claude App\] → Provisioning → To App → Attribute Mappings**. Locate the `email` (or `userName`) row. The **Value** column shows the Okta expression being sent.

2.  **Check SSO email (SAML):** Go to **Applications → \[Claude App\] → Sign On → SAML Settings → Edit → Attribute Statements**. Find the `email` attribute. The **Value** column indicates which Okta field is asserted.

3.  **Check SSO email (OIDC):** Go to **Applications → \[Claude App\] → Sign On → OpenID Connect ID Token** and review the **Claims** section for email.

4.  If SCIM and SSO values reference different Okta fields, the mismatch is confirmed.

### Step 2 — Identify the scope of the problem

- **Most or all provisioned people affected:** This is a systemic attribute mapping problem. The fix is in your IdP's SCIM attribute mapping.

- **One or two people affected:** The issue is specific to those accounts. Check their user profile directly.

### Step 3 — Verify Okta user profiles directly

1.  Go to **Directory → People → \[User\] → Profile**.

2.  Compare the Login and Email field values. Differences between these fields will produce mapping mismatches.

------------------------------------------------------------------------

## Resolution

### Align both mappings to the same Okta field

The safest fix is to use `user.email` for both SCIM and SSO, as this field contains the canonical email address in Okta.

1.  **Update SCIM mapping:** In **Provisioning → To App → Attribute Mappings**, change the `email` (or `userName`) source to `user.email`.

2.  **Update SSO claim (SAML):** In **Sign On → SAML Settings → Attribute Statements**, change the email attribute value to user.email.

3.  **Update SSO claim (OIDC):** In **Sign On → OpenID Connect ID Token → Claims**, update the email claim to map to `user.email`.

4.  Click “Save.”

### Force a full re-sync

**Critical — Full sync required:** An incremental sync will not update existing records after you change an attribute mapping.

1.  In **Provisioning → To App**, click “Force Sync” to trigger a full re-evaluation of everyone assigned.

2.  Alternatively, for specific people: **Provisioning → Push Users → select affected people → Push Now**.

3.  Check Okta System Log (**Reports → System Log**) for provisioning errors.

4.  Verify updated email values appear in provisioning logs before people retry login.

------------------------------------------------------------------------

## Post-fix cleanup

After correcting the attribute mapping and completing the full sync:

- **Rogue free accounts:** If organization creation wasn't restricted before the fix, some people may have created free personal Claude accounts. Contact **[our Support team](https://support.claude.com/en/articles/9015913)** for removal.

- **Ghost accounts (wrong-email seats):** The originally provisioned accounts with the incorrect email may still exist in your organization, occupying seats that can never be used. Contact our Support team for deprovisioning.

- **Seat availability:** If ghost accounts are occupying all contracted seats, new logins will fail with an out-of-seats error even after the mapping is fixed.

- **Re-adding affected people:** After ghost account removal, people with the corrected email may need to be re-invited or re-provisioned.

- **Prevent future occurrences:** Enable "Restrict organization creation" in your organization's Identity and access settings to prevent unprovisioned people from creating free accounts.

------------------------------------------------------------------------

## Verification

After completing the fix and any cleanup:

1.  Check a sample of provisioned people—confirm their email in the provisioning log matches the email format that SSO sends.

2.  Ask an affected person to clear browser cookies for claude.ai, then log in via SSO. They should land directly in your organization's workspace without error.

3.  Confirm people aren't creating free accounts—with organization creation restricted, blocked people see a clear error instead of landing on a personal account.

4.  If Claude Code was affected, have the person re-run `claude auth login --enterprise` and confirm the email matches their provisioned seat.

------------------------------------------------------------------------

## Common issues

[TABLE]

------------------------------------------------------------------------

## When to contact Support

Contact **[our Support team](https://support.claude.com/en/articles/9015913)** with your organization's domain, the affected person's email, and attribute mapping screenshots when:

- SCIM and SSO attributes appear identical but people still cannot access their seats.

- You need confirmation of the email Claude recorded during SCIM provisioning for specific people.

- You need help cleaning up ghost accounts or rogue free organizations.

- People are hitting an out-of-seats error despite available contracted seats.

---

## Okta SSO setup | Claude Help Center

Source: https://support.claude.com/en/articles/13917894-okta-sso-setup
Path: 13-Enterprise-Admin/okta-sso-setup.md

# Okta SSO setup

September 2, 2026


This guide covers configuring SSO and SCIM provisioning for Claude with Okta as your identity provider. It applies to Team plans, Enterprise plans, and Console organizations.

## Prerequisites

- A Claude Team plan, Enterprise plan, or Console organization with a parent organization (see **[Important considerations before enabling SSO](https://support.claude.com/en/articles/10276682)** for Console parent org requirements)

- Owner or Primary Owner role (Team and Enterprise) or Admin role (Console)

- Okta administrator access

- Your domain verified in Claude's Identity and access settings—see **[Set up single sign-on](https://support.claude.com/en/articles/13132885)** for the full setup path including domain verification

## Where to find your configuration values

The ACS URL, Entity ID, and SCIM credentials referenced below are provided in the WorkOS setup flow within your Identity and access settings.

- **Team and Enterprise plans:** go to **[claude.ai/admin-settings/identity](https://claude.ai/admin-settings/identity)**

- **Console organizations:** go to **[platform.claude.com/settings/identity](https://platform.claude.com/settings/identity)**

Start the SSO setup flow there and keep it open alongside the Okta Admin console as you work through the steps below.

------------------------------------------------------------------------

## Step 1 — Create the Okta application

1.  In the Okta Admin console, go to **Applications → Applications → Create App Integration**.

2.  For the SSO method, choose "SAML 2.0" (recommended) or "OIDC."

3.  Name the app "Claude" and click "Next."

## Step 2 — Configure SAML

1.  In the **Configure SAML** tab, enter: **Single sign-on URL (ACS URL)** from the WorkOS setup flow, **Audience URI (Entity ID)** from the WorkOS setup flow, **Name ID format:** EmailAddress, and **Application username:** Email.

2.  Under **Attribute Statements**, add an attribute named email with value user.email.

    1.  If you use group mappings (JIT or SCIM provisioning with **Enable group mappings** turned on), also add a Group Attribute Statement named "groups" with a filter that includes every Okta group you map to a Claude role or seat type. Without it, sign-ins arrive with no group information: users can’t be assigned a mapped role, and under JIT with group mappings, a user whose sign-in includes no mapped group is removed from the organization at their next login. See **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195-set-up-jit-or-scim-provisioning)**.

3.  Download the **Identity Provider metadata** XML and upload it in the WorkOS setup flow when prompted.

## Step 3 — Enable SCIM provisioning

**Note:** SCIM provisioning is available on Enterprise plans and eligible Console organizations only. If you're on a Team plan, skip this step—you can use JIT provisioning instead. See **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195)**.

1.  In the app, go to the **Provisioning** tab and click “Configure API Integration.”

2.  Check **Enable API integration** and enter the SCIM Base URL and API Token from the WorkOS setup flow.

3.  Click “Test API credentials” to verify, then save.

4.  Under **To App**, enable **Create Users**, **Update User Attributes**, and **Deactivate Users**.

5.  In **Attribute Mappings → To App**, confirm the email attribute maps to user.email — the same field as your SAML assertion.

**Important:** SCIM and SAML must use the same Okta field for email. A common issue is SCIM using user.login while SAML uses user.email. See **[Okta SSO/SCIM email mismatch](https://support.claude.com/en/articles/13917840)** if people can't log in after provisioning.

## Step 4 — Assign people or groups

1.  In the **Assignments** tab, assign the people or Okta groups that should access Claude.

2.  If you enable SCIM, Okta will automatically provision assigned people. If you're using JIT, people will be provisioned on their first SSO login.

## Step 5 — Verify

1.  If you enabled SCIM, check provisioning logs to confirm people were successfully created in Claude.

2.  Have a test user complete SSO and verify they land in your organization's workspace.

------------------------------------------------------------------------

## Need help?

For the full end-to-end flow including domain verification and choosing a provisioning approach, see **[Set up single sign-on](https://support.claude.com/en/articles/13132885)**. If you run into issues, contact **[our Support team](../15-Claude-AI-Features/how-to-get-support.md)** with your organization's domain and a screenshot of your Okta SAML configuration.

---

## Ping Identity SSO/SCIM email mismatch | Claude Help Center

Source: https://support.claude.com/en/articles/13917875-ping-identity-sso-scim-email-mismatch
Path: 13-Enterprise-Admin/ping-identity-sso-scim-email-mismatch.md

# Ping Identity SSO/SCIM email mismatch

March 24, 2026


Claude uses email as the primary identifier to match SSO logins to provisioned seats. Ping Identity products (PingOne and PingFederate) have flexible, layered attribute configuration. When SCIM provisioning and SAML/OIDC SSO pull from different user attributes, a mismatch blocks access.

**Applies to:** Enterprise plans and Console organizations using SCIM provisioning. Team plans don't have SCIM provisioning, so this mismatch scenario doesn't apply—see **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195)** for what's available on each plan.

------------------------------------------------------------------------

## Symptoms

People may experience one or more of the following when attempting to access your organization via SSO:

- **"Account creation is blocked"** — The person authenticates via SSO but Claude cannot find a matching provisioned account.

- **Landing on a free personal account** — If organization creation is not restricted, the person bypasses your organization entirely.

- **"Please confirm your email" mismatch** — The SSO callback shows a different email than the one the person entered at login.

- **Claude Code authentication failure** — The Claude Code CLI shows an email mismatch error during the authentication flow.

------------------------------------------------------------------------

## How this happens

Ping Identity products allow granular attribute mapping at multiple levels (directory, IdP adapter, SP connector, application). SCIM and SSO can each traverse different paths through these layers, resulting in different email values reaching Claude:

[TABLE]

Claude requires an exact string match between the SCIM-provisioned email and the SSO-asserted email.

**PingFederate note:** PingFederate's attribute contract system is especially complex—email can pass through multiple layers (LDAP → IdP adapter → adapter contract → SP connector → assertion). A mismatch at any layer will cause the wrong value to reach Claude. Trace the value end-to-end.

------------------------------------------------------------------------

## Diagnostic steps

### Step 1 — Confirm the mismatch (PingOne)

1.  **Check SCIM attribute mapping:** In PingOne Admin, go to **Connections → Applications → \[Claude app\] → Attribute Mappings**. Find the attribute mapped to emails\[primary\].value or email. Note the PingOne user attribute used as the source.

2.  **Check SSO attribute mapping:** In the same app, go to the **SAML** or **OIDC** tab and find the attribute or claim mapped to email. Note its source attribute.

3.  If SCIM and SSO reference different PingOne attributes, you've confirmed the mismatch.

### Step 1 (alternate) — Confirm the mismatch (PingFederate)

1.  In the PingFederate Admin console, locate the **SP Connection** for Claude.

2.  In **Attribute Contract Fulfillment**, find the email attribute and trace its source back to the IdP adapter or LDAP data store.

3.  Separately, check the SCIM provisioning connector or outbound provisioning channel for Claude and trace the source of the email attribute being sent.

4.  If the two traces lead to different directory attributes, you've confirmed the mismatch.

### Step 2 — Identify the scope of the problem

- If **most or all** provisioned people share the same email format mismatch, this is a **systemic attribute mapping problem**. The fix is in your IdP's SCIM attribute mapping.

- If only **one or two people** are affected, the issue is likely specific to those accounts. Check their profile directly.

### Step 3 — Check attribute values for a specific person

1.  **PingOne:** Go to **Identities → Users → \[User\]** and compare the Username and Email field values.

2.  **PingFederate with LDAP:** Check the person's LDAP record and compare mail, userPrincipalName, sAMAccountName, and any other attributes being used in your adapter mapping.

------------------------------------------------------------------------

## Resolution

### PingOne — Align both mappings to the email attribute

1.  In **Connections → Applications → \[Claude app\]**, open **Attribute Mappings**.

2.  For SCIM: Ensure emails\[primary\].value maps to PingOne's **Email Address** attribute.

3.  For SAML/OIDC: Ensure the email attribute or claim also maps to PingOne's **Email Address** attribute.

4.  Save changes.

### PingFederate — Align attribute contract across all layers

1.  In the SP Connection for Claude, go to **Attribute Contract Fulfillment**.

2.  Find the email attribute. Ensure its source is the same LDAP or data store attribute used in your SCIM outbound provisioning channel.

3.  If using a custom IdP adapter, ensure the adapter's contract includes the canonical email attribute and that it's correctly mapped through to the SP connection.

4.  Update SCIM provisioning to use the same source attribute.

### Trigger a full re-sync

**Critical — Full sync required:** An incremental sync will not update existing records after you change an attribute mapping. You must trigger a **full restart** of the provisioning cycle.

1.  **PingOne:** In the app's provisioning settings, trigger a full provisioning cycle. You may need to disable and re-enable provisioning to force a complete re-push.

2.  **PingFederate:** Trigger a full sync in your outbound provisioning channel. Check your provisioning logs to confirm the updated email values are being sent.

3.  Verify that updated email values appear in provisioning logs before asking people to retry login.

------------------------------------------------------------------------

## Post-fix cleanup

After correcting the attribute mapping and completing the full sync:

- **Rogue free accounts:** If organization creation was not restricted before the fix, some people may have inadvertently created free personal Claude accounts. Contact **[our Support team](../15-Claude-AI-Features/how-to-get-support.md)** to have these removed.

- **Ghost accounts (wrong-email seats):** Contact our Support team to deprovision these.

- **Seat availability:** If ghost accounts are occupying all contracted seats, new logins will fail. Contact our Support team.

- **Re-adding affected people:** After ghost accounts are removed, people may need to be re-invited or re-provisioned.

- **Prevent future occurrences:** Enable "Restrict organization creation" in your organization's Identity and access settings.

------------------------------------------------------------------------

## Verification

1.  Check a sample of provisioned people—confirm their email in the provisioning log matches the email format that SSO sends.

2.  Ask an affected person to clear browser cookies for claude.ai, then log in via SSO.

3.  Confirm people aren't creating free accounts.

4.  If Claude Code was affected, have the person re-run `claude auth login --enterprise` and confirm the email matches their provisioned seat.

------------------------------------------------------------------------

## Common issues

[TABLE]

------------------------------------------------------------------------

## **When to contact Support**

Contact **[our Support team](../15-Claude-AI-Features/how-to-get-support.md)** with your organization's domain, the affected person's email, and screenshots of your attribute mappings when:

- SCIM and SSO attributes appear identical but people still cannot access their seats.

- You need confirmation of the email Claude recorded during SCIM provisioning for specific people.

- You need help cleaning up ghost accounts or rogue free orgs.

- People are hitting an out-of-seats error despite available contracted seats.

---

## Ping Identity SSO setup | Claude Help Center

Source: https://support.claude.com/en/articles/13917902-ping-identity-sso-setup
Path: 13-Enterprise-Admin/ping-identity-sso-setup.md

# Ping Identity SSO setup

August 6, 2026


This guide covers configuring Claude with PingOne or PingFederate as your identity provider. It applies to Team plans, Enterprise plans, and Console organizations.

## Prerequisites

- A Claude Team plan, Enterprise plan, or Console organization with a parent organization (see **[Important considerations before enabling SSO](https://support.claude.com/en/articles/10276682)** for Console parent org requirements)

- Owner or Primary Owner role (Team and Enterprise) or Admin role (Console)

- PingOne Environment Admin or PingFederate Admin access

- Your domain verified in Claude's Identity and access settings—see **[Set up single sign-on](https://support.claude.com/en/articles/13132885)** for the full setup path including domain verification

## Where to find your configuration values

The ACS URL, Entity ID, and SCIM credentials referenced below are provided in the WorkOS setup flow within your Identity and access settings—not by contacting Support.

- **Team and Enterprise plans:** go to **[claude.ai/admin-settings/identity](https://claude.ai/admin-settings/identity)**

- **Console organizations:** go to **[platform.claude.com/settings/identity](https://platform.claude.com/settings/identity)**

Start the SSO setup flow there and keep it open alongside your Ping admin console as you work through the steps below.

------------------------------------------------------------------------

## PingOne setup

### Step 1 — Create an application connection

1.  In the PingOne admin console, go to **Connections → Applications → + Add Application**.

2.  Select "SAML Application."

3.  Name it "Claude" and click "Configure."

### Step 2 — Configure SAML

1.  Choose "Manually Enter" and provide the SP details from the WorkOS setup flow: **ACS URL** and **Entity ID**.

2.  Download the PingOne **IdP metadata** and upload it in the WorkOS setup flow when prompted.

3.  In **Attribute Mappings**, map `email` to the PingOne **Email Address** attribute.

### Step 3 — Enable SCIM provisioning

**Note:** SCIM provisioning is available on Enterprise plans and eligible Console organizations only. If you're on a Team plan, skip this step—you can use JIT provisioning instead. See **[Set up JIT or SCIM provisioning](https://support.claude.com/en/articles/13133195)**.

1.  In the application settings, go to the **Provisioning** tab.

2.  Enable **Outbound Provisioning** and enter the SCIM endpoint URL and access token from the WorkOS setup flow.

3.  Map `emails[primary].value` to the PingOne **Email Address** attribute—the same attribute used in SAML.

**Critical:** Ensure SAML and SCIM use identical attribute sources. See **[Ping Identity SSO/SCIM email mismatch](https://support.claude.com/en/articles/13917875)** for troubleshooting.

### Step 4 — Assign population

1.  Under **Populations**, assign the user population that should access Claude.

2.  Enable the application and click "Save."

------------------------------------------------------------------------

## PingFederate setup

PingFederate configurations vary significantly by version and deployment. The general approach is:

1.  Create a new **SP Connection** using the SP metadata from the WorkOS setup flow.

2.  Configure the **Attribute Contract** to include `email`.

3.  Map the email attribute in the **Adapter Mapping** to the person's primary email field.

4.  For SCIM (Enterprise plans and eligible Console organizations only), configure an outbound provisioning channel targeting the SCIM endpoint from the WorkOS setup flow.

Contact **[our Support team](https://support.claude.com/en/articles/9015913)** for PingFederate-specific guidance.

------------------------------------------------------------------------

## Need help?

See **[Set up single sign-on](https://support.claude.com/en/articles/13132885)** for the full end-to-end flow including domain verification and choosing a provisioning approach. If you run into issues, contact **[our Support team](https://support.claude.com/en/articles/9015913)** with your Ping environment details.

---

## Responsible Use of Anthropic's Models: Guidelines for Organizations Serving Minors | Claude Help Center

Source: https://support.claude.com/en/articles/9307344-responsible-use-of-anthropic-s-models-guidelines-for-organizations-serving-minors
Path: 13-Enterprise-Admin/responsible-use-of-anthropic-s-models-guidelines-for-organizations-serving-minors.md

# Responsible Use of Anthropic's Models: Guidelines for Organizations Serving Minors

March 16, 2026


At Anthropic, we recognize the unique vulnerabilities and needs of children in digital spaces. In order to create a safer digital environment and mitigate risks, organizations providing minors with the ability to directly interact with products that incorporate our API(s) should implement the following safeguards:

**1. Additional Technical Measures**

Organizations with products serving minors should implement additional safety features tailored to their unique use cases, as they are best situated to understand the specific ways their end users may interact with products that incorporate Anthropic's services. These safety measures may include, but are not limited to:

- Age verification systems to ensure only intended users can access the product

- Content moderation and filtering to block inappropriate or harmful content

- Monitoring and reporting mechanisms to identify and address potential issues

- Educational resources and guidance for minors on safe and responsible use of the product

In addition to these organization-specific measures, Anthropic may make available technical measures intended to tailor product experiences for certain end users, including minors. For example, we may provide a child-safety system prompt, which organizations serving minors should implement as part of a comprehensive suite of safety measures. It is important to note that, while helpful, these technical measures are not infallible and should be used in conjunction with the organization's own safety features to ensure a robust approach to child safety.

**2. Regulatory Compliance**

It is the responsibility of organizations to comply with all applicable child safety and data privacy regulations, such as the Children's Online Privacy Protection Act (COPPA) in the United States. Compliance with these regulations should be clearly stated on the organization’s website or similar public-facing documentation.

**3. Disclosure Requirements**

Organizations must disclose to their users that they are interacting with an AI system rather than a human.

Anthropic will periodically audit organizations for compliance with these safeguards. If your organization has a high violation rate and has not implemented these safety recommendations, we may ask you to implement them. Failure to implement these recommendations when requested, or a continued high violation rate, may lead to the suspension or termination of your account.

---

## Restrict access to Claude with IP allowlisting | Claude Help Center

Source: https://support.claude.com/en/articles/13200993-restrict-access-to-claude-with-ip-allowlisting
Path: 13-Enterprise-Admin/restrict-access-to-claude-with-ip-allowlisting.md

# Restrict access to Claude with IP allowlisting


IP allowlisting is available for Enterprise plans only.

IP allowlisting enables Enterprise plan administrators to control which IP addresses can access Claude through their organization. This feature ensures that requests can only be made from approved network locations, providing an additional layer of security.

When enabled, we validate the source IP address of every authenticated request against your organization's configured allowlist. Requests from IP addresses not added to the allowlist will be blocked.

IP allowlisting supports CIDR ranges. For example: `10.0.0.0/8, 2001:db8::/32`.

## What IP allowlisting covers

When IP allowlisting is on, the following are only available from your allowlisted IP addresses:

- Chats and projects

- Artifacts, including Claude Docs and other artifact types

- Artifact links shared within your organization

- Live editing and collaboration in Claude Docs

- Access to Claude Docs from connected tools

If someone in your organization opens an artifact from an IP address that isn't on your allowlist, they see an access-denied message instead of the content.

**Important:** Artifacts shared outside your organization aren't limited by your allowlist. To keep artifacts behind your allowlist, turn off **External sharing** and **Email invitations outside your organization** in **Organization settings \> Artifacts**. Artifacts you've allowed for external sharing individually stay available outside your allowlist. Learn more about **[managing sharing outside your organization](../17-Billing-Plans/artifacts-admin-guide-for-team-and-enterprise-plans.md#h_fd0c095985)**.

## How to configure IP allowlisting

If your Enterprise organization is interested in enabling an IP allowlist, please compile a list of all necessary CIDR ranges for your organization, including office locations, VPN exit points, and any other approved access points. Omitting required CIDR ranges could result in users getting locked out of Claude. Then, reach out to your Anthropic Contact or our **[Support team](https://support.claude.com/en/articles/9015913)** to share your list of CIDR ranges. They can add these to your account’s allowlist to enable the feature.

When a request originates from an IP address that’s not in your allowlist, access is denied. Users should contact their IT administrator if they believe they're being blocked in error.

---

## Restrict verified-domain connectors to your Enterprise | Claude Help Center

Source: https://support.claude.com/en/articles/15402193-restrict-verified-domain-connectors-to-your-enterprise
Path: 13-Enterprise-Admin/restrict-verified-domain-connectors-to-your-enterprise.md

# Restrict verified-domain connectors to your Enterprise


This article explains how to prevent Claude accounts outside your Enterprise organization from connecting certain services, like Gmail and Slack, using an email address on your verified domains. Use the **Restrict verified-domain connectors to your enterprise** setting to keep company data from reaching personal Claude accounts through connectors.

This setting is available to Owners, Primary Owners, and custom roles with the Identity & Access permission set to Manage on the Enterprise plan. Your Enterprise organization needs at least one verified domain.

## How it works

When you turn on **Restrict verified-domain connectors to your enterprise**, only Claude accounts in your Enterprise organization can connect the supported connectors using an email address on your verified domains. Anyone who tries to make one of these connections from a Claude account outside your Enterprise is blocked. This setting applies to your entire Enterprise. You manage it at the parent organization level, and it applies to all child organizations.

This check relies on the connected service sharing the account's identity during the connection process. For supported connectors, Claude can confirm the email domain of the account being connected and enforce the restriction. When a service doesn't share account identity, Claude can't perform the check and the connection proceeds normally.

This setting applies to the following connectors:

- Airtable

- Asana

- Atlassian

- Box

- Canva

- Figma

- Gmail

- Google Calendar

- Google Docs

- Google Drive

- Google Sheets

- Google Slides

- HubSpot

- Linear

- Microsoft 365

- Miro

- Notion

- Slack

- Smartsheet

- Tableau

You can also view the current list in **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)**. Contact **[our support team](../15-Claude-AI-Features/how-to-get-support.md)** to request additional connectors.

For example, someone has two Claude accounts: a personal account using their personal email, and a work account using their work email. The work email is on a domain your Enterprise organization has verified.

When the setting is off, they can sign in to Claude with their personal account and connect the Gmail account they use for work. That moves company information outside your organization's controls.

When the setting is on, they can't connect their work Gmail from their personal account. They can still connect it from their work account. That keeps company information inside your organization's controls.

------------------------------------------------------------------------

## Prerequisites

Before you can turn on this setting, your Enterprise organization must verify your domain.

## Turn on the restriction

1.  Navigate to **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)**.

2.  Scroll to **Connector domain restriction**.

3.  Toggle on "Restrict verified-domain connectors to your enterprise."

You can turn off this setting at any time. It's separate from domain capture, which can't be reversed once enabled.

------------------------------------------------------------------------

## What people see when a connection is restricted

If someone tries to make a connection that’s restricted, the connection fails at sign-in with this message:

“This corporate identity belongs to an Enterprise that manages access through their own Claude account. Sign in to your organization's Claude account to use this connection.”

## What the setting doesn't do

- **It doesn't restrict connections in the other direction.** When the setting is on, people in your Enterprise can still connect services for personal use to their Enterprise Claude account. For example, they can connect their personal Gmail account.

- **It doesn't replace data loss prevention.** It's a safeguard against accidentally connecting a work account to the wrong Claude account, not a control against deliberate data movement.

- **It doesn't disconnect existing connections.** The restriction applies only to new connection attempts. Connections made before you turned on the setting stay connected.

- **It doesn't notify admins.** Only the person attempting the connection sees the message if it fails.

------------------------------------------------------------------------

## Frequently asked questions

### How is this different from domain claiming?

Domain claiming moves existing personal Claude accounts on your domain into your Enterprise workspace. This setting leaves Claude accounts where they are, and makes it so that only Claude accounts in your Enterprise organization can connect supported connectors using an email address on your verified domains. Learn more about **[claiming and migrating accounts on your domain](../21-Account-Support/claim-and-migrate-accounts-on-your-domain.md)**.

### What should someone do if their connection is blocked?

Sign in to your organization's Claude account and make the connection there. Anyone who doesn't have a Claude account in your organization should contact their admin for access.

### Are all connections on a supported connector covered?

Coverage depends on the identity information each service provides. In a small number of cases (for example, some Canva Education (.edu) accounts) the service does not share the identity details needed to apply the restriction, and those connections are allowed as normal. Where the restriction can't be evaluated, the connection is allowed rather than blocked.

---

## Roles and permissions | Claude Help Center

Source: https://support.claude.com/en/articles/9267276-roles-and-permissions
Path: 13-Enterprise-Admin/roles-and-permissions.md

# Roles and permissions

Roles and permissions that can be set for members of your plan


What an individual can see and do in their Team or Enterprise plan account is dictated by their role. Roles are provisioned with the following permissions.

**Important notes about the Primary Owner role:**

- Team and Enterprise organizations can only have one Primary Owner.

- The Primary Owner seat uses one of your plan's licenses.

- Your organization's Primary Owner can be a service account that isn't tied to an individual user.

- If you're unsure of your organization's Primary Owner, you can check by going to **[Settings \> Account](https://claude.ai/settings/account)**.

**About custom roles (Enterprise plans only):** Enterprise plans support custom roles, which let you control feature access at the group level. Members with roles set to “Custom” have no default permissions—their access is determined entirely by the custom roles assigned to their groups. Because these members have no built-in permissions, they don't appear in the tables below.

To learn more, refer to the following articles:

- **[Manage custom roles on Enterprise plans](../17-Billing-Plans/manage-custom-roles-on-enterprise-plans.md)**

- **[Manage groups and group spend limits on Enterprise plans](../17-Billing-Plans/manage-groups-and-group-spend-limits-on-enterprise-plans.md)**

## Billing

[TABLE]

## Chat controls

[TABLE]

## Features and integrations

[TABLE]

## Membership management

[TABLE]

## Prioritized support routing (Enterprise plan only)

[TABLE]

## Security and data controls (Team and Enterprise plans)

[TABLE]

## Security and data controls (Enterprise plan only)

[TABLE]

## Usage analytics (Enterprise plans)

[TABLE]

## Usage analytics (Team plans)

[TABLE]

---

## Security - Claude Code Docs

Source: https://code.claude.com/docs/en/security
Path: 13-Enterprise-Admin/security.md

## On this page

- [How we approach security](#how-we-approach-security)
  - [Security foundation](#security-foundation)
  - [Permission-based architecture](#permission-based-architecture)
  - [Built-in protections](#built-in-protections)
  - [User responsibility](#user-responsibility)
- [Protect against prompt injection](#protect-against-prompt-injection)
  - [Core protections](#core-protections)
  - [Privacy safeguards](#privacy-safeguards)
  - [Additional safeguards](#additional-safeguards)
- [MCP security](#mcp-security)
- [IDE security](#ide-security)
- [Cloud execution security](#cloud-execution-security)
- [Security best practices](#security-best-practices)
  - [Working with sensitive code](#working-with-sensitive-code)
  - [Team security](#team-security)
  - [Reporting security issues](#reporting-security-issues)
- [Related resources](#related-resources)

Security and data

# Security

Copy pageCopy page

Learn about Claude Code’s security safeguards and best practices for safe usage.

Copy pageCopy page


[​](#how-we-approach-security)

How we approach security


[​](#security-foundation)

Security foundation

Your code’s security is paramount. Claude Code is built with security at its core, developed according to Anthropic’s comprehensive security program. Learn more and access resources (SOC 2 Type 2 report, ISO 27001 certificate, etc.) at [Anthropic Trust Center](https://trust.anthropic.com).


[​](#permission-based-architecture)

Permission-based architecture

A session’s permission mode sets which actions Claude can take without asking you first. Auto mode is the built-in starting permission mode for interactive terminal and VS Code sessions. [Which mode a session starts in](../02-Claude-Code-CLI/permission-modes.md#which-mode-a-session-starts-in) covers earlier versions, other surfaces, and the settings that change the starting permission mode.

- **Auto mode**: A separate classifier model reviews actions instead of you and blocks the ones it judges unsafe. [How the classifier evaluates actions](../02-Claude-Code-CLI/permission-modes.md#how-the-classifier-evaluates-actions) lists which actions Claude Code approves outright, which it sends to the classifier, and which Claude Code still asks you about. Your explicit ask and deny rules still apply, and your organization can [turn auto mode off](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode)
- **Manual mode**: Claude Code starts with read-only permissions. When it needs to edit files, run tests, or execute commands, it asks you first, and you choose whether to approve the action once or allow it from then on. It runs a built-in set of [read-only commands](../02-Claude-Code-CLI/permissions.md#read-only-commands) such as `ls`, `cat`, and `git status` without asking

You and your organization configure these permissions directly. For detailed permission configuration, see [Permissions](../02-Claude-Code-CLI/permissions.md).


[​](#built-in-protections)

Built-in protections

To mitigate risks in agentic systems:

- **Sandboxed bash tool**: [Sandbox](../02-Claude-Code-CLI/sandboxing.md) bash commands with filesystem and network isolation, reducing permission prompts while maintaining security. Configure with `/sandbox` to define boundaries where Claude Code can work autonomously
- **Working directory boundary**: In Manual mode, Claude Code asks you before its file tools read or write outside the folder where it was started and its subfolders. The boundary is a permission prompt, so a Bash command you approve can still write anywhere your user account can
  - To read a folder without the prompt, add it as an [additional directory](../02-Claude-Code-CLI/permissions.md#working-directories)
  - To restrict Bash commands at the operating system level, turn on [sandboxing](../02-Claude-Code-CLI/sandboxing.md#filesystem-isolation)
- **Prompt fatigue mitigation**: Support for allowlisting frequently used safe commands per-user, per-codebase, or per-organization
- **Accept Edits mode**: Auto-approves file edits and a fixed set of filesystem Bash commands like `mkdir`, `touch`, `rm`, `mv`, `cp`, and `sed` for paths in the working directory. Other Bash commands and out-of-scope paths still prompt


[​](#user-responsibility)

User responsibility

You’re responsible for reviewing proposed code and commands for safety before approval.


[​](#protect-against-prompt-injection)

Protect against prompt injection

Prompt injection is a technique where an attacker attempts to override or manipulate an AI assistant’s instructions by inserting malicious text. Claude Code includes several safeguards against these attacks:


[​](#core-protections)

Core protections

- **Permission system**: In Manual mode, sensitive operations require explicit approval
- **Network command approval**: Commands that fetch content from the web such as `curl` and `wget` are not auto-approved by default. In Manual mode they prompt like any other non-read-only Bash command, so you can still approve once or add an explicit allow rule like `Bash(curl *)`. To stop Claude from running them, add them to [`permissions.deny`](../02-Claude-Code-CLI/permissions.md#tool-specific-permission-rules). A deny rule matches the command [as written](../02-Claude-Code-CLI/permissions.md#bash-rule-limits); for network enforcement that doesn’t depend on the command text, see [sandbox network isolation](../02-Claude-Code-CLI/sandboxing.md#network-isolation)


[​](#privacy-safeguards)

Privacy safeguards

We have implemented several safeguards to protect your data, including:

- Limited retention periods for sensitive information (see the [Privacy Center](https://privacy.anthropic.com/en/articles/10023548-how-long-do-you-store-my-data) to learn more)
- Restricted access to user session data
- User control over data training preferences. Consumer users can change their [privacy settings](https://claude.ai/settings/privacy) at any time.

For full details, please review our [Commercial Terms of Service](https://www.anthropic.com/legal/commercial-terms) (for Team, Enterprise, and API users) or [Consumer Terms](https://www.anthropic.com/legal/consumer-terms) (for Free, Pro, and Max users) and [Privacy Policy](https://www.anthropic.com/legal/privacy).


[​](#additional-safeguards)

Additional safeguards

- **Network request approval**: In Manual mode, most tools that make network requests require user approval by default
- **Web page summaries**: For most fetches, WebFetch runs a separate model call over the page, and Claude receives that call’s answer instead of the raw page. See [WebFetch tool behavior](../02-Claude-Code-CLI/tools-reference.md#webfetch-tool-behavior)
- **Trust verification**: In an interactive session, Claude Code shows the workspace trust dialog when you start it in a folder you haven’t trusted. Servers in a project’s `.mcp.json` have their own approval prompt, and [Project scope](../06-MCP-Tools/General/mcp.md#project-scope) lists the sessions that skip it
  - Note: A `-p` session shows neither prompt. [What runs before you trust a folder](../02-Claude-Code-CLI/permissions.md#what-runs-before-you-trust-a-folder) lists what a repository’s files can run there
  - Note: When you start Claude Code directly in your home directory, trust acceptance is held for the current session only and is not written to disk, so the prompt reappears on each launch. There is no setting to persist it. Start Claude Code from a project subdirectory instead, where trust acceptance is saved per directory
- **Command injection detection**: In Manual mode, Claude Code asks before running a Bash command it can’t fully analyze. An allow rule for part of a command, such as `Bash(git *)`, doesn’t skip that prompt. [Sandboxed commands](../02-Claude-Code-CLI/permissions.md#how-permissions-interact-with-sandboxing) can run without it
- **Fail-closed matching**: In Manual mode, unmatched commands require approval by default
- **Secure credential storage**: API keys and tokens are stored in the macOS Keychain when available. On Linux they’re stored in a file with mode `0600`, and on Windows in a file that inherits the access controls of your user profile directory. See [Credential Management](../02-Claude-Code-CLI/authentication.md#credential-management)

**Windows WebDAV security risk**: When running Claude Code on Windows, we recommend against enabling WebDAV or allowing Claude Code to access paths such as `\\*` that may contain WebDAV subdirectories. [WebDAV has been deprecated by Microsoft](https://learn.microsoft.com/en-us/windows/whats-new/deprecated-features#:~:text=The%20Webclient%20(WebDAV)%20service%20is%20deprecated) due to security risks. Enabling WebDAV may allow Claude Code to trigger network requests to remote hosts, bypassing the permission system.

**Best practices for working with untrusted content**:

1.  Review suggested commands before approval
2.  Avoid piping untrusted content directly to Claude
3.  Verify proposed changes to critical files
4.  Use virtual machines (VMs) to run scripts and make tool calls, especially when interacting with external web services
5.  Report suspicious behavior with `/feedback`

While these protections significantly reduce risk, no system is completely immune to all attacks. Always maintain good security practices when working with any AI tool.


[​](#mcp-security)

MCP security

You can connect Claude Code to Model Context Protocol (MCP) servers. Project-scoped servers are defined in `.mcp.json`, which you can check into source control. Servers at [other scopes](../06-MCP-Tools/General/mcp.md#mcp-installation-scopes) and [claude.ai connectors](../06-MCP-Tools/General/mcp.md#how-connectors-reach-claude-code) are configured outside the repository, and plugins can add servers too, so reviewing `.mcp.json` doesn’t show every server a session can load. To restrict which servers run in your organization, see [Managed MCP configuration](../06-MCP-Tools/General/managed-mcp.md). We encourage either writing your own MCP servers or using MCP servers from providers that you trust. You are able to configure Claude Code permissions for MCP servers. Anthropic reviews connectors against its [listing criteria](https://claude.com/docs/connectors/building/review-criteria) before adding them to the [Anthropic Directory](https://claude.ai/directory), but does not security-audit or manage any MCP server.


[​](#ide-security)

IDE security

See [The built-in IDE MCP server](../03-IDE-Integrations/vs-code.md#the-built-in-ide-mcp-server) for more information on running Claude Code in an IDE.


[​](#cloud-execution-security)

Cloud execution security

When you use [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md), additional security controls are in place. Sessions your organization routes to a [self-hosted environment](self-hosted-environments.md) run on your own infrastructure, where isolation, network egress, and git credentials are your deployment’s responsibility. In Anthropic-hosted environments:

- **Isolated virtual machines**: Each cloud session runs in an isolated, Anthropic-managed VM
- **Network access controls**: Network access is limited by default and can be configured to be disabled or allow only specific domains
- **Credential protection**: GitHub credentials are stored encrypted on Anthropic’s servers and never enter the session VM. The VM holds a short-lived credential scoped to that session, and GitHub traffic goes through an [Anthropic proxy](../02-Claude-Code-CLI/cloud-environments.md#github-proxy) that attaches the GitHub credential on the server side. See [GitHub authentication options](../02-Claude-Code-CLI/claude-code-on-the-web.md#github-authentication-options) for how you grant access
- **Push restrictions**: The [GitHub proxy](../02-Claude-Code-CLI/cloud-environments.md#github-proxy) rejects branch deletions and pushes of anything other than a branch, such as a tag. GitHub decides which branches a session can update by applying your repository’s branch protection rules and rulesets to the GitHub access you connected. A rule that access can bypass doesn’t block a session’s push
- **Audit logging**: All operations in cloud sessions are logged for compliance and audit purposes
- **Automatic cleanup**: Session VMs are reclaimed after a period of inactivity
- **Deletion**: You can [delete a session](../02-Claude-Code-CLI/claude-code-on-the-web.md#delete-sessions) at any time. See [Cloud execution data flow](data-usage.md#cloud-execution-data-flow-and-dependencies) for what Anthropic stores for a cloud session

For more details on cloud execution, see [Use Claude Code in the cloud](../02-Claude-Code-CLI/claude-code-on-the-web.md); to configure network access for cloud sessions, see [Configure cloud environments](../02-Claude-Code-CLI/cloud-environments.md#network-access). [Remote Control](../02-Claude-Code-CLI/remote-control.md) sessions work differently: the web interface connects to a Claude Code process running on your local machine. All code execution and file access stays local, and session traffic travels through the Anthropic API over TLS; while connected, the session transcript is stored on Anthropic servers to sync the conversation across devices, as described in [Connection and security](../02-Claude-Code-CLI/remote-control.md#connection-and-security). No cloud VMs or sandboxing are involved. The connection uses multiple short-lived, narrowly scoped credentials, each limited to a specific purpose and expiring independently, to limit the blast radius of any single compromised credential.


[​](#security-best-practices)

Security best practices


[​](#working-with-sensitive-code)

Working with sensitive code

- Review all suggested changes before approval
- Use project-specific permission settings for sensitive repositories
- For additional isolation, run the whole Claude Code (local mode) process inside the [sandbox runtime](../02-Claude-Code-CLI/sandbox-environments.md#sandbox-runtime) or a [dev container](../02-Claude-Code-CLI/devcontainer.md)
- Regularly audit your permission settings with `/permissions`


[​](#team-security)

Team security

- Use [managed settings](../02-Claude-Code-CLI/settings.md#where-settings-live) to enforce organizational standards
- Share approved permission configurations through version control
- Train team members on security best practices
- Monitor Claude Code usage through [OpenTelemetry metrics](monitoring-usage.md)
- Audit or block settings changes during sessions with [`ConfigChange` hooks](../07-Hooks/hooks.md#configchange)


[​](#reporting-security-issues)

Reporting security issues

If you discover a security vulnerability in Claude Code:

1.  Do not disclose it publicly
2.  Report it through our [HackerOne program](https://hackerone.com/4f1f16ba-10d3-4d09-9ecc-c721aad90f24/embedded_submissions/new)
3.  Include detailed reproduction steps
4.  Allow time for us to address the issue before public disclosure


[​](#related-resources)

Related resources

- [Security guidance plugin](../02-Claude-Code-CLI/security-guidance.md): have Claude review and fix vulnerabilities in its own code changes during the session
- [`/security-review`](../02-Claude-Code-CLI/commands.md#all-commands): run an on-demand security pass over the changes on your current branch
- [Sandbox environments](../02-Claude-Code-CLI/sandbox-environments.md): compare isolation approaches and choose one for your threat model
- [Sandboxing](../02-Claude-Code-CLI/sandboxing.md): filesystem and network isolation for Bash commands
- [Permissions](../02-Claude-Code-CLI/permissions.md): configure permissions and access controls
- [Monitoring usage](monitoring-usage.md): track and audit Claude Code activity
- [Development containers](../02-Claude-Code-CLI/devcontainer.md): secure, isolated environments
- [Anthropic Trust Center](https://trust.anthropic.com): security certifications and compliance
- [CISO’s guide to agentic AI](https://claude.com/blog/ciso-guide-to-agentic-ai): a security leader’s framework for assessing agentic AI deployments

---

## Customize sessions in self-hosted environments - Claude Code Docs

Source: https://code.claude.com/docs/en/self-hosted-environments-configuration
Path: 13-Enterprise-Admin/self-hosted-environments-configuration.md

## On this page

- [Wrapper scripts](#wrapper-scripts)
  - [Give a default to variables that can be unset](#give-a-default-to-variables-that-can-be-unset)
  - [Quote the arguments and variables your script expands](#quote-the-arguments-and-variables-your-script-expands)
  - [Keep stdin and file descriptor 3 attached](#keep-stdin-and-file-descriptor-3-attached)
  - [Pass the system prompt flags through](#pass-the-system-prompt-flags-through)
  - [Provision credentials scoped to the session creator](#provision-credentials-scoped-to-the-session-creator)
- [Lifecycle hooks](#lifecycle-hooks)
  - [checkout](#checkout)
  - [Get git credentials in the hook](#get-git-credentials-in-the-hook)
  - [When the hook fails](#when-the-hook-fails)
  - [post-session](#post-session)
  - [Hook timing when the runner releases a session](#hook-timing-when-the-runner-releases-a-session)
  - [Git configuration inside lifecycle hooks](#git-configuration-inside-lifecycle-hooks)
  - [command](#command)
- [On-demand runners](#on-demand-runners)
  - [The spawn-runner hook](#the-spawn-runner-hook)
  - [Make on-demand runners exit when idle](#make-on-demand-runners-exit-when-idle)
  - [Send a background process’s output to a file](#send-a-background-process%E2%80%99s-output-to-a-file)
  - [Exit the hook without waiting for the runner](#exit-the-hook-without-waiting-for-the-runner)
  - [Keep transient failures retryable in a shell hook](#keep-transient-failures-retryable-in-a-shell-hook)
- [Send model requests to Bedrock or Agent Platform](#send-model-requests-to-bedrock-or-agent-platform)
  - [What differs from sessions on the Anthropic API](#what-differs-from-sessions-on-the-anthropic-api)
- [MCP servers](#mcp-servers)
  - [Wait for MCP servers before the first turn](#wait-for-mcp-servers-before-the-first-turn)
  - [Turn off built-in session tools](#turn-off-built-in-session-tools)
- [Prompt sessions to push their work](#prompt-sessions-to-push-their-work)
  - [Submodules and the Stop hook](#submodules-and-the-stop-hook)
  - [Replace an earlier copy of the Stop hook script](#replace-an-earlier-copy-of-the-stop-hook-script)
- [Permissions and tool approval](#permissions-and-tool-approval)
  - [How each session’s config is assembled](#how-each-session%E2%80%99s-config-is-assembled)
  - [Repository settings in sessions with several repositories](#repository-settings-in-sessions-with-several-repositories)
  - [Repository-committed permission rules](#repository-committed-permission-rules)
- [What’s next](#what%E2%80%99s-next)

Self-hosted environments

# Customize sessions in self-hosted environments

Copy pageCopy page

Customize self-hosted environment sessions with wrapper scripts for per-session credentials, lifecycle hooks, and on-demand runner spawning.

Copy pageCopy page

Self-hosted environments are in public beta on Team and Enterprise plans; an [Owner](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) enables them by turning on **Allow self-hosted environments** on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments). This page assumes a working runner; see the [quickstart](self-hosted-environments-quickstart.md) for setup and [Deploy to production](self-hosted-environments-deploy.md) for the fleet recipes.

A [self-hosted environment](self-hosted-environments.md) runs Claude Code [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) on your own infrastructure, executed by a runner process you deploy. With no configuration, that runner clones the session’s repository, spawns Claude Code, and cleans up. This page is for the platform engineer operating the runners: it covers the extension points for when those defaults don’t fit, from per-session credential provisioning to replacing checkout entirely. Wrappers and hooks run as executable files on the runner host, which is Linux or macOS, and the examples on this page assume a POSIX shell. A few hook environment variables on this page still use `pool`, such as `CLAUDE_RUNNER_POOL_ID`; the CLI flag and env var names use `environment`, such as `--environment-secret-file`.


[​](#wrapper-scripts)

Wrapper scripts

Use a wrapper script when each session needs setup the runner can’t do on its own: provisioning short-lived credentials scoped to the session creator, exporting environment-specific secrets, preparing language toolchains, or applying resource limits around the child process. The runner starts your wrapper in place of the Claude Code binary, once per session. End the wrapper by `exec`-ing into `$CLAUDE_RUNNER_CLAUDE_BIN`, the runner’s own binary, so signals and exit codes propagate correctly. Point `--exec-path`, or `SELF_HOSTED_RUNNER_EXEC_PATH`, at the wrapper when you start the runner:

```python
claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret --exec-path /etc/claude/session-wrapper.sh
```

The runner sets the following in the wrapper’s environment:

| Variable                              | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|:--------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CLAUDE_CODE_SESSION_ACCESS_TOKEN`    | The session JWT, prefixed `sk-ant-cc-`. Its `act` claim identifies the session creator, with the creator’s email when the creating surface recorded it. The value is the token at spawn time; refreshes arrive over the child’s stdin, so a wrapper sees only the initial value. See [Verify session identity](self-hosted-environments-identity.md).                                                                                                                                                                                                                  |
| `CCR_SESSION_ACCOUNT_EMAIL`           | The session creator’s email, pre-extracted by the runner from the token’s `act.email` claim without signature verification. Suitable for labelling, such as commit trailers. When the email gates credential issuance, verify the token and read the claim from it instead. See [Provision credentials scoped to the session creator](#provision-credentials-scoped-to-the-session-creator). Unset when the token carries no creator email, for example in sessions your organization’s service identity creates. Treat as personally identifiable information.              |
| `CLAUDE_RUNNER_CLIENT_PLATFORM`       | The client surface that created the session, such as `web_claude_ai`, `desktop_app`, `ios`, `claude_code_cli`, or `scheduled_trigger`. Anthropic records the value once at session creation, so the wrapper and every lifecycle hook see the same value. Use it for adoption analytics and labelling only, not as an authorization signal. Unset when the session has no recorded or recognized surface. Requires Claude Code v2.1.229 or later.                                                                                                                             |
| `CLAUDE_RUNNER_CLAUDE_BIN`            | Absolute path to the runner’s own Claude Code binary. End your wrapper with `exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@"` to pass control to the pinned binary without hardcoding an install path.                                                                                                                                                                                                                                                                                                                                                                                 |
| `CLAUDE_CODE_REMOTE_SESSION_ID`       | Session ID in the tagged `cse_...` form. This is the same session the [lifecycle hooks](#lifecycle-hooks) see as `CLAUDE_RUNNER_SESSION_ID` in `session_...` form; the UUID variables match across both, and substituting the `cse_` prefix with `session_` yields the ID shown in the session URL.                                                                                                                                                                                                                                                                          |
| `CLAUDE_CODE_REMOTE_SESSION_UUID`     | The same session ID in canonical UUID form, for systems that key on UUIDs.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| `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.                                                                                                                                                                                                                                                                                                                                                                      |
| `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.                                                                                                                                                                                                                                                                                                                                     |
| `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.                                                                                                                                                                    |
| `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`](self-hosted-environments-reference.md#runner-cli-flags); see [Reuse a pre-warmed checkout](self-hosted-environments-deploy.md#reuse-a-pre-warmed-checkout). |
| `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`](../02-Claude-Code-CLI/cli-reference.md#cli-flags) that isn’t in the update. Requires Claude Code v2.1.299 or later.                                                                                            |
| `ANTHROPIC_BASE_URL`                  | The API base URL the child will use, delivered by the control plane per session and normally `https://api.anthropic.com`. Don’t override it: the session’s inference credential is an Anthropic-issued OAuth token that other providers don’t accept.                                                                                                                                                                                                                                                                                                                        |
| `CLAUDE_CODE_OAUTH_TOKEN`             | The short-lived OAuth access token the child uses for model inference, with a lifetime of about 30 minutes. The runner re-mints it before expiry and delivers the rotation over the child’s stdin, so a wrapper that doesn’t [keep stdin attached](#keep-stdin-and-file-descriptor-3-attached) sees only the initial value. Don’t rely on your organization’s IP allowlist to bound this token’s use: treat it as a bearer credential that stays usable for roughly 30 minutes if it leaks, and don’t log it, write it to disk, or forward it outside the session container. |

The 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. `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.


[​](#give-a-default-to-variables-that-can-be-unset)

Give a default to variables that can be unset

`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:-}"`.


[​](#quote-the-arguments-and-variables-your-script-expands)

Quote the arguments and variables your script expands

Quote every argument and variable that the runner passes to your wrapper or [`command` hook](#command). Some values contain characters a shell acts on, because they hold JSON, whole sentences, or a [permission rule](../02-Claude-Code-CLI/permissions.md#permission-rule-syntax) such as `Bash(npm run test *)`.

- **Arguments**: pass them on as `"$@"`
- **Variables**: quote each one, as in `"${CLAUDE_CODE_REMOTE_SLACK_THREAD_URL:-}"`. The Slack thread link, for example, can contain `?` and `&`.
- **`eval` and `sh -c` strings**: don’t substitute a value into a string that `eval` or `sh -c` runs, even inside quotes. Have that string reference the variable instead.


[​](#keep-stdin-and-file-descriptor-3-attached)

Keep stdin and file descriptor 3 attached

The child’s stdin is the runner’s control channel. Token rotations and session-end signals arrive on it. The runner also opens a pipe on file descriptor 3 and reads the child’s activity signals from it to drive idle and startup timeouts. A plain `exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@"` preserves both automatically. If your wrapper backgrounds the child with a bare `&`, it severs the child’s stdin. The session looks healthy until the initial OAuth token’s roughly 30-minute lifetime expires, and then every API call that uses the token fails with `401 authentication_error`. If your wrapper must background the child, for example to keep a teardown trap alive, save stdin on file descriptor 4 or higher and re-attach it explicitly:

```python
exec 4<&0
"$CLAUDE_RUNNER_CLAUDE_BIN" "$@" <&4 4<&- &
CHILD=$!
trap 'teardown' EXIT
wait "$CHILD"
```

You can redirect the child’s stdout. Keep file descriptor 3 and stderr attached to the runner:

- **File descriptor 3**: carries the child’s activity signals to the runner. Don’t close it or reuse it in the wrapper.
- **stderr**: when the wrapper or the child exits non-zero, the runner posts the last lines of stderr to the session and prints them in its own log. The session’s user sees those lines, so don’t print secrets to stderr, and remove `set -x` before you deploy the wrapper. If you redirect stderr, sessions still run, but the runner reports a failure with the exit code alone.


[​](#pass-the-system-prompt-flags-through)

Pass the system prompt flags through

The system prompt and appended system prompt that Anthropic’s control plane sends for a session reach your wrapper as file paths, not as inline text. The runner writes each prompt to a file in the session’s config directory, `CLAUDE_CONFIG_DIR`, and passes its path in the arguments your wrapper receives, as [`--system-prompt-file <path>` or `--append-system-prompt-file <path>`](../02-Claude-Code-CLI/cli-reference.md#system-prompt-flags). Runners on Claude Code v2.1.281 or later deliver the prompts as files. Before v2.1.281, the runner passed them as `--system-prompt <text>` and `--append-system-prompt <text>`. In your wrapper script or [`command` hook](#command), handle these flags as follows:

- **Pass them through**: end the wrapper with `exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@"`, which forwards the file flags along with every other argument. Don’t drop or rewrite them. If a session loses a prompt file flag, it runs without the instructions the control plane sent for it.
- **On a runner at v2.1.281 or later, a file flag you append replaces the server’s, never adds to it**: each prompt file flag takes a single value and Claude Code keeps the last occurrence, so if you append `--append-system-prompt-file <path>` after `"$@"`, your file’s contents replace the server’s appended instructions. To add instructions on top of the server’s, put them in the runner image’s `CLAUDE.md`, which the runner [seeds into every session’s user-level config](#how-each-session%E2%80%99s-config-is-assembled).


[​](#provision-credentials-scoped-to-the-session-creator)

Provision credentials scoped to the session creator

Use the `decode-token` subcommand to read claims from the session JWT. It reads the token from an argument, from `CLAUDE_CODE_SESSION_ACCESS_TOKEN`, or from stdin, in that order; see [Verify the token inside the session](self-hosted-environments-identity.md#verify-the-token-inside-the-session) for what it checks. The example below decodes the creator identity, exchanges it for short-lived AWS credentials, and execs into Claude Code:

```python
#!/bin/bash
# Key on the stable Anthropic user ID and require a human creator.
CREATOR_SUB=$("$CLAUDE_RUNNER_CLAUDE_BIN" self-hosted-runner decode-token \
  | jq -re '.act.sub // "" | select(startswith("user:"))') \
  || { echo "decode-token: verification failed or no human creator" >&2; exit 1; }

creds=$(your-sts-helper assume-role --subject "$CREATOR_SUB") \
  || { echo "credential exchange failed" >&2; exit 1; }
eval "$creds"

exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@"
```

Use `jq -re` rather than `jq -r` when the extracted claim gates an auth decision, so an absent claim exits non-zero instead of passing the literal string `null` downstream. Sessions created by an organization service identity, such as bot and agent sessions, carry an `agent:` subject rather than `user:`, so this example refuses them; if your environment serves those sessions, decide explicitly whether the wrapper falls back to a default credential for them instead of exiting. When your credential exchange needs the email instead, read `.act.email` and handle its absence: the token carries it only when the creating surface recorded it, and a [CLI-dispatched session](self-hosted-environments-testing.md#run-the-test-loop) can lack it. For the full claim reference and verification from services outside the runner, see [Verify session identity](self-hosted-environments-identity.md).


[​](#lifecycle-hooks)

Lifecycle hooks

Lifecycle hooks replace stages of the runner’s per-session pipeline with your own scripts. Point the runner at a directory of hooks with `--hooks-dir <path>`, or `SELF_HOSTED_RUNNER_HOOKS_DIR`. The runner looks for executable files with well-known names; any hook that isn’t present falls through to the built-in behavior, so you only write the ones you need. Hooks run with the runner’s own privileges, and session children share that UID, so mount the hooks directory read-only, or bake it into the image, so session code can’t modify it; see the [hardening section](self-hosted-environments-deploy.md#harden-your-deployment). These hooks are distinct from [Claude Code hooks](../07-Hooks/hooks.md), which run inside the session; lifecycle hooks run on the runner, around the session.


[​](#checkout)

checkout

Runs once per repository, in place of the runner’s built-in clone and fetch. Use the hook to clone from a read-through mirror that you reach over HTTPS or SSH, 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:

| Variable                                                     | Description                                                                                                                                                                                                                                                                    |
|:-------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CLAUDE_RUNNER_REPO_URL`                                     | Repository URL to clone, after any `--git-host-rewrite` and `--git-ssh-rewrite` have been applied                                                                                                                                                                              |
| `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.                                                                                          |
| `CLAUDE_RUNNER_CHECKOUT_PATH`                                | Absolute path where the working tree must be left                                                                                                                                                                                                                              |
| `CLAUDE_RUNNER_SESSION_ID`                                   | Session ID in the tagged `session_...` form, for logging and correlation                                                                                                                                                                                                       |
| `CLAUDE_RUNNER_SESSION_UUID`                                 | The same session ID in canonical UUID form                                                                                                                                                                                                                                     |
| `CLAUDE_RUNNER_API_BASE_URL`                                 | Anthropic API base URL for session-scoped calls                                                                                                                                                                                                                                |
| `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, so reference it as `"${CLAUDE_RUNNER_CLIENT_PLATFORM:-}"` under `set -u`. Requires Claude Code v2.1.229 or later. |
| `CLAUDE_CODE_SESSION_ACCESS_TOKEN`                           | The session access token, for session-scoped API calls                                                                                                                                                                                                                         |
| `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.                                                                        |

The script must leave a working tree at `CLAUDE_RUNNER_CHECKOUT_PATH` checked out at the requested revision. A detached HEAD works, because the runner creates the session’s working branch on top. After your hook returns, the runner verifies that `CLAUDE_RUNNER_CHECKOUT_PATH` contains a `.git`. 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).


[​](#get-git-credentials-in-the-hook)

Get git credentials in the hook

The runner doesn’t pass a git credential to the hook. The `decode-token` subcommand isn’t available here either, because `CLAUDE_RUNNER_CLAUDE_BIN` isn’t set in the checkout-hook environment. Mint a per-session clone credential from the session’s identity instead, or fall back to the host’s own git authentication:

- **Per-session clone credential**: verify `CLAUDE_CODE_SESSION_ACCESS_TOKEN` with a standard JWT library against the JWKS endpoint under `CLAUDE_RUNNER_API_BASE_URL`, as described in [Verify the token from your service](self-hosted-environments-identity.md#verify-the-token-from-your-service). Then have your credential service issue a short-lived clone credential for the identity in the token’s `act` claim. Key that credential on `act.sub`, and don’t require `act.email`.
- **Host git authentication**: use whatever git authentication the host already has, such as an SSH agent, credential helper, or `.netrc`.


[​](#when-the-hook-fails)

When the hook fails

The hook fails when it exits non-zero, or exits 0 without leaving a usable checkout behind:

- **A repository the session pushes results to**: the runner fails the session, and on a non-zero exit surfaces the tail of the script’s stderr to the user. Write the reason to stderr as one short line, write it last, and then exit non-zero. Don’t print secrets to stderr, and remove `set -x` before you deploy the hook. If git’s `couldn't find remote ref` appears in roughly the last 4 KB of the hook’s stderr, the runner reports a deleted branch instead. The session’s error message then says the branch no longer exists on the remote and asks the user to restore it. If that branch exists, see [A session fails with `no longer exists on the remote` or `couldn't find remote ref`](self-hosted-environments-deploy.md#no-longer-exists-on-the-remote).
- **A repository the session only reads from**, such as a repository added to a running session: the runner skips it and continues with the remaining repositories. The runner posts a `Skipped` step to the session and removes whatever the hook left at the checkout path. When the `Skipped` step mentions the checkout hook, find the failure detail on the `[runner:warn] checkout hook failed` line in the runner’s log. When the step says a source ref `no longer exists on remote`, see [A session fails with `no longer exists on the remote` or `couldn't find remote ref`](self-hosted-environments-deploy.md#no-longer-exists-on-the-remote). If skipping leaves the session with no repository at all, the runner fails the session anyway.

When the hook succeeds, the runner removes the checkout path after the session ends.


[​](#post-session)

post-session

Runs once per session, after the Claude Code child has exited and before the runner tears the workspace down. This hook is your only chance to save uncommitted work: at `--capacity` above one, the runner deletes per-session worktrees right after the hook returns, and at `--capacity 1` the reused [canonical clone](self-hosted-environments-deploy.md#reuse-a-pre-warmed-checkout) is hard-reset when the next session starts, so uncommitted tracked changes don’t survive on either path. Typical uses are pushing a snapshot branch of uncommitted changes, archiving logs, or emitting a session-ended event to your own systems. The hook fires on every session end where a child process was spawned, whatever the cause; the `CLAUDE_RUNNER_EXIT_REASON` values below enumerate the cases. It can’t fire when the runner terminates abruptly, such as a VM preemption or a power loss; if you need guarantees against abrupt termination, snapshot periodically from inside the session with a Claude Code `PostToolUse` hook instead. The runner sets:

| Variable                                                     | Description                                                                                                                                                                                                                                                                    |
|:-------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CLAUDE_RUNNER_SESSION_ID`                                   | Session ID in the tagged `session_...` form                                                                                                                                                                                                                                    |
| `CLAUDE_RUNNER_SESSION_UUID`                                 | The same session ID in canonical UUID form                                                                                                                                                                                                                                     |
| `CLAUDE_RUNNER_EXIT_REASON`                                  | How the session ended; see the values below the table                                                                                                                                                                                                                          |
| `CLAUDE_RUNNER_WORKSPACE_PATHS`                              | Colon-separated absolute paths of the session’s working trees. Empty for zero-repo sessions.                                                                                                                                                                                   |
| `CLAUDE_RUNNER_DEBUG_LOG_PATH`                               | Path to the session’s debug log, still on disk while the hook runs                                                                                                                                                                                                             |
| `CLAUDE_RUNNER_API_BASE_URL`                                 | Anthropic API base URL for session-scoped calls                                                                                                                                                                                                                                |
| `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, so reference it as `"${CLAUDE_RUNNER_CLIENT_PLATFORM:-}"` under `set -u`. Requires Claude Code v2.1.229 or later. |
| `CLAUDE_CODE_SESSION_ACCESS_TOKEN`                           | The session access token, for session-scoped API calls                                                                                                                                                                                                                         |
| `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.                                                                        |

`CLAUDE_RUNNER_EXIT_REASON` takes one of four values:

- `completed`: the session ended cleanly. The Claude Code process exited normally, or exited by itself after the session was archived or deleted.
- `failed`: the Claude Code process crashed, or setup failed after it started.
- `interrupted`: the runner stopped the session, in one of these cases:
  - The runner released the session to free the slot.
  - The session timed out at startup.
  - The server moved the session off this runner.
  - The runner’s poll noticed an archive or delete before the process exited.
  - The runner was draining.
  - The session outlasted its [`--kill-session-after-min`](self-hosted-environments-reference.md#runner-cli-flags) limit.
- `abandoned`: reserved for a session another runner claimed. The hook doesn’t currently fire in that case.

If you compare hook receipts with the [session lifecycle counters](self-hosted-environments-reference.md#session-lifecycle-counter-semantics), expect some `interrupted` receipts to count as `completed` there. The counters count a release, a startup timeout, a server move, and an archive or delete that the runner’s poll noticed first as `completed`, because the runner handed the slot back cleanly. The hook’s exit status never affects the session outcome; a failure is logged and ignored. The runner waits up to `--post-session-hook-timeout-sec`, 60 seconds by default, on every session end including runner shutdown. This example saves uncommitted work to a rescue branch:

```python
#!/usr/bin/env bash
set -u
export GIT_ALLOW_PROTOCOL=${GIT_ALLOW_PROTOCOL:-https:http:ssh}
IFS=':'
# -c overrides beat repo-local settings, blocking session-written fsmonitor,
# hook-path, and gpg-program config from executing code with the hook's
# privileges. -c commit.gpgsign=false also leaves these rescue commits
# unsigned under --configure-git.
# Repo-local credential.helper and pushurl still apply, and on a runner
# before v2.1.280 so does core.sshCommand; see the note below the script
# before you give this push a credential.
g() { git -c core.fsmonitor=false -c core.hooksPath=/dev/null \
        -c commit.gpgsign=false "$@"; }
for ws in $CLAUDE_RUNNER_WORKSPACE_PATHS; do
  cd "$ws" 2>/dev/null || continue
  [ -z "$(g status --porcelain 2>/dev/null)" ] && continue
  g add -A
  g commit -q -m "runner snapshot: $CLAUDE_RUNNER_SESSION_ID ($CLAUDE_RUNNER_EXIT_REASON)" || continue
  g push -q origin "HEAD:refs/heads/rescue/$CLAUDE_RUNNER_SESSION_ID" || true
done
```

The `GIT_ALLOW_PROTOCOL` line in the script limits git to HTTPS, HTTP, and SSH remotes. If the runner’s environment already sets a non-empty `GIT_ALLOW_PROTOCOL` list of its own, the script keeps that list. The hook pushes with whatever git credentials are available in its own environment on the runner host. Under the [no-credentials-in-the-image posture](self-hosted-environments-deploy.md#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](self-hosted-environments-identity.md) describes. Treat any credential your hook gives git as one a session can obtain, and mint it so that it can do no more than this push. Git in your hook reads configuration files that a session can write, and a credential helper or filter driver named in one of them runs with your hook’s privileges. Settings in those files can also change where a push goes, whatever remote you name. For the git settings the runner fixes in your hook and the ones it leaves to those files, see [Git configuration inside lifecycle hooks](#git-configuration-inside-lifecycle-hooks).


[​](#hook-timing-when-the-runner-releases-a-session)

Hook timing when the runner releases a session

A released session can resume on another runner. On a runner on v2.1.236 or later, what the session was doing at release decides whether it can resume before this hook finishes:

- **Idle after a turn, or timed out at startup**: the runner stops the child and runs this hook to completion. Only then does it release the session. A user message sent while the hook runs can’t resume the session on another runner before the hook finishes.
- **Waiting for the user to answer a prompt, such as a permission prompt**: the runner releases the session first, then runs this hook. A user message sent while the hook runs can resume the session on another runner before the hook finishes.

This applies whenever the runner releases a session: at the idle timeout, at the [`--retire-at`](self-hosted-environments-reference.md#runner-cli-flags) time, and, on a runner on v2.1.260 or later, at a session’s [`--kill-session-after-min`](self-hosted-environments-reference.md#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. During a `SIGTERM` drain, the runner holds the session lease until the hook finishes; see [Shutdown timing](self-hosted-environments-deploy.md#shutdown-timing).


[​](#git-configuration-inside-lifecycle-hooks)

Git configuration inside lifecycle hooks

The `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.

- **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.
- **File system monitor**: `core.fsmonitor` is empty, so git in your hook doesn’t run a monitor program that a configuration file names.
- **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`.
- **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.
- **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.
- **Commit signing**: with [`--configure-git`](self-hosted-environments-deploy.md#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`.

To change one of these settings, use the runner’s environment or a `git -c` option inside the hook:

- **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.
- **Git environment variables**: the runner leaves `GIT_ALLOW_PROTOCOL`, `GIT_SSH_COMMAND`, and `GIT_ASKPASS` as you set them in its environment.
- **`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.

Git 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. Before 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`.


[​](#command)

command

Runs once per session after checkout, in place of the built-in child spawn. The hook receives the same environment as a [wrapper script](#wrapper-scripts) and should `exec` into `"$CLAUDE_RUNNER_CLAUDE_BIN"` the same way. Use the `command` hook to keep all customization in one hooks directory; use `--exec-path` when the wrapper lives elsewhere. If `--exec-path` is also set, the flag takes precedence and the `command` hook is ignored. Always `exec` the runner’s own binary rather than a PATH-resolved `claude`; otherwise you defeat [version pinning](self-hosted-environments-deploy.md#pin-the-version).


[​](#on-demand-runners)

On-demand runners

Instead of running a fixed fleet, you can boot one runner per session. The orchestrator is a separate, stateless subcommand that polls Anthropic for spawn requests, one per session that’s queued with no runner available, and runs your `spawn-runner` hook for each. Your hook submits a workload to your platform: a Kubernetes Job, an EC2 instance, a Nomad dispatch. On-demand runners improve credential hygiene. On a fixed fleet, the environment secret lives on every runner host, which is the same host that runs user sessions. With the orchestrator, the environment secret stays only on the orchestrator host, which never runs user code; each spawned runner receives a single-use work order that registers exactly one runner and then expires. To start the orchestrator, pass the environment secret and a hooks directory containing an executable `spawn-runner` script:

```python
claude self-hosted-runner orchestrator \
  --environment-secret-file /etc/claude/environment-secret \
  --hooks-dir /etc/claude/hooks
```

The orchestrator keeps no state between polls, so you can run two or more replicas against the same environment for availability. Each spawn request is claimed server-side by exactly one replica. All replicas must use the same `--expected-spawn-seconds` value; see the [hook contract](#the-spawn-runner-hook).


[​](#the-spawn-runner-hook)

The spawn-runner hook

The orchestrator runs `${hooks-dir}/spawn-runner` once per spawn request. The hook must submit work asynchronously, without waiting for the runner to boot, and return within `--hook-timeout`, 60 seconds by default. The hook receives:

| Variable                              | Description                                                                                                                                                                                                                                                                                                                                                                                                          |
|:--------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `CLAUDE_RUNNER_WORK_ORDER_FILE`       | Path to a temp file containing the signed work-order JWT the new runner registers with. Deleted after the hook exits. Don’t log the file’s contents.                                                                                                                                                                                                                                                                 |
| `CLAUDE_RUNNER_ORDER_ID`              | Opaque idempotency key, unique per spawn request and safe for Kubernetes resource names. Use only the order ID as your provisioner’s dedup key.                                                                                                                                                                                                                                                                      |
| `CLAUDE_RUNNER_SESSION_ID`            | The session this request is for, as the `session_...` ID in the session’s URL. It repeats on every re-request for the session, so use it for logging and routing, not as a dedup key. Empty for pre-warming requests, which boot a standby runner ahead of any specific session when [`--min-idle`](self-hosted-environments-reference.md#orchestrator-cli-flags) is set, so don’t assume the variable is set. |
| `CLAUDE_RUNNER_SESSION_UUID`          | The same session ID in canonical UUID form. Empty for pre-warming requests.                                                                                                                                                                                                                                                                                                                                          |
| `CLAUDE_RUNNER_ATTEMPT`               | A per-session counter to use for logging. It isn’t a retry count or a request count. `0` for pre-warming requests, though a request for a session can carry `0` too.                                                                                                                                                                                                                                                 |
| `CLAUDE_RUNNER_ORDER_SERVER_TIME`     | Server time from the poll response’s HTTP `Date` header. When the hook verifies the work-order JWT’s `exp`, compare against this value instead of the local clock to tolerate skew. Empty when the gateway omitted the header.                                                                                                                                                                                       |
| `CLAUDE_RUNNER_POOL_ID`               | The ID of the environment the new runner should join, in `ccpool_...` form                                                                                                                                                                                                                                                                                                                                           |
| `CLAUDE_RUNNER_ACCOUNT_ID`            | Tagged ID of the account that enqueued the session. Use it for per-account routing, quota, or chargeback, not as a dedup key. Empty when unavailable, and always empty for Claude Tag channel sessions, which no account enqueues.                                                                                                                                                                                   |
| `CLAUDE_RUNNER_ACCOUNT_EMAIL`         | Email of the account that enqueued the session. Empty when unavailable. Treat the email as personally identifiable information and don’t log it.                                                                                                                                                                                                                                                                     |
| `CLAUDE_RUNNER_PRIMARY_REPO_URL`      | URL of the session’s first git source, for routing to a runner with that repository pre-warmed. Empty when the session has no git sources.                                                                                                                                                                                                                                                                           |
| `CLAUDE_RUNNER_PRIMARY_REPO_REVISION` | Revision of the session’s first git source: branch, SHA, tag, or full reference name. Empty when unspecified.                                                                                                                                                                                                                                                                                                        |
| `CLAUDE_RUNNER_REPO_SOURCES`          | JSON array of `{url, revision}` for all the session’s git sources, for hooks that route on a secondary repository. Empty when there are no sources.                                                                                                                                                                                                                                                                  |
| `CLAUDE_RUNNER_CORRELATION_ID`        | The correlation ID supplied at session create, echoed back so the hook can map this work order to the request that created the session. Empty when the session has none.                                                                                                                                                                                                                                             |
| `CLAUDE_RUNNER_CLIENT_PLATFORM`       | The client surface that created the session, such as `web_claude_ai`, `desktop_app`, `ios`, or `scheduled_trigger`, for adoption analytics. Unset when the session has no recorded or recognized surface, and for pre-warming requests; check it with `[ -n "${CLAUDE_RUNNER_CLIENT_PLATFORM:-}" ]`, which stays safe under `set -u`.                                                                                |

The spawned runner registers with the work order in place of the environment secret:

- **Start it with the work order**: point [`--environment-secret-file`](self-hosted-environments-reference.md#runner-cli-flags) at a file containing the work-order JWT, or set `SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET` to the JWT value.
- **Copy the JWT before the hook exits**: the orchestrator deletes the work-order file after the hook exits, so copy the JWT into the workload you submit, such as a Kubernetes Secret on the spawned Job, rather than passing the file path through.
- **Use `--capacity 1` on spawned runners**: a session-bound work order registers exactly one runner bound to that session, so a higher capacity adds slots that never receive work, and the runner logs a warning at startup.
- **Pre-warming work orders register unbound**: the standby runner isn’t bound to a session and claims queued work like a fixed-fleet runner.

The contract has four rules, whichever platform your hook provisions on:

1.  **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.
2.  **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`.
3.  **Use the exit-code contract.** Exit with the status that matches the outcome:
    - **Exit 0**: submitted.
    - **Exit 1, or still running when `--hook-timeout` ends**: retryable failure. The session backs off and is re-offered.
    - **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](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) selects **Retry** on it in the environment’s **Activity** tab.

    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). A pre-warming request has no session to fail: the orchestrator logs a non-zero exit locally only, and the server re-requests the spawn after the `--expected-spawn-seconds` lease expires.
4.  **Set `--expected-spawn-seconds` to at least your p99 time from spawn request to runner registration.** Measure from when the orchestrator receives the spawn request, and include any wait for capacity on your platform as well as boot time. This value is the server-side lease, and the work order expires with it, so a runner whose workload takes longer can’t register. All orchestrator replicas must use the same value.

Everything 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. A 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.


[​](#make-on-demand-runners-exit-when-idle)

Make on-demand runners exit when idle

By 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](self-hosted-environments-reference.md#runner-cli-flags):

| Flag, with an example value     | Pass it to                                                       | What it does                                                                                                                                                                                                                                                       |
|:--------------------------------|:-----------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--release-idle-session-min 30` | Every runner your hook starts                                    | The runner releases its session after that many idle minutes and then exits 0. The session resumes when its user sends their next message, but [work it hadn’t pushed is gone](self-hosted-environments-deploy.md#additional-limitations).                   |
| `--kill-session-after-min 480`  | Every runner your hook starts                                    | Limits each session to that many minutes, as a backstop for [sessions that don’t count as idle](self-hosted-environments-deploy.md#some-sessions-don%E2%80%99t-count-as-idle), so set it above your longest expected session                                 |
| `--exit-if-unused-min 60`       | Standby runners, which your hook starts for pre-warming requests | A standby that never takes a session exits after that many minutes. If you set [`--min-idle`](self-hosted-environments-reference.md#orchestrator-cli-flags), you get a replacement unless the standby was surplus, so use a value well above your boot time. |

Remove the workload when the runner exits, for example by setting `ttlSecondsAfterFinished` on a Kubernetes Job or by having the VM’s start script delete the VM afterwards.


[​](#send-a-background-process’s-output-to-a-file)

Send a background process’s output to a file

If your `spawn-runner` hook leaves a process running in the background, send that process’s output to a file that the hook’s user can create. For example, append `>> "$HOME/claude-runner.log" 2>&1` to the command that starts the process, ahead of any `&`. This hook line shows where the redirect goes, with `your-background-command` standing in for the command your hook backgrounds:

```python
your-background-command >> "$HOME/claude-runner.log" 2>&1 &
```

Without the redirect, the process writes to the hook’s stdout and stderr:

- About a second after the hook exits, the orchestrator logs `exited, but a process it started still has the hook's stdout or stderr open`. From that line on, it logs up to 64 KB more of that output under `[runner:hook:spawn-runner:after-exit jti=<order id>]` and then drops the rest.
- Once the orchestrator exits, the process’s writes there fail.

A runner your hook starts itself runs sessions on the orchestrator host, which holds the [environment secret](self-hosted-environments-deploy.md#harden-your-deployment). Before v2.1.305, a hook that had exited still reached [`--hook-timeout`](self-hosted-environments-reference.md#orchestrator-cli-flags) while a process it started had the hook’s stdout or stderr open, shown as:

- **Activity tab**, in a failed session’s spawn error: `spawn-runner hook timed out after 60000ms`, or the last lines on the hook’s stderr.
- **Orchestrator log**: `timed out after 60000ms, sending SIGTERM to process tree`.


[​](#exit-the-hook-without-waiting-for-the-runner)

Exit the hook without waiting for the runner

If every on-demand runner stops about 60 seconds after its hook starts, and your hook runs again for the same session, check whether the hook waits for the runner. The 60 seconds are the default [`--hook-timeout`](self-hosted-environments-reference.md#orchestrator-cli-flags), so if you set your own, runners stop that long after the hook starts. A hook that starts `claude self-hosted-runner` on the orchestrator’s host and waits for it, in the foreground or with `wait`, is still running when `--hook-timeout` ends. The orchestrator then stops the hook and the processes running under it, including that runner and the session’s Claude process, so a turn that’s still running is cut off.

- **To confirm that the timeout is what stops your runners**: look in the orchestrator’s log for `timed out after 60000ms, sending SIGTERM to process tree` on every spawn request. The `60000` is your `--hook-timeout` in milliseconds.
- **To fix it**: have the hook submit the runner to your platform and exit 0. If you raise `--hook-timeout` instead, the runner only stops later.


[​](#keep-transient-failures-retryable-in-a-shell-hook)

Keep transient failures retryable in a shell hook

In a shell hook that uses `set -e`, a failure that a retry could have cleared can block the session. The hook stops at the failing command and exits with that command’s own status, and the orchestrator applies the exit-code contract to that status. Many failures return a status of 2 or higher, such as `127` when a command isn’t installed and `22` from `curl --fail` on an HTTP error, so they block the session at its first failure. A session the hook has already blocked stays blocked until a user sends it a new message or an [Owner](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) selects **Retry** on it in the environment’s **Activity** tab. To turn such a failure into exit 1 instead, put these lines directly below the hook’s `#!` line, above anything that can fail:

```python
set -e
PERMANENT=; permanent() { printf '%s\n' "$*" >&2; PERMANENT=1; exit 2; }
trap 'rc=$?; [ "$rc" -eq 0 ] || [ -n "${PERMANENT:-}" ] || exit 1' EXIT
```

These lines change how the rest of the hook behaves, so check it for each of these patterns after you add them:

- **Bare `exit 2` or higher**: with the trap set, it becomes exit 1. For an error that no retry can fix, call `permanent` with the reason instead, such as `permanent "namespace claude-runners does not exist"`. Call it in the main shell, not inside `$( )`, `( )`, or a pipe.
- **`exec`**: don’t start the hook’s last command with `exec`, because `exec` replaces the shell and the trap doesn’t run.
- **Second `EXIT` trap**: a second `trap ... EXIT` replaces the first, so merge the two into a single trap. Put your cleanup commands directly after `rc=$?;` and end each with `|| true;`. Cleanup then runs on failure as well as on success, and a failing cleanup command doesn’t set the hook’s exit status. This merged trap shows the shape, with `your-cleanup-command` standing in for your own:

  ``` shiki
  trap 'rc=$?; your-cleanup-command || true; [ "$rc" -eq 0 ] || [ -n "${PERMANENT:-}" ] || exit 1' EXIT
  ```
- **Commands that are allowed to fail**: if the hook didn’t use `set -e` before, it now stops at the first command that returns non-zero, such as a lookup that finds nothing. If the hook acts on the result, make that command the condition of an `if`. If it ignores the result, follow the command with `|| true`.
- **The command that submits the workload**: don’t follow it with `|| true`. With `|| true`, the hook exits 0 even when your platform refuses the submit, for example over quota, and the session waits for a runner that was never started. Name the workload after `CLAUDE_RUNNER_ORDER_ID`, as [the hook’s first rule](#the-spawn-runner-hook) says. An already-exists answer is then a redelivery of the same request, so treat that answer alone as success. For anything else, print the answer to stderr and exit 1, or call `permanent` if no retry can fix it. This example does that for `kubectl create`, with a `job.yaml` that holds only the workload:

  ``` shiki
  if ! out=$(kubectl create -f job.yaml 2>&1); then
    case "$out" in
      *AlreadyExists*) ;;
      *) printf '%s\n' "${out:-kubectl create failed}" >&2; exit 1 ;;
    esac
  fi
  ```

  The orchestrator’s log then shows that message. If the hook creates other objects, name each after the order ID, give it its own command, and treat its already-exists answer as success in the same way. For an object that holds a secret, print a fixed message in place of the command’s output.

To confirm the trap works, add a line directly below the `trap` line that calls a command that doesn’t exist, such as `no-such-command`. Run the hook file from your shell and check that `echo $?` prints `1`, then remove the line.


[​](#send-model-requests-to-bedrock-or-agent-platform)

Send model requests to Bedrock or Agent Platform

If your organization needs model requests to go through its own AWS or Google Cloud account, configure the runner for [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md) or [Google Cloud’s Agent Platform, formerly Vertex AI](../02-Claude-Code-CLI/google-vertex-ai.md). Every session that runner starts then calls the model in your cloud account, with your cloud credentials. Without this configuration, sessions send model requests to the Anthropic API. The runner still polls Anthropic for sessions, and each session still sends its event stream to `api.anthropic.com`. The event stream carries prompts, responses, and tool results. The plan requirement and the Zero Data Retention exclusion in [Availability and limitations](self-hosted-environments.md#availability-and-limitations) still apply. Sessions are routed to an environment, not to a runner, and a requeued or resumed session can run on a different runner. Configure every runner in the environment the same way. Before you start, read [what differs on these providers](#what-differs-from-sessions-on-the-anthropic-api).

1

Prepare the cloud account and your egress rules

Set up model access, a narrowly scoped policy or role, and network access:

- **Amazon Bedrock**: [submit use case details](../02-Claude-Code-CLI/amazon-bedrock.md#1-submit-use-case-details), then create the policy in [IAM configuration](../02-Claude-Code-CLI/amazon-bedrock.md#iam-configuration), limiting `bedrock:InvokeModel` and `bedrock:InvokeModelWithResponseStream` to the inference profiles your sessions use and the foundation models behind them
- **Agent Platform**: [enable the API](../02-Claude-Code-CLI/google-vertex-ai.md#1-enable-agent-platform-api) and [request model access](../02-Claude-Code-CLI/google-vertex-ai.md#2-request-model-access), then create the custom role that [IAM configuration](../02-Claude-Code-CLI/google-vertex-ai.md#iam-configuration) describes, with only `aiplatform.endpoints.predict`
- **Egress**: allow your provider’s endpoints through your egress rules. See [Network requirements](self-hosted-environments-deploy.md#network-requirements). If sessions can’t reach them, Claude Code can keep retrying for hours before the session shows an error.

2

Give sessions narrowly scoped credentials

Attach the policy or role from step 1 to an identity that can do nothing else. For the methods Claude Code accepts, see [Configure AWS credentials](../02-Claude-Code-CLI/amazon-bedrock.md#2-configure-aws-credentials) and [Configure GCP credentials](../02-Claude-Code-CLI/google-vertex-ai.md#3-configure-gcp-credentials).

Anyone who can get code to run in a session, including through prompt injection, can use these credentials at your cost for as long as the credentials are valid. Claude Code runs inside the session, so the credential it calls the model with has to be readable there.The shell commands Claude runs, your [Claude Code hooks](../07-Hooks/hooks.md), and stdio MCP servers inherit the session’s environment and run as the same user as Claude Code. As a result, they can read credential variables and credential files.When you [harden your deployment](self-hosted-environments-deploy.md#harden-your-deployment), you keep host credentials out of sessions, but you can’t keep this credential out. Give the identity behind it nothing beyond the policy or role from step 1.

Check the method you choose against these runner behaviors:

- **Metadata endpoint**: if you deny sessions the [cloud metadata endpoint](self-hosted-environments-deploy.md#harden-your-deployment) outright, credentials served from it, such as an instance profile, don’t reach Claude Code either. A file-based web identity, such as IAM Roles for Service Accounts (IRSA) on Amazon EKS or a Workload Identity Federation credential file, doesn’t depend on it.
- **Renewal**: a session can outlive a credential, so use a method that renews itself, such as a file-based web identity
- **Wrapper script**: the runner starts your [wrapper script](#provision-credentials-scoped-to-the-session-creator) once per session, so credentials it exports aren’t renewed. Claude Code reads AWS credentials from its environment, so if your wrapper already exports AWS credentials for other work, Claude Code can sign model requests with them.

3

Set one provider's variables in the runner's environment

Set exactly one provider’s variables where you set the runner’s other environment variables, such as the container spec or service unit, then restart the runner. The examples show them as shell exports. With [on-demand runners](#on-demand-runners), set them on the workload your `spawn-runner` hook starts.Start these runners with [`--confine-repo-settings enforce`](self-hosted-environments-deploy.md#harden-your-deployment). It refuses sessions on repositories whose committed settings it flags, so run in the default `warn` mode first and clear what it logs.

- Amazon Bedrock

- Google Cloud's Agent Platform

Replace the region with your own:

```python
export CLAUDE_CODE_USE_BEDROCK=1
export AWS_REGION=us-east-1
```

For how Claude Code resolves the region, see [Configure Claude Code](../02-Claude-Code-CLI/amazon-bedrock.md#3-configure-claude-code). For which inference profile prefix Claude Code uses for your region, see [Cross-region inference profile prefixes](../02-Claude-Code-CLI/amazon-bedrock.md#cross-region-inference-profile-prefixes).

Replace the region and project ID with your own:

```python
export CLAUDE_CODE_USE_VERTEX=1
export CLOUD_ML_REGION=global
export ANTHROPIC_VERTEX_PROJECT_ID=YOUR-PROJECT-ID
```

To choose a region, see [Region configuration](../02-Claude-Code-CLI/google-vertex-ai.md#region-configuration).

4

Check that the variables reached a session

Your own shell on the host is a different process, so check from inside a session. Start one in the environment and ask Claude to run this command:

```python
env | grep -E 'CLAUDE_CODE_USE_(BEDROCK|VERTEX)'
```

A line that sets `CLAUDE_CODE_USE_BEDROCK` or `CLAUDE_CODE_USE_VERTEX` to `1` means the variable reached the session. If both appear, Claude Code uses Amazon Bedrock. No output means neither reached it.The command shows configuration, not traffic. To confirm the requests themselves, look for them in your cloud account’s own metrics or request logs. If the first message fails instead, see troubleshooting for [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md#troubleshooting) or [Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md#troubleshooting).


[​](#what-differs-from-sessions-on-the-anthropic-api)

What differs from sessions on the Anthropic API

A session that sends model requests to Amazon Bedrock or Google Cloud’s Agent Platform differs from a session on the Anthropic API in these ways:

- **Policy from claude.ai**: [server-managed settings](server-managed-settings.md) don’t reach these sessions. Neither do the organization policies an Owner sets in Claude Code admin settings, so Claude Code doesn’t enforce them inside the session. Put the rules you rely on in the runner image’s [managed settings file](managed-settings.md#delivery-mechanisms).
- **Account skills**: these sessions don’t download the skills enabled for a person’s claude.ai account. See [How each session’s config is assembled](#how-each-session%E2%80%99s-config-is-assembled).
- **Files**: files that people attach to a session in claude.ai or the mobile or desktop app don’t reach it, and Claude can’t send files back with the [`SendUserFile` tool](../02-Claude-Code-CLI/tools-reference.md). Put input files in the repository or on the runner instead.
- **Model selection**: Anthropic’s control plane sends each session’s model, and when a session starts without one, Claude Code uses its default for the provider. You can’t choose the model with `ANTHROPIC_MODEL` or `ANTHROPIC_DEFAULT_MODEL` in the runner’s environment, but you can pin what an alias resolves to:
  - **`ANTHROPIC_MODEL` and `ANTHROPIC_DEFAULT_MODEL`**: the runner removes them from the environment it passes to sessions, even though the provider pages’ examples set `ANTHROPIC_MODEL`.
  - **Per-family pinning variables**: the variables in Pin model versions for [Amazon Bedrock](../02-Claude-Code-CLI/amazon-bedrock.md#4-pin-model-versions) and [Agent Platform](../02-Claude-Code-CLI/google-vertex-ai.md#5-pin-model-versions) do reach sessions. They decide what an alias such as `opus` resolves to, not what a full model ID resolves to.
- **Models your account doesn’t serve**: a session can fail on a message with an error that names the model. Enable the models your developers can choose, the background model described in Pin model versions, and the classifier model that [auto mode](../02-Claude-Code-CLI/permission-modes.md#enable-auto-mode-on-bedrock-agent-platform-or-foundry) uses. On Amazon Bedrock, allow each of them in your policy.
- **Web search and fast mode**: [web search](../02-Claude-Code-CLI/tools-reference.md#websearch-tool-behavior) isn’t available on Amazon Bedrock, and [fast mode](../02-Claude-Code-CLI/fast-mode.md) isn’t available on either provider. For other capabilities that differ by provider, see [CLI capabilities that vary by provider](../02-Claude-Code-CLI/feature-availability.md#cli-capabilities-that-vary-by-provider).


[​](#mcp-servers)

MCP servers

To make [MCP servers](../06-MCP-Tools/General/mcp.md) available in every session, add them at image build time with the same `claude mcp add` command used on a desktop install. If your runner is a bare process rather than a container, run the same command as the runner’s user on the host, then restart the runner: it reads host config once at startup. The `--scope user` flag is required; the default local scope writes under a per-directory key that the runner doesn’t seed into sessions. For example, in your Dockerfile:

```python
RUN claude mcp add --scope user sidecar -- /usr/local/bin/mcp-sidecar
RUN claude mcp add --scope user --transport http internal http://mcp-gateway.svc.cluster.local:8080
```

The runner snapshots the host’s config once at startup. The snapshot captures the `mcpServers` key from the host’s `.claude.json`, which lives next to rather than inside `~/.claude/`, and the runner seeds only that key into each session’s isolated config; account state and project history are dropped. To confirm the servers reached sessions, start a session on the environment and ask Claude to list its MCP tools; the runner also logs a startup warning for any captured entry whose `type` it doesn’t recognize and drops the entry, so you can see why that server is missing from sessions. When `SELF_HOSTED_RUNNER_HOST_CONFIG_DIR` is set, the runner reads `.claude.json` from that directory instead, so pointing the variable at an empty directory disables MCP seeding too. Claude Code also loads MCP servers from other sources:

- The enterprise-scope [managed MCP file](../06-MCP-Tools/General/managed-mcp.md) at its standard system path: `/etc/claude-code/managed-mcp.json` on Linux runner hosts, `/Library/Application Support/ClaudeCode/managed-mcp.json` on macOS hosts. Use it for locked-down fleets where only administrator-listed servers may load. See [exclusive control with managed-mcp.json](../06-MCP-Tools/General/managed-mcp.md#exclusive-control-with-managed-mcp-json) for the precedence rules. When this file is on the runner host, Claude Code skips the MCP servers Anthropic’s control plane delivers to a session, including claude.ai connectors, and names them in a warning on the session child’s stderr, which the runner records at the `debug` log level. Before v2.1.229, those sessions exited at startup with `You cannot dynamically configure MCP servers when an enterprise MCP config is present`.
- The [`managedMcpServers`](../02-Claude-Code-CLI/settings-reference.md#managedmcpservers) key in [managed settings](managed-settings.md) on the runner host: provides HTTP and SSE servers without taking exclusive control, so servers from the other sources still load. Requires Claude Code v2.1.259 or later.
- `<repo>/.mcp.json`: project scope. Outside a [project](../02-Claude-Code-CLI/claude-projects.md#what-threads-pick-up-from-your-repositories) thread, commit the file to the repository, and its servers are auto-approved in cloud sessions. In a session with several repositories, [at most one repository’s file loads](#repository-settings-in-sessions-with-several-repositories).
- A [`--mcp-config`](../02-Claude-Code-CLI/cli-reference.md#cli-flags) file from your wrapper script: append the flag after `"$@"`, as in `exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --mcp-config /etc/claude/mcp.json`. To keep that file’s servers running when the session’s delivered servers are updated mid-session, have your wrapper pass the [full environment](#wrapper-scripts) to the child, as `exec` does. Before v2.1.299, the stdio, HTTP, and SSE servers in that file shut down at the first such update that didn’t name them.

When connector delivery is enabled for your organization, Anthropic’s control plane delivers the connectors you’ve configured on claude.ai to interactively-created sessions through server-provided MCP configuration, routed through `api.anthropic.com`. Sessions created programmatically, such as [CLI dispatches](self-hosted-environments-testing.md#run-the-test-loop), don’t receive connector delivery; give them MCP servers through any of the other sources this section lists instead. The child’s OAuth token doesn’t carry a scope for fetching connectors directly, so the child doesn’t attempt that fetch itself; delivery is server-driven.

In a session that received a connector, a server of the same name from the host’s `.claude.json` or a repository’s `.mcp.json` isn’t connected. To use both, give your server a different name.

`settings.json` doesn’t carry MCP server definitions, and there is no top-level `mcpServers` field in the settings schema. In managed settings, provide servers with the [`managedMcpServers`](../02-Claude-Code-CLI/settings-reference.md#managedmcpservers) key instead. Sessions inherit the runner’s environment, so set [`ENABLE_TOOL_SEARCH`](../06-MCP-Tools/General/mcp.md#scale-with-mcp-tool-search) there to control MCP tool search for every session a runner spawns; the MCP page covers the values.


[​](#wait-for-mcp-servers-before-the-first-turn)

Wait for MCP servers before the first turn

A self-hosted session waits briefly for MCP servers that are still connecting, at two separate points. A server that misses a wait has its tools missing when the first turn starts, and they become available later with no action on your part. The two waits are:

- **Session startup**: before the tool list is first taken, the session waits up to 5 seconds by default for an HTTP or SSE server whose entry sets [`alwaysLoad: true`](../06-MCP-Tools/General/mcp.md#exempt-a-server-from-deferral), or for all servers when you set [`MCP_CONNECTION_NONBLOCKING=0`](../02-Claude-Code-CLI/env-vars.md) in the runner’s environment. HTTP and SSE servers otherwise connect in the background. While the session waits here, it’s slower to initialize. [`MCP_CONNECT_TIMEOUT_MS`](../02-Claude-Code-CLI/env-vars.md) changes the 5-second default.
- **First turn**: after the message arrives, the first turn waits up to 2 seconds for stdio servers that are still connecting. While the session waits here, the first reply is slower. To change how long this wait lasts, set [`CLAUDE_CODE_MCP_STARTUP_WAIT_MS`](../02-Claude-Code-CLI/env-vars.md) in the runner’s environment. It doesn’t change which servers the wait covers. Requires Claude Code v2.1.274 or later.

`claude mcp add` has no `alwaysLoad` flag. To set the key, add the server with `claude mcp add-json` instead, which takes it in the server’s JSON and writes it to `.claude.json`. In your Dockerfile:

```python
RUN claude mcp add-json core '{"type":"http","url":"https://mcp.example.com/mcp","alwaysLoad":true}' --scope user
```

If a server’s tools don’t appear on later turns either, check whether the server reached the session at all, as [MCP servers](#mcp-servers) describes.


[​](#turn-off-built-in-session-tools)

Turn off built-in session tools

Anthropic’s control plane attaches its own MCP server, named Claude Code Remote, to cloud sessions. Claude uses the server’s tools to create [cloud scheduled tasks](../02-Claude-Code-CLI/web-scheduled-tasks.md), start and steer other cloud sessions, attach more repositories, and follow pull request activity. To turn off the whole server, add a [server-level deny rule](../02-Claude-Code-CLI/permissions.md#mcp) to your settings. The control plane registers the server under one of three names, depending on how the session was created. Claude Code matches the name in a rule exactly, including case, so write the rule once per name as shown:

```python
{
  "permissions": {
    "deny": [
      "mcp__Claude_Code_Remote",
      "mcp__claude-code-remote",
      "mcp__bf7c680d-5fdc-5ef4-b4a0-abadb619bf0a"
    ]
  }
}
```

A rule that names the whole server covers tools the server gains later too. To turn off one tool and keep the rest, append two more underscores and the tool name to each rule, as in `mcp__Claude_Code_Remote__add_repo`. To block the server from connecting at all rather than removing its tools, add the three names without the `mcp__` prefix as `serverName` entries under [`deniedMcpServers`](../06-MCP-Tools/General/managed-mcp.md#policy-based-control-with-allowlists-and-denylists) instead. Put the rules in [server-managed settings](server-managed-settings.md) to reach sessions with no change to the runner, or in `~/.claude/settings.json` on the runner. On a runner that [sends model requests to Bedrock or Agent Platform](#send-model-requests-to-bedrock-or-agent-platform), use that file, because server-managed settings don’t reach those sessions. [Permissions and tool approval](#permissions-and-tool-approval) explains how settings on the runner reach sessions. To confirm the rules took effect, start a session on the environment and ask Claude to list its MCP tools. Claude Code removes a denied tool from Claude’s context, so the denied tools are absent from its answer.


[​](#prompt-sessions-to-push-their-work)

Prompt sessions to push their work

Anthropic-hosted sessions run a [`Stop` hook](../07-Hooks/hooks.md#stop), the Claude Code hook that runs when Claude finishes responding, that prompts Claude to commit and push its work. The runner doesn’t install one. Without it, a session that ends with uncommitted changes leaves that work only on the runner’s disk, and the **Create PR** button in claude.ai/code stays inactive until the branch exists on the remote. The reference implementation below has two parts. Merge the settings block into `~/.claude/settings.json` on the runner host, which the runner seeds into every session, and save the script as `~/.claude/hooks/stop-hook-nudge.sh` on the runner host and make it executable:

```python
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "timeout": 10,
            "command": "\"$CLAUDE_CONFIG_DIR/hooks/stop-hook-nudge.sh\""
          }
        ]
      }
    ]
  }
}
```

```python
#!/bin/sh
# Stop-hook reference implementation for self-hosted runners.
# Nudges Claude once per turn if the project directory has uncommitted
# changes OR unpushed commits, so work isn't lost when an idle session
# is released and so the "Create PR" button on claude.ai/code lights up.
# Runner-level (no repo changes): drop this file at ~/.claude/hooks/ on
# the runner host and merge the accompanying Stop-hook settings block
# into ~/.claude/settings.json — the runner seeds both into every session.
# Repo-level alternative: commit to <repo>/.claude/hooks/ and change the
# settings.json command path to $CLAUDE_PROJECT_DIR/.claude/hooks/.
# stdin: hook JSON payload (see https://code.claude.com/docs/en/hooks)
# stdout: {"decision":"block","reason":"..."} to nudge, or nothing to allow stop.

# Re-entry guard: the harness sets stop_hook_active=true when re-invoking
# the Stop hook after a block. Bail so we only nudge once per turn. The
# harness emits compact JSON (no space after the colon), which this
# pattern relies on; use jq if you need a whitespace-tolerant check.
in=$(cat)
case "$in" in *'"stop_hook_active":true'*) exit 0 ;; esac

d="$CLAUDE_PROJECT_DIR"

# Hardening: this script runs outside Claude Code's Bash sandbox, so its git
# calls skip some features that can start a program named in the checkout's
# git settings. They don't skip them all, so don't count on the sandbox alone.
GIT_OPTIONAL_LOCKS=0
export GIT_OPTIONAL_LOCKS
git() { command git -c core.fsmonitor= -c core.hooksPath=/dev/null "$@"; }

# Not a git repo → nothing to nudge.
git -C "$d" rev-parse --git-dir >/dev/null 2>&1 || exit 0

# No remote → "push to the remote" is unsatisfiable; bail.
[ -z "$(git -C "$d" remote 2>/dev/null)" ] && exit 0

# Uncommitted changes (staged, unstaged, or untracked). Exclude .claude/
# entirely — operator-seeded settings and CLI-written runtime state
# (scheduler lock, worktrees, routine state) live there and neither is
# "uncommitted work" the model needs to push.
s=$(git -C "$d" status --porcelain --ignore-submodules=dirty -- . ':(exclude).claude/' 2>/dev/null)
if [ -n "$s" ]; then
  printf '{"decision":"block","reason":"There are uncommitted changes in the repository. Please commit and push these changes to the remote branch."}'
  exit 0
fi

# Unpushed commits. Count commits on HEAD not reachable from any
# remote-tracking ref or FETCH_HEAD. This works uniformly for:
#   - init+fetch checkouts (runner default: only FETCH_HEAD exists)
#   - clone-based checkouts (origin/* exist)
#   - the runner default: the child starts on the session's outcome
#     branch, which the runner creates after checkout
#   - detached HEAD, when a custom setup skips that branch creation
# With no reference point at all (never fetched), stay silent rather
# than false-positive on a read-only turn.
base=""
git -C "$d" rev-parse --verify -q FETCH_HEAD >/dev/null && base="FETCH_HEAD"
if [ -z "$base" ] && [ -z "$(git -C "$d" for-each-ref --count=1 refs/remotes/origin 2>/dev/null)" ]; then
  exit 0
fi
# shellcheck disable=SC2086  # $base is either "" or "FETCH_HEAD", intentional word-split
unpushed=$(git -C "$d" rev-list HEAD --not $base --remotes=origin --count 2>/dev/null) || unpushed=0
if [ "$unpushed" -gt 0 ]; then
  branch=$(git -C "$d" symbolic-ref --short -q HEAD)
  if [ -n "$branch" ]; then
    # $branch is attacker-influenced — git-check-ref-format(1) allows `"`
    # in ref names. `\` is forbidden (rule 10) but escaped anyway as cheap
    # defense-in-depth.
    # Escape JSON metacharacters before interpolating into the hand-built
    # payload so a branch like x","continue":false can't inject keys into
    # the hook-output JSON the harness parses. $unpushed is safe — the
    # -gt guard above rejects anything that isn't a plain integer.
    branch_esc=$(printf '%s' "$branch" | sed 's/\\/\\\\/g; s/"/\\"/g')
    printf '{"decision":"block","reason":"There are %s unpushed commit(s) on branch '\''%s'\''. Please push these changes to the remote repository."}' "$unpushed" "$branch_esc"
  else
    printf '{"decision":"block","reason":"There are %s unpushed commit(s) on a detached HEAD. Please create a branch and push it to the remote repository."}' "$unpushed"
  fi
  exit 0
fi

exit 0
```

The hook prompts Claude to commit and push before the session ends, and stays silent when the directory isn’t a git repository or has no remote. For a session with several repositories, see [what `$CLAUDE_PROJECT_DIR` names](#repository-settings-in-sessions-with-several-repositories). The Stop hook only prompts Claude. For the commit and push to work, set up:

- **A git identity**: set `user.name` and `user.email` in your runner image, or start the runner with [`--configure-git`](self-hosted-environments-deploy.md#let-the-runner-configure-git)
- **Push credentials**: use [Anthropic-managed git](self-hosted-environments-deploy.md#use-the-anthropic-git-proxy), or supply credentials as [Ship git config in your image](self-hosted-environments-deploy.md#ship-git-config-in-your-image) describes
- **For resumed sessions**: set [`--push-outcome-on-release`](self-hosted-environments-reference.md#runner-cli-flags) on every runner in the environment. A resumed session then starts from the commits that Claude pushed to the session’s branch. For the flag’s limits, see [Resumed sessions lose unpushed work](self-hosted-environments-deploy.md#additional-limitations).

The Stop hook prompts at most once per turn. If the commit or push fails, the turn still ends. Look for git’s error in the session and fix what it names. On a large checkout, raise `timeout` in the Stop hook settings and restart the runners. The script’s `git status` skips `core.fsmonitor`, so it can take longer than the 10 seconds set there. A hook that times out ends the turn with no prompt.


[​](#submodules-and-the-stop-hook)

Submodules and the Stop hook

The Stop hook checks which commit each submodule is on, not the files inside it:

- **A new commit in a submodule**: Claude is prompted until the parent repository records it, even if `.gitmodules` sets `ignore = all` for that submodule
- **Uncommitted files in a submodule**: Claude isn’t prompted, so that work stays only on the runner’s disk
- **Unpushed commits in a submodule**: the Stop hook doesn’t check whether they’re pushed, so the parent can point at a commit that nobody else can fetch


[​](#replace-an-earlier-copy-of-the-stop-hook-script)

Replace an earlier copy of the Stop hook script

Search your copy of `stop-hook-nudge.sh` for `GIT_OPTIONAL_LOCKS`. If it’s missing, you have an earlier copy. Replace it with the [current script](#prompt-sessions-to-push-their-work) wherever you installed it, then restart the runners. Keep your Stop hook settings, except to raise `timeout` on a large checkout.

- **When**: if your sessions run with the [Bash sandbox](../02-Claude-Code-CLI/sandboxing.md) on, replace it now. Otherwise, replace it at your next image build or runner restart.
- **What changes**: uncommitted files in a submodule no longer prompt Claude. A new commit in a submodule that `.gitmodules` marks `ignore = all` now does.

Hooks run outside the Bash sandbox, so the current script’s git calls skip some git features that can start a program named in the checkout’s git settings. They don’t skip them all. Don’t count on the sandbox alone, and isolate sessions as [Harden your deployment](self-hosted-environments-deploy.md#harden-your-deployment) describes.


[​](#permissions-and-tool-approval)

Permissions and tool approval

A self-hosted session has no terminal attached, so an unanswered permission prompt stalls the turn until the user responds in the UI. Anthropic’s control plane sends each session’s tool list and permission rules with the work payload; the default configuration pre-approves routine tool calls, including `Bash`, and cloud sessions [pre-approve file edits regardless of mode](../02-Claude-Code-CLI/permission-modes.md#switch-permission-modes). A call that nothing pre-approves prompts through the session UI.

Only pin auto mode on an environment whose session containers run with [default-deny network egress](self-hosted-environments-deploy.md#default-deny-egress) and the rest of the [hardening section](self-hosted-environments-deploy.md#harden-your-deployment) in place. Routine tool calls, including `Bash` network requests, run without a human in the loop on both the default pre-approved tool set and in auto mode, so the network boundary is what limits where those calls can reach.

To keep prompts to a minimum regardless of what the control plane sends, pin [auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) from your wrapper script or [`command` hook](#command). Auto mode lets sessions run without routine permission prompts: a separate classifier model reviews actions before they run and blocks the ones it rejects, and explicit ask rules still force a prompt; the permission modes page covers what the classifier checks. The runner appends server-computed flags before invoking the wrapper, and for single-value flags such as `--permission-mode` the parser honors the last occurrence, so a flag you append after `"$@"` overrides the server-sent value:

```python
#!/bin/bash
exec "$CLAUDE_RUNNER_CLAUDE_BIN" "$@" --permission-mode auto
```

To pre-approve specific tools instead, append `--allowed-tools` with your rules, for example `--allowed-tools "Bash(bazel *) Bash(yarn *) mcp__internal__*"`. List flags such as `--allowed-tools` and `--disallowed-tools` accumulate across occurrences rather than overriding, so your rules apply on top of any rules the control plane sends. To narrow, append `--disallowed-tools`, which denies tools even if another rule allows them.


[​](#how-each-session’s-config-is-assembled)

How each session’s config is assembled

The runner gives each session its own config directory, seeded from a snapshot of the host’s `~/.claude/` that the runner captures once at startup: `settings.json`, `CLAUDE.md`, hooks, agents, commands, and skills in your runner image apply to every session as the user-level baseline. If you change config on a running host, the change takes effect only after you restart the runner. Set `SELF_HOSTED_RUNNER_HOST_CONFIG_DIR` to seed from a different path, or point it at an empty directory to disable seeding. Sessions also read these settings files:

- **Project settings**: outside a [project](../02-Claude-Code-CLI/claude-projects.md#what-threads-pick-up-from-your-repositories) thread, a repository-committed `.claude/settings.json` layers on top of the user-level baseline. In a session with several repositories, [at most one repository’s file takes effect](#repository-settings-in-sessions-with-several-repositories).
- **Managed settings**: sessions read [`managed-settings.json`](../02-Claude-Code-CLI/settings.md#where-settings-live) from the standard system path in your runner image. For whether its keys apply alongside [server-managed settings](server-managed-settings.md), see [how Claude Code combines managed sources](managed-settings.md#how-claude-code-combines-managed-sources).

For the order these sources apply in, see [settings precedence](../02-Claude-Code-CLI/settings.md#settings-precedence). When Anthropic’s control plane supplies a session with [Claude Code hooks](../07-Hooks/hooks.md), the runner installs them alongside, not over, your own configuration. Requires Claude Code v2.1.229 or later.

- **Where they land**: the runner writes each supplied hook script to a reserved `hooks/.ccr-launcher/` subdirectory of the session’s config directory and registers the scripts in a separate settings file it passes to the session with `--settings`, leaving the seeded `settings.json` and your own scripts at `hooks/<name>` untouched. The runner recreates the reserved subdirectory for each session and doesn’t seed host content at `~/.claude/hooks/.ccr-launcher/` into sessions.
- **Who authors them**: the control plane populates the scripts from fixed constants in its own deployment, never from per-session or third-party input.
- **What still governs them**: hooks delivered through `--settings` enter the ordinary merged hook configuration, not the managed tier, so your managed settings still apply. `disableAllHooks` disables them, and they are not among the categories [`allowManagedHooksOnly`](../02-Claude-Code-CLI/settings-reference.md#allowmanagedhooksonly) keeps loaded.

When a person starts their own session, Claude Code downloads the [skills enabled for their claude.ai account](../08-Plugins-Skills/skills.md#skills-in-cowork-and-cloud-sessions) into that session’s config directory. A [cloud scheduled task](../02-Claude-Code-CLI/web-scheduled-tasks.md) run doesn’t get its owner’s skills, and a session that [sends model requests to Bedrock or Agent Platform](#send-model-requests-to-bedrock-or-agent-platform) doesn’t download any. For a skill those sessions need, add it to your runner image or, outside a [project](../02-Claude-Code-CLI/claude-projects.md#what-threads-pick-up-from-your-repositories) thread, commit it to the repository’s `.claude/skills/`. Outside [Claude Tag](https://claude.com/docs/claude-tag/overview) sessions, a session in a self-hosted environment runs with [auto memory](../02-Claude-Code-CLI/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. The 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.


[​](#repository-settings-in-sessions-with-several-repositories)

Repository settings in sessions with several repositories

In a thread in a [project](../02-Claude-Code-CLI/claude-projects.md#what-threads-pick-up-from-your-repositories), no repository’s `.claude/settings.json` takes effect as project settings and no repository’s `.mcp.json` or skills load, wherever the thread starts. To give those threads a skill, add it to your runner image. In any other session with several repositories, Claude Code reads project settings from the directory the session starts in, so at most one repository’s `.claude/settings.json` takes effect as project settings. A hook defined in another repository’s file doesn’t run, a deny rule in it doesn’t apply, and its `env` isn’t set.

- **`--capacity 1`, the default, with the built-in checkout**: the session starts in the first repository in its list of repositories. That repository’s `.claude/settings.json` takes effect as project settings and its `.mcp.json` loads, and the other repositories’ don’t.
- **A `--capacity` above one, or a [`checkout` hook](#checkout)**: the session starts in a per-session directory that contains the checkouts. No repository’s `.claude/settings.json` takes effect as project settings, no repository’s `.mcp.json` loads, and [`$CLAUDE_PROJECT_DIR`](../07-Hooks/hooks.md#reference-scripts-by-path) in a hook command is that directory, not a checkout.

Each repository’s `CLAUDE.md` loads wherever the session starts. Outside a project thread, its skills load too. The runner passes every repository to Claude Code as an [additional directory](../02-Claude-Code-CLI/permissions.md#additional-directories-grant-file-access-not-configuration), so Claude Code also reads the `enabledPlugins` and `extraKnownMarketplaces` keys from each repository’s `.claude/settings.json`. To run a hook or apply a permission rule in every session, put it in `~/.claude/settings.json` on the runner host. The runner [seeds the host file into every session](#how-each-session%E2%80%99s-config-is-assembled), wherever the session starts. Write a path in a `Read` or `Edit` rule as a `//` absolute or `~/` home-relative [pattern](../02-Claude-Code-CLI/permissions.md#read-and-edit), because other patterns anchor at the settings source or the current directory.


[​](#repository-committed-permission-rules)

Repository-committed permission rules

Don’t put a bare `"Edit"`, `"Write"`, or `"NotebookEdit"` entry in a repository-committed `permissions.allow`. A bare file-tool rule matches the tool regardless of path, granting writes anywhere on the host rather than only the workspace, so the runner’s write-scope confine guard flags the session; with [`--confine-repo-settings enforce`](self-hosted-environments-reference.md#runner-cli-flags) it refuses to spawn the session instead of logging and continuing. See the [hardening section](self-hosted-environments-deploy.md#harden-your-deployment). A repository needs no file-tool rule at all: cloud sessions [pre-approve file edits regardless of mode](../02-Claude-Code-CLI/permission-modes.md#switch-permission-modes). If you do commit a rule, scope it to the workspace, such as `"Edit(/**)"`; a single leading slash is relative to the project root, which is the session’s workspace. Bare file-tool rules are fine in the operator’s host-level `settings.json`, since that file isn’t repository-committed. A `defaultMode` of `auto` is only honored from the image-wide or user-level settings file, so a checked-out repository can’t grant itself auto mode. For which modes cloud sessions accept and the full rule syntax, see [permission modes](../02-Claude-Code-CLI/permission-modes.md).


[​](#what’s-next)

What’s next

- [Reference](self-hosted-environments-reference.md): every CLI flag, environment variable, and metric
- [Verify session identity](self-hosted-environments-identity.md): validate the session token from services outside the runner

---

## Deploy self-hosted environments to production - Claude Code Docs

Source: https://code.claude.com/docs/en/self-hosted-environments-deploy
Path: 13-Enterprise-Admin/self-hosted-environments-deploy.md

## On this page

- [Harden your deployment](#harden-your-deployment)
- [Network requirements](#network-requirements)
  - [Default-deny egress](#default-deny-egress)
  - [Authenticate to an egress proxy](#authenticate-to-an-egress-proxy)
  - [Choose where the Proxy-Authorization value comes from](#choose-where-the-proxy-authorization-value-comes-from)
  - [Configurations the runner refuses to start with](#configurations-the-runner-refuses-to-start-with)
  - [What the runner changes while a proxy-authorization flag is set](#what-the-runner-changes-while-a-proxy-authorization-flag-is-set)
- [Configure git](#configure-git)
  - [Let the runner configure git](#let-the-runner-configure-git)
  - [Ship git config in your image](#ship-git-config-in-your-image)
  - [Use the Anthropic git proxy](#use-the-anthropic-git-proxy)
  - [Turn the Anthropic git proxy on](#turn-the-anthropic-git-proxy-on)
  - [How Anthropic serves git for a session](#how-anthropic-serves-git-for-a-session)
  - [When sessions fail to start on a runner with the git proxy](#when-anthropic-doesnt-serve-a-session)
  - [Turn the Anthropic git proxy off](#turn-the-anthropic-git-proxy-off)
  - [GitHub API access without the GitHub CLI](#github-api-access-without-the-github-cli)
  - [Trust a private certificate authority with Anthropic-managed git](#trust-a-private-certificate-authority-with-anthropic-managed-git)
  - [Rewrite git URLs for private networks](#rewrite-git-urls-for-private-networks)
- [Build the runner image](#build-the-runner-image)
- [Size CPU and memory for sessions](#size-cpu-and-memory-for-sessions)
- [Kubernetes](#kubernetes)
- [Docker Compose](#docker-compose)
- [Shutdown timing](#shutdown-timing)
  - [Let running turns finish before a restart](#let-running-turns-finish-before-a-restart)
  - [Post a status message before a restart](#post-a-status-message-before-a-restart)
  - [Defer the drain past the first signal](#defer-the-drain-past-the-first-signal)
  - [What happens to the sessions the runner holds after the first signal](#what-happens-to-the-sessions-the-runner-holds-after-the-first-signal)
  - [Size the stop timeout](#size-the-stop-timeout)
  - [What reaches a running post-session hook](#what-reaches-a-running-post-session-hook)
- [Keep the base directory and capacity identical across runners](#keep-the-base-directory-and-capacity-identical-across-runners)
- [Reuse a pre-warmed checkout](#reuse-a-pre-warmed-checkout)
- [Pin the version](#pin-the-version)
- [Scale the fleet](#scale-the-fleet)
- [Known issues and limitations](#known-issues-and-limitations)
  - [Connector traffic leaves your network](#connector-traffic-leaves-your-network)
  - [Some sessions don’t count as idle](#some-sessions-don%E2%80%99t-count-as-idle)
  - [Additional limitations](#additional-limitations)
  - [Report an issue](#report-an-issue)
- [Troubleshooting](#troubleshooting)
  - [When the runner exits](#when-the-runner-exits)
  - [Recognize a failed start](#recognize-a-failed-start)
  - [Restart with a wait that grows](#restart-with-a-wait-that-grows)
  - [Check why the runner keeps exiting](#check-why-the-runner-keeps-exiting)
- [What’s next](#what%E2%80%99s-next)

Self-hosted environments

# Deploy self-hosted environments to production

Copy pageCopy page

Run self-hosted runners in production: security hardening, network egress control, git credentials, Kubernetes and Compose recipes, and troubleshooting.

Copy pageCopy page

Self-hosted environments are in public beta on Team and Enterprise plans; [Availability and limitations](self-hosted-environments.md#availability-and-limitations) covers the enablement path. This page covers running the fleet in production; see the [quickstart](self-hosted-environments-quickstart.md) for your first runner and session.

A [self-hosted environment](self-hosted-environments.md) runs Claude Code [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) on runners you deploy inside your network, and in production those sessions execute model-directed code on behalf of everyone who can dispatch a session to the environment. This page is for the operator taking a working environment to production. It works through the deployment in order: what to lock down before connecting real systems, the egress the fleet needs, how sessions authenticate to your git host, the deployment recipes themselves, and what to check when sessions misbehave.


[​](#harden-your-deployment)

Harden your deployment

A self-hosted runner executes arbitrary, model-directed code on your infrastructure on behalf of everyone who can dispatch a session to its environment. That’s any member of your Anthropic organization, and anyone who can start a [Claude Tag](https://claude.com/docs/claude-tag/overview) channel session in a scope an Owner routed to the environment. Work through each item before you connect an environment to production systems:

- **Ephemeral, per-session containers**: run each runner process in a fresh container or VM that’s destroyed when the process exits, with `--capacity 1` and the default `--drain-grace-sec 0` so each container serves exactly one session. At a higher capacity, or with a positive drain grace, one container serves multiple sessions from the same [locked owner](self-hosted-environments.md#key-concepts); see [Runner lifecycle](self-hosted-environments.md#runner-lifecycle). Don’t reuse a filesystem between runner restarts, except in the deliberate [pre-warmed checkout](#reuse-a-pre-warmed-checkout) setup, and never across owners.
  - When the runner stops a session, it sends no signal to a process still running after its shell command exited, such as a service that daemonized. Destroying the container or VM ends that process.
- **No broad credentials in the image**: don’t include long-lived SSH keys, cloud-provider credentials, or personal access tokens that grant more than a session needs. Mint credentials used during a session, such as push or API tokens, per session from your [wrapper script](self-hosted-environments-configuration.md#wrapper-scripts). The initial clone happens before the wrapper runs, so handle it with a [`checkout` lifecycle hook](self-hosted-environments-configuration.md#checkout), or with [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy) when all of a session’s repositories are on github.com. For both, see [Configure git](#configure-git).
- **Keep the host’s GitHub credentials away from sessions**: Claude can use any GitHub credential a session can read, with whatever access that credential grants. Keep the runner host’s own broadly scoped GitHub credentials out of anything a session can read. Such a credential can be a personal access token, the token `gh auth login` saves for your account, or a `GH_TOKEN` in the runner’s environment.
  - **With [Anthropic-managed git](#use-the-anthropic-git-proxy)**: with such a credential, Claude reaches GitHub directly instead of through Anthropic-managed git.
  - **Without Anthropic-managed git**: a clone credential can stay in the image if you scope it as tightly as [Ship git config in your image](#ship-git-config-in-your-image) describes.
- **Keep the environment secret off session-running hosts**: the environment secret can register runners and pick up any session queued on the environment. On a fixed fleet it lives on every runner host, where any session’s code can read the secret file. Prefer [on-demand runners](self-hosted-environments-configuration.md#on-demand-runners), where the secret stays on the orchestrator host, which never runs user code, and each runner receives a single-use work order that registers exactly one runner. On a fixed fleet, treat the environment-secret file as readable by every session and rotate the secret after any suspected session compromise.
- **Default-deny network egress**: restrict runner and session container outbound traffic at your own network boundary on every environment; [Default-deny egress](#default-deny-egress) covers what to allow and why.
- **Least-privilege host IAM**: the compute identity attached to the runner host, such as an instance profile or node service account, should grant only what the runner itself needs. Sessions should obtain their own credentials through your wrapper script rather than inheriting the host’s.
- **Block the cloud metadata endpoint from sessions**: keeping sessions off the host identity requires blocking their access to the cloud metadata endpoint, and subnet-level egress policies don’t intercept link-local metadata traffic, so block it in the container itself:
  - IMDSv2 with a hop limit of one
  - GKE Workload Identity with metadata concealment
  - An explicit deny for `169.254.169.254` in the session container’s network namespace

  The block applies to your wrapper script and lifecycle hooks too, since they share the container. Authenticate any token exchange with the [session JWT](self-hosted-environments-identity.md) against your own token service over allowlisted egress, or use a file-based web identity such as IAM Roles for Service Accounts (IRSA) on Amazon EKS.
- **Per-runner filesystem isolation**: each runner process gets its own working directory that no other process on the host can read or write. Make `--hooks-dir`, the wrapper script, and the host’s `~/.claude/` read-only to the session, either built into the image or mounted read-only.
- **Dispatch has no per-environment access control**: any member of your Anthropic organization can dispatch a session to any of its environments. If an Owner [routes Claude Tag channels to the environment](../02-Claude-Code-CLI/cloud-environments.md#set-the-environment-a-claude-tag-channel-uses), anyone the [Claude Tag access setting](https://claude.com/docs/claude-tag/admins/restrict-access#restrict-who-can-use-claude) admits can start channel sessions that run there. By default that’s anyone in the connected Slack workspace, with or without a Claude account. Treat every runner host as reachable for code execution by everyone who can dispatch to it, and place on a runner host only data and credentials that all of those people are allowed to read. [`--lock-to-account`](self-hosted-environments-reference.md#runner-cli-flags) bounds which account’s sessions a given host executes, but it doesn’t narrow who can dispatch into the environment. To make self-hosted environments the only picker option, an [Owner](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) can hide Anthropic-hosted environments for the whole organization from the [**Cloud environments** page](https://claude.ai/admin-settings/cloud-environments).
- **Enforce the repo-settings guard**: choose the guard mode with [`--confine-repo-settings`](self-hosted-environments-reference.md#runner-cli-flags). The default `warn` logs a violation and still spawns the session, `enforce` refuses the session, and `off` disables the scan. The runner scans each repository’s committed settings for:
  - A grant that resolves outside that session’s own workspace: an `additionalDirectories` entry, an `Edit`, `Write`, or `NotebookEdit` rule in `permissions.allow`, or a `sandbox.filesystem.allowWrite` or `allowRead` entry
  - A non-empty `env` block
  - An operator-posture override such as `sandbox.enabled: false`

  The guard runs regardless of [`--trust-workspace`](self-hosted-environments-reference.md#runner-cli-flags), and doesn’t cover repository hooks, `.mcp.json`, or Bash rules; see [Permissions and tool approval](self-hosted-environments-configuration.md#permissions-and-tool-approval) for where those grants belong.

If your organization has [IP allowlisting](restrict-access-to-claude-with-ip-allowlisting.md) enabled, add the public egress addresses of your runners and session containers to the allowlist before you start them. If you run [on-demand runners](self-hosted-environments-configuration.md#on-demand-runners), add the orchestrator host’s address too. Don’t rely on the allowlist as a network control for runner or session traffic. Apply default-deny egress at your own network boundary instead.


[​](#network-requirements)

Network requirements

The runner and the session children it spawns make outbound connections to the hosts below. Restrict session-container egress to these hosts and the specific internal services sessions need to reach; [Default-deny egress](#default-deny-egress) covers how and why. These hosts are always required:

| Host                                                               | Port                                                                      | Used for                                                                                                                                                                                                                                                           |
|:-------------------------------------------------------------------|:--------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `api.anthropic.com`                                                | 443, HTTPS; WSS for [Anthropic-managed git](#use-the-anthropic-git-proxy) | Runner control plane and session streaming, model inference, feature flags, product analytics, [JWKS](self-hosted-environments-identity.md) key fetches, commit signing, and Anthropic-managed git when `--use-anthropic-git-proxy` is set                   |
| Your git host, such as `github.com` or your GitHub Enterprise host | 443 or 22                                                                 | Cloning and pushing repositories on each git host the runner’s sessions use. On a runner that uses [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), see [when the `github.com` path is still needed](#github-com-egress-with-the-anthropic-git-proxy). |

A runner that uses [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy) routes its `github.com` git traffic through `api.anthropic.com`, so it doesn’t need the git host path for `github.com`. It still needs that path if you set `--push-outcome-on-release` or push from a `post-session` hook. Whether these hosts are needed depends on your configuration:

| Host                                                                                                                                                                          | Port | When required                                                                                                                                                                                                                                                                                |
|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|:-----|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `downloads.claude.ai`                                                                                                                                                         | 443  | At install time, when you install or update Claude Code on the host with the native installer; the `install.sh` script itself is served from `claude.ai`. At session runtime, only when sessions install plugins from the official Anthropic marketplace.                                    |
| `storage.googleapis.com`                                                                                                                                                      | 443  | At session runtime, for the plugin install counts and metadata shown in `/plugin`.                                                                                                                                                                                                           |
| `code.claude.com` and `claude.com`                                                                                                                                            | 443  | Documentation lookups by the built-in claude-code-guide agent and pre-approved WebFetch requests during sessions. Blocking these hosts only affects documentation lookups.                                                                                                                   |
| `*.frame.claudeusercontent.com`                                                                                                                                               | 443  | Only when the [Artifact tool](../02-Claude-Code-CLI/artifacts.md#availability) is available for sessions in your organization; defaults vary by plan, per the availability table there. Set `CLAUDE_CODE_DISABLE_ARTIFACT=1` on the runner to keep the tool disabled regardless of the organization setting. |
| `registry.npmjs.org`                                                                                                                                                          | 443  | When a session installs a plugin, both for fetching npm-source plugin packages and for installing a plugin’s Node.js dependencies, or when an `npx`-launched MCP server runs                                                                                                                 |
| `http-intake.logs.us5.datadoghq.com`                                                                                                                                          | 443  | Anthropic operational metrics. Only when `CLAUDE_CODE_BYOC_ENABLE_DATADOG=1` is set; off by default in self-hosted environments.                                                                                                                                                             |
| `browser-intake-us5-datadoghq.com`                                                                                                                                            | 443  | Anthropic error-report uploads, sent only when [error reporting](data-usage.md#telemetry-services) is enabled for the session’s account. Suppressed by `DISABLE_ERROR_REPORTING=1` or `DISABLE_TELEMETRY=1`.                                                                           |
| Your cloud provider’s endpoints for model requests, model lookups, and renewing credentials, such as `bedrock-runtime.us-east-1.amazonaws.com` or `aiplatform.googleapis.com` | 443  | Only when the runner [sends model requests to Amazon Bedrock or Google Cloud’s Agent Platform](self-hosted-environments-configuration.md#send-model-requests-to-bedrock-or-agent-platform)                                                                                             |

You don’t need to allowlist these hosts for runner or session traffic:

- **`statsig.anthropic.com`, `*.sentry.io`, `claude.ai`, and `platform.claude.com`**: these hosts appear in some older enterprise network checklists, but the runner doesn’t reach them. Feature-flag fetches go to `api.anthropic.com`, and the runner authenticates with the environment secret rather than interactive OAuth.
- **`mcp-proxy.anthropic.com`**: self-hosted sessions don’t use it. When connector delivery is enabled for your organization, your organization’s claude.ai connectors reach sessions through `api.anthropic.com`. See [MCP servers](self-hosted-environments-configuration.md#mcp-servers).

These host-side flows do reach `claude.ai`, so run them from a host whose egress allows it rather than widening session-container egress:

- **The one-line installer**: fetches `install.sh` from `claude.ai` at install time.
- **Interactive `claude auth login`**: signs in through `claude.ai`, `claude.com`, and `platform.claude.com`. The [guided setup](self-hosted-environments-quickstart.md#run-the-guided-setup), `doctor`’s signed-in mode, and [CI dispatch](self-hosted-environments-testing.md#authenticate-from-ci) use it. The browser you sign in with also loads the claude.ai sign-in page’s browser checks from `hcaptcha.com`, `*.hcaptcha.com`, and `challenges.cloudflare.com`. If your organization uses single sign-on, that browser can also be redirected through `sso.claude.com`.


[​](#default-deny-egress)

Default-deny egress

Deploy runner and session containers in a network segment or namespace whose outbound traffic is limited to the hosts in the [network requirements table](#network-requirements), your git host, and the specific internal services sessions need to reach. The product can’t verify or enforce this, so apply it at your own network boundary on every environment. Session code is model-directed and can attempt connections to arbitrary hosts; default-deny egress at the network layer bounds where those attempts can land. This applies regardless of permission mode: the default pre-approved tool set already includes `Bash`, so shell egress runs without a prompt even without [auto mode](self-hosted-environments-configuration.md#permissions-and-tool-approval). For details on which telemetry each session emits and how to turn it off, see [Telemetry](self-hosted-environments-reference.md#telemetry).


[​](#authenticate-to-an-egress-proxy)

Authenticate to an egress proxy

Some corporate egress proxies require a `Proxy-Authorization` header on every connection. The token in that header often rotates too fast to write into the proxy URL you set in `HTTPS_PROXY`. Set `HTTPS_PROXY` or `HTTP_PROXY` to your proxy’s URL as usual, then set `--proxy-authorization-command` or `--proxy-authorization-file` to tell the runner where to read the header value from. Both flags require Claude Code v2.1.238 or later.


[​](#choose-where-the-proxy-authorization-value-comes-from)

Choose where the `Proxy-Authorization` value comes from

Pick the flag that matches how you produce the `Proxy-Authorization` token:

- **[`--proxy-authorization-command <command>`](self-hosted-environments-reference.md#runner-cli-flags)**: choose this for a token you generate on demand. The runner runs the shell command and uses its trimmed stdout as the header value, for example `Bearer <token>`.
- **[`--proxy-authorization-file <path>`](self-hosted-environments-reference.md#runner-cli-flags)**: choose this for a token another process rotates in place. The runner reads the file and uses its trimmed contents as the header value.


[​](#configurations-the-runner-refuses-to-start-with)

Configurations the runner refuses to start with

Each flag also has an environment variable form, listed beside it in the [runner CLI flags reference](self-hosted-environments-reference.md#runner-cli-flags). Before the runner contacts your proxy or the control plane, it checks the flags and their variables, and refuses to start in three cases:

- **Both flags set**: one flag plus the other flag’s environment variable counts as setting both.
- **No proxy URL**: neither `HTTPS_PROXY` nor `HTTP_PROXY` holds an `http://` or `https://` URL. The runner reads both variables in upper or lower case, and doesn’t consult `ALL_PROXY`.
- **Either flag passed to the orchestrator subcommand**: `self-hosted-runner orchestrator` doesn’t accept the flags or their environment variables. Pass the flag to each runner the orchestrator starts instead.


[​](#what-the-runner-changes-while-a-proxy-authorization-flag-is-set)

What the runner changes while a proxy-authorization flag is set

With either flag set, the runner starts a listener of its own and sends proxy traffic from itself, its lifecycle hooks, and its sessions through that listener. The listener adds the `Proxy-Authorization` header on the way to your proxy.

- **Listener**: the listener is a forward proxy on `127.0.0.1`. The runner starts the listener before registering with the control plane, and exits at startup if the listener can’t start.
- **Proxy variables**: the runner rewrites whichever of `HTTPS_PROXY` and `HTTP_PROXY` you set so that it points at the listener. That rewritten value reaches the runner itself, its lifecycle hooks, and every session it runs.
- **Token rotation**: a rotated token takes effect without a restart. For each connection the listener opens to your proxy, the runner runs your command or reads your file again and adds the result as the header.
- **Session environment**: a session reaches your proxy only through the listener. In each session’s environment the runner removes `ALL_PROXY`, removes any spelling of `HTTPS_PROXY` or `HTTP_PROXY` that you didn’t set, and pins `NO_PROXY` to the runner’s own value.
- **Logs**: the runner never logs the header value.


[​](#configure-git)

Configure git

The runner manages repository checkouts but doesn’t configure git identity or credentials by default. You control the runner’s image and process environment, so you control the git config. Choose one of two approaches:

- **Let the runner configure git**: start the runner with `--configure-git` to have it write the same identity and commit-signing config that Anthropic-hosted sessions use
- **Ship git config in your image**: set identity and push credentials yourself, for example to commit under your own bot identity

For repositories on github.com, you can also start the runner with [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), or set `CLAUDE_RUNNER_USE_GIT_PROXY=1`, to ask Anthropic to serve git for the runner’s sessions. Git version floors on the runner host: [`--configure-git`](#let-the-runner-configure-git) SSH commit signing requires Git 2.34 or later, [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy) requires 2.32 or later, and resuming sessions from branches pushed by [`--push-outcome-on-release`](self-hosted-environments-reference.md#runner-cli-flags) requires 2.29 or later. Git 2.24 is sufficient if you omit all three and manage git identity yourself.


[​](#let-the-runner-configure-git)

Let the runner configure git

Start the runner with `--configure-git`, or set `SELF_HOSTED_RUNNER_CONFIGURE_GIT=1`, to have it write global git config at startup:

- `user.name = Claude` and `user.email = noreply@anthropic.com`, matching Anthropic-hosted sessions
- SSH-format commit and tag signing, routed through a runner-managed shim that signs each commit via Anthropic’s signing service using the session’s own credentials. Signatures are verifiable on GitHub against Anthropic’s published SSH signing key.
- `push.negotiate = true`, so git asks your git host which commits it already has before packing a push. Requires Claude Code v2.1.257 or later.
- `core.hooksPath` pointing at a runner-managed hooks directory. Its `commit-msg` and `prepare-commit-msg` hooks add a `Co-authored-by:` trailer for the session’s creator to each commit. The trailer is built from the email in [`CCR_SESSION_ACCOUNT_EMAIL`](self-hosted-environments-configuration.md#wrapper-scripts) and omitted when that variable is unset. If your image already sets `core.hooksPath` and the runner doesn’t use [Anthropic-managed git](#use-the-anthropic-git-proxy), the runner keeps your setting, skips installing these hooks, and prints a `[runner:git]` warning.

Commit signing requires git 2.34 or later; the runner checks at startup and exits with an error if your git is older. This flag doesn’t configure push credentials, which you still provide in the image. On a runner on v2.1.280 or later, commits you make from a `checkout` or `post-session` lifecycle hook are signed as the session too, without the `Co-authored-by:` trailer. [Git configuration inside lifecycle hooks](self-hosted-environments-configuration.md#git-configuration-inside-lifecycle-hooks) describes the git settings the runner fixes inside those hooks. With or without `--configure-git`, Claude Code instructs Claude to end its commit messages with a `Claude-Session: <url>` trailer and its pull request descriptions with the session’s URL. To omit both, set [`attribution.sessionUrl`](../02-Claude-Code-CLI/settings-reference.md#attribution-sessionurl) to `false` in the runner host’s [`~/.claude/settings.json`](self-hosted-environments-configuration.md#how-each-session%E2%80%99s-config-is-assembled), then restart the runner.


[​](#ship-git-config-in-your-image)

Ship git config in your image

Git identity is required for any commit. Set it system-wide in your Dockerfile so the config applies regardless of which user the runner process runs as:

```python
RUN git config --system user.name "Claude" && \
    git config --system user.email "noreply@anthropic.com"
```

Without an identity, `git commit` fails with `Please tell me who you are` and sessions can’t make progress. You can use your own bot identity instead; the runner doesn’t override these values. Don’t bake long-lived or broadly scoped push credentials into a shared runner image: a credential in the image is available to every session the image runs, whoever started it. Instead, mint a short-lived, least-scoped token per session from your [wrapper script](self-hosted-environments-configuration.md#wrapper-scripts), using the session creator’s identity decoded from the session JWT. Pair it with an ephemeral per-session container, which requires `--capacity 1`, so no credential outlives the session that minted it; see the [hardening section](#harden-your-deployment). If you must configure push credentials at the image level, for example for a read-only deploy key, scope them as tightly as your git host allows:

- An SSH deploy key limited to one repository with a `url.<base>.insteadOf` rewrite
- A `credential.helper` that returns a minimally scoped token
- `GIT_SSH_COMMAND` pointing at a narrowly scoped key

Whichever mechanism you configure must work without a prompt, because the runner’s built-in clone and fetch disable the prompts that git, SSH, and Git Credential Manager would otherwise show:

- The runner sets `GIT_TERMINAL_PROMPT=0`, so git doesn’t ask for a username or password.
- The runner runs SSH with `BatchMode=yes`, appended to your `GIT_SSH_COMMAND` if you set one, so SSH doesn’t ask for a passphrase or host confirmation.
- The runner sets `GCM_INTERACTIVE=never`, so Git Credential Manager doesn’t open a sign-in dialog.
- The runner clears `core.askPass`, so if you use an askpass helper, set it through the `GIT_ASKPASS` environment variable instead.

If your git host rejects the credential, or you didn’t configure one, the runner retries a few times and then fails repository preparation when the repository is the one the session pushes results to. For a repository the session only reads from, [Troubleshooting](#troubleshooting) covers when the runner skips it instead. The runner doesn’t pass these settings into the session’s environment. Keep any program you name in `GIT_SSH_COMMAND` or `GIT_ASKPASS` where sessions can’t write to it, the way the [hardening checklist](#harden-your-deployment) asks for the hooks directory and the wrapper script. The same goes for any key or file on that program’s command line. The runner’s own git runs that program when it clones or fetches. If checkout directories are owned by a different uid than the runner process, git refuses to operate on them; add `safe.directory`:

```python
RUN git config --system --add safe.directory '*'
```


[​](#use-the-anthropic-git-proxy)

Use the Anthropic git proxy

With the Anthropic git proxy, also called Anthropic-managed git, the runner image needs no SSH keys, credential helper, `.netrc`, or other git credentials for the session itself. Instead, the runner asks Anthropic to serve git for its sessions. For a user’s session that Anthropic serves, the runner’s clone and the session’s own fetches and pushes go through Anthropic, which uses the GitHub OAuth token stored for the session’s creator. [How Anthropic serves git for a session](#how-anthropic-serves-git-for-a-session) covers bot and agent sessions. The git proxy is off unless you [turn it on](#turn-the-anthropic-git-proxy-on). A runner that reaches your git host with its own credentials doesn’t need it, and its git works with any git host. In exchange, the git proxy limits what the runner supports and changes what it needs:

- **github.com only**: Anthropic serves a session only when all of its repositories are on github.com, and the git proxy doesn’t support GitHub Enterprise Server yet. On a runner with the git proxy, a session with a repository on another git host [fails to start](#when-anthropic-doesnt-serve-a-session).
- **Credentials for the session’s repositories only**: Anthropic supplies git credentials for the repositories that are part of the session, not for other repositories on the same git host. A private submodule, a dependency that your package manager fetches with git, or a plugin marketplace in another repository gets no credential from Anthropic. Ask the people who create sessions to [add every repository](../02-Claude-Code-CLI/web-quickstart.md#start-a-task) a session needs when they create it. Each run of a [cloud scheduled task](../02-Claude-Code-CLI/web-scheduled-tasks.md) is a session with the task’s repositories, so ask each task’s owner to [add every repository its runs need](../02-Claude-Code-CLI/web-scheduled-tasks.md#edit-and-control-routines).
- **Branch pushes only**: a push that deletes a branch fails, and so does a push to any other kind of ref, such as a tag. For which branches a push can update, see [GitHub proxy](../02-Claude-Code-CLI/cloud-environments.md#github-proxy).
- **Connected GitHub accounts**: ask each person who creates user sessions with a repository to [connect GitHub](../02-Claude-Code-CLI/web-quickstart.md#connect-github). Without their own connection, those sessions [don’t start](#creator-has-no-github-connection).
- **`--capacity 1`**: the git proxy requires one session per runner process, so run more replicas for parallelism. [Turn the Anthropic git proxy on](#turn-the-anthropic-git-proxy-on) lists the requirements.
- **Replaced global git config**: the runner [deletes and replaces the global git config](#git-proxy-replaces-global-git-config) of the user it runs as. Run it as a dedicated user or in a container.
- **Host credentials for host pushes**: the runner’s [`--push-outcome-on-release`](self-hosted-environments-reference.md#runner-cli-flags) push and any push your [`post-session` hook](self-hosted-environments-configuration.md#post-session) makes still use the runner host’s own git credentials and its [network path to `github.com`](#github-com-egress-with-the-anthropic-git-proxy). Keep those credentials out of the [global git config](#git-proxy-replaces-global-git-config), and see [Ship git config in your image](#ship-git-config-in-your-image) for ways to supply them.
- **Per-session decision**: Anthropic decides for each session on the runner whether to serve its git, and a session it doesn’t serve fails to start. [When sessions fail to start on a runner with the git proxy](#when-anthropic-doesnt-serve-a-session) covers the causes.

With `--use-anthropic-git-proxy` set, the runner deletes and replaces the global git config of the user it runs as, and keeps no backup. It does this at startup and before each session. A login or credential helper you kept there is lost. Settings that [`--configure-git`](#let-the-runner-configure-git) writes survive. Run the runner as a dedicated user or in a container, never as your own user.

Keep git settings that aren’t secret, such as identity and `safe.directory`, in the system git configuration.


[​](#turn-the-anthropic-git-proxy-on)

Turn the Anthropic git proxy on

Before you start the runner with `--use-anthropic-git-proxy`, confirm that the runner host meets each of these requirements. The runner refuses to start when the capacity or git requirement is unmet:

- **Claude Code v2.1.267 or later**: earlier versions accept the flag but don’t report the request for Anthropic to serve git or print the `Registering as opted in` line, so Anthropic doesn’t serve their sessions.
- **`--capacity 1`, the default**: each runner process handles one session at a time, so run more replicas for parallelism.
- **Git 2.32 or later**: older git ignores the per-session git configuration that the runner sets up for the git proxy.

The [Kubernetes](#kubernetes) and [Docker Compose](#docker-compose) recipes on this page use `--capacity 4`. If you add `--use-anthropic-git-proxy` or `CLAUDE_RUNNER_USE_GIT_PROXY=1` to one of them without changing the capacity to `1`, the runner exits at startup every time your orchestrator restarts it. Set `--capacity 1` and run more replicas for parallelism. [When the runner exits](#when-the-runner-exits) shows the line the runner prints.

To turn the git proxy on, add `--use-anthropic-git-proxy` to the runner’s command, or set `CLAUDE_RUNNER_USE_GIT_PROXY=1` in the runner’s environment. This command, run in a shell on the runner host, starts the [quickstart](self-hosted-environments-quickstart.md#set-up-manually)’s runner with the git proxy on:

```python
claude self-hosted-runner --environment-secret-file '/etc/claude/environment-secret' --base-dir '<writable-dir>' --use-anthropic-git-proxy
```

At startup, the runner prints `Registering as opted in to Anthropic-managed git (--use-anthropic-git-proxy)`. Anthropic then decides for each session on that runner whether to serve its git. For each session it serves, the runner logs a `[runner:session]` line containing `governed git ACTIVE`. If a session fails to start instead, see [When sessions fail to start on a runner with the git proxy](#when-anthropic-doesnt-serve-a-session).


[​](#how-anthropic-serves-git-for-a-session)

How Anthropic serves git for a session

For a session that Anthropic serves, the runner’s clone and the session’s own fetches and pushes go through Anthropic, authenticated with the session’s own short-lived token:

- **User sessions**: Anthropic uses the GitHub OAuth token stored for the session’s creator.
- **Bot and agent sessions**: Anthropic uses your organization’s GitHub App installation token.
- **URL rewrites**: `--git-host-rewrite` and `--git-ssh-rewrite` have no effect on a repository that the git proxy serves.


[​](#when-anthropic-doesnt-serve-a-session)

When sessions fail to start on a runner with the git proxy

On a runner started with `--use-anthropic-git-proxy`, a session fails to start when Anthropic doesn’t serve its git. Look in the runner’s log for a git error that names an `api.anthropic.com` address containing `/git_proxy/`. For each session, a runner on Claude Code v2.1.267 or later also logs either a `[runner:session]` line containing `governed git ACTIVE` when Anthropic serves the session’s git, or one `[runner:warn]` line containing `the server withheld Anthropic-managed git for this session` when it doesn’t. Find the line you’re seeing among these cases:

- **Neither `governed git ACTIVE` nor the `withheld` line**: a runner older than Claude Code v2.1.267 logs neither line, and Anthropic doesn’t serve its sessions. Update the runner to v2.1.267 or later by following [Pin the version](#pin-the-version).
- **The `withheld` line**: Anthropic didn’t serve the session. A runner that worked with the git proxy before can fail this way with no change on your side.
  - **A repository isn’t on github.com**: a session with even one repository on another git host, such as GitHub Enterprise Server, isn’t served, including its github.com repositories. [Turn the Anthropic git proxy off](#turn-the-anthropic-git-proxy-off) for that environment’s runners.
  - **Every repository is on github.com**: report the failure to [your Anthropic account team](#report-an-issue) with the session ID from the `withheld` line. Anthropic records the reason on its side.
- **A line containing `remote: access denied by the git proxy`**: a session that Anthropic serves can still be refused, for example when organization policy denies git access for the session, or the session isn’t authorized for the repository. The runner’s log then shows a line containing `remote: access denied by the git proxy`, and the rest of that line says why.
- **`GitHub authentication required`**: this appears when the session’s creator has no working GitHub connection on claude.ai. The session’s clone fails, and the git error contains `GitHub authentication required. Please reconnect your GitHub account.` Ask that person to connect or reconnect GitHub in their claude.ai settings.

After you fix the cause, start the failed sessions again.


[​](#turn-the-anthropic-git-proxy-off)

Turn the Anthropic git proxy off

If sessions in an environment use a repository on a git host other than github.com, such as GitHub Enterprise Server, turn `--use-anthropic-git-proxy` off for that environment’s runners.

1

Remove the flag

Remove `--use-anthropic-git-proxy` from the runner’s command. If you set `CLAUDE_RUNNER_USE_GIT_PROXY` in the runner’s environment, such as a pod spec or a Compose file, remove it there. In a shell, unset it:

```python
unset CLAUDE_RUNNER_USE_GIT_PROXY
```

2

Give the runner git credentials

Provide credentials that work without a prompt for every git host the runners’ sessions use, github.com included. Any credential that was in the runner user’s global git config is gone, because the runner deleted that config while `--use-anthropic-git-proxy` was set. [Ship credentials in your image](#ship-git-config-in-your-image) or use a [`checkout` lifecycle hook](self-hosted-environments-configuration.md#checkout).

3

Open the network path

Allow the runner to reach each git host the runners’ sessions use on port 443 or 22. See the git host row in [Network requirements](#network-requirements).

4

Restart the runners

Restart the runners so that they register without the git proxy. Then start each failed session again.


[​](#github-api-access-without-the-github-cli)


If your runner image doesn’t include the GitHub CLI, Claude Code can provide a built-in `gh`, so Claude can still open pull requests, comment, and read CI results. The built-in `gh` is for runners that use Anthropic-managed git. It supports one command, `gh api`, which calls GitHub’s REST API. Requires Claude Code v2.1.287 or later in the runner image. This command opens a pull request in place of `gh pr create`. The built-in `gh` fills in `{owner}` and `{repo}` for the current repository:

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

- **Credentials**: the built-in `gh` sends its REST requests through Anthropic-managed git, which supplies the GitHub credential on Anthropic’s side, so the image needs no GitHub token for it
- **Which sessions get it**: Anthropic decides per session whether Anthropic-managed git serves the session’s `gh`. When it does, the `[runner:session] governed git ACTIVE` line that the runner logs for the session shows `gh_path_shim=true`. When it doesn’t, the session has no `gh`
- **`jq`**: install `jq` in the image if you want `--jq` to work
- **[`CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`](../02-Claude-Code-CLI/env-vars.md)**: if the session environment sets it, Claude Code doesn’t provide the built-in `gh`, and the session has no `gh`

When the image includes the GitHub CLI, sessions use it.


[​](#trust-a-private-certificate-authority-with-anthropic-managed-git)

Trust a private certificate authority with Anthropic-managed git

This section applies if you set `GIT_SSL_CAINFO` or `GIT_SSL_NO_VERIFY` in the environment of a runner whose sessions use Anthropic-managed git. The handling it describes requires the runner to run Claude Code v2.1.283 or later. When git on the runner must trust a private certificate authority (CA), such as the one a TLS-inspecting proxy signs with, the usual approaches work out as follows:

- **System certificate store**: install your CA in the runner host’s system certificate store, and git trusts it without either variable.
- **`GIT_SSL_CAINFO`**: set it to a PEM file of your CAs, for example `GIT_SSL_CAINFO=/etc/ssl/corp-ca.pem`.
- **`GIT_SSL_NO_VERIFY`**: doesn’t help behind a re-signing proxy. The runner’s own clone through Anthropic-managed git checks certificates even when the variable is set, so that clone fails until git trusts your CA through one of the other two approaches.

For git connections that carry a session’s token to Anthropic-managed git, the runner applies the two variables as follows. A [`command` hook](self-hosted-environments-configuration.md#command) starts with the session’s environment, so it gets what git inside the session gets:

- **`GIT_SSL_CAINFO`**: what git checks Anthropic-managed git against depends on where git runs:
  - **Runner’s own clone and fetches**: run without the variable and check Anthropic-managed git against a per-session certificate file the runner writes. The runner builds that file from the runner host’s system CA bundle and the certificates in your file.
  - **Git inside the session**: gets `http.sslCAInfo` configuration naming your file in place of the variable, plus `http.<url>.sslCAInfo` entries that check Anthropic-managed git against the per-session file.
  - **`checkout` and `post-session` hooks**: inherit the variable unchanged.
- **`GIT_SSL_NO_VERIFY`**: which certificate checks stay off depends on where git runs:
  - **Runner’s own clone and fetches**: run without the variable and check the certificate they’re presented.
  - **Git inside the session**: gets `http.sslVerify=false` configuration in place of the variable, so checks stay off for other hosts. It also gets `http.<url>.sslVerify=true` entries that keep checks on for Anthropic-managed git.
  - **`checkout` and `post-session` hooks**: when the session has a repository on Anthropic-managed git, get `http.sslVerify=false` configuration in place of the variable. They also get `http.<url>.sslVerify=true` entries that keep checks on for Anthropic-managed git.

The per-session certificate file needs a system CA bundle at `/etc/ssl/certs/ca-certificates.crt` or `/etc/pki/tls/certs/ca-bundle.crt` on the runner host. It also needs a `GIT_SSL_CAINFO` file that the runner’s user can read, that holds PEM `CERTIFICATE` blocks, and that is at most 1 MiB. When the runner can’t build the per-session file, it logs a `[runner:warn]` line containing `did not build the certificate file` and the reason. Git then uses your file as it is for Anthropic-managed git. Fix what the line names. For each session that uses Anthropic-managed git, the runner also logs a `[runner:warn]` line that begins `governed git: GIT_SSL_CAINFO is set` or `governed git: GIT_SSL_NO_VERIFY is set`. The line says what the runner did with that variable for its own git, for git inside the session, and for your lifecycle hooks. It ends with whether you need to change anything.


[​](#rewrite-git-urls-for-private-networks)

Rewrite git URLs for private networks

Repository URLs arrive from the control plane as HTTPS, with the hostname of your git host; for GitHub Enterprise, that’s the hostname you configured for the [GitHub Enterprise integration](../02-Claude-Code-CLI/github-enterprise-server.md) on claude.ai. Two repeatable flags rewrite those URLs before clone:

- `--git-host-rewrite <from>=<to>`: for split-horizon DNS, where Anthropic reaches your git host via an external hostname but runners must use an internal one
- `--git-ssh-rewrite <host>`: for git hosts that only accept SSH, or a runner host that has only an SSH key. For example, to clone github.com repositories over SSH, add `--git-ssh-rewrite github.com` to the runner command. The runner then rewrites `https://github.com/owner/repo` to `git@github.com:owner/repo`. SSH must [connect without a prompt](#git-must-work-without-a-prompt).

Host rewriting runs first, so list the internal hostname in `--git-ssh-rewrite` if you need both. For full control over checkout, use a [`checkout` lifecycle hook](self-hosted-environments-configuration.md#checkout).


[​](#build-the-runner-image)

Build the runner image

Anthropic doesn’t publish a pre-built runner image. Build your own around the `claude` binary, layering in whatever toolchain your repositories need: language runtimes, compilers, package managers, and [MCP](../06-MCP-Tools/General/mcp.md) sidecars. The recipes below use `--capacity 4`, so one container serves up to four concurrent sessions from the same locked owner. That doesn’t provide the per-session container isolation in the [hardening section](#harden-your-deployment): before connecting an environment to production systems, either run the recipes at `--capacity 1` with one container per session, or use [on-demand runners](self-hosted-environments-configuration.md#on-demand-runners), which also keep the environment secret off session-running hosts. If you add the [Anthropic git proxy](#use-the-anthropic-git-proxy) to one of these recipes, also change `--capacity` to `1`. This Dockerfile is a minimal starting point:

```python
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 \
 && 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"]
```

Swap `linux-x64` for `linux-arm64` if your nodes are ARM, or for `linux-x64-musl` or `linux-arm64-musl` on a musl-based image such as Alpine; see [Alpine Linux setup](../01-Getting-Started/setup.md#alpine-linux-and-musl-based-distributions) for the extra packages musl images need. The URL is the standard Claude Code release location, so you can verify the downloaded binary against the release’s signed manifest as described in [Binary integrity and code signing](../01-Getting-Started/setup.md#binary-integrity-and-code-signing). The runner requires Claude Code version 2.1.224 or later. Build the image, then push it to your registry and reference it in the recipes below:

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

The command substitution looks up the current `stable` release number and passes it as the build argument, so running the same command after a new stable release rebuilds the download layer with the newer binary. To pin a specific release for reproducible builds, pass the version number directly as `CLAUDE_CODE_VERSION`. Replace `stable` with `latest` in the lookup URL when you need a release newer than the stable channel, such as one a [newly launched model requires](../02-Claude-Code-CLI/model-config.md).


[​](#size-cpu-and-memory-for-sessions)

Size CPU and memory for sessions

Size a runner’s container or host for the sessions it runs rather than for the runner process. The runner itself polls for work, prepares each session’s checkout, runs your [lifecycle hooks](self-hosted-environments-configuration.md#lifecycle-hooks), and starts and supervises the session processes. The load comes from the sessions: each one is a Claude Code process plus whatever it starts, such as builds, test suites, package installs, and [MCP servers](../06-MCP-Tools/General/mcp.md). For one session, start with the following values, stated as Kubernetes requests and limits or your platform’s equivalent, and treat them as a starting point rather than a requirement:

- **Memory**: a request and a limit of 4 GiB each, which meets the 4 GB minimum in Claude Code’s [system requirements](../01-Getting-Started/setup.md#system-requirements). Keep the two equal so the scheduler accounts for the container’s full memory. When the container reaches its memory limit, the kernel kills processes inside it, which can end a session mid-task.
- **CPU**: a request of 2 CPUs and a limit of 4 CPUs, so a session can burst above the request during builds. The kernel throttles a container at its CPU limit rather than killing processes in it, so sessions at the limit run slower but keep running.

In a Kubernetes container spec, set those starting values with the following `resources` block:

```python
resources:
  requests:
    cpu: "2"
    memory: 4Gi
  limits:
    cpu: "4"
    memory: 4Gi
```

Builds and tests are usually the largest and most variable part of a session’s load, so run a representative build of your repository, measure its peak CPU and memory, and raise any starting value that leaves no room for the Claude Code process on top of that peak. The runner uses `--capacity` to cap how many sessions it runs at once. It doesn’t divide CPU or memory between them, so the sessions on a runner share the container’s CPU and memory. To cap one session’s share, apply limits from your [wrapper script](self-hosted-environments-configuration.md#wrapper-scripts). What to give one container therefore depends on how many sessions it serves at once:

- **One session per runner**: give each container one session’s values. Use this sizing at `--capacity 1`, which the [hardening section](#harden-your-deployment) recommends, and for [on-demand runners](self-hosted-environments-configuration.md#on-demand-runners), where you set the values on the workload your [`spawn-runner` hook](self-hosted-environments-configuration.md#the-spawn-runner-hook) submits, such as a Kubernetes Job’s pod template.
- **Several sessions per runner**: at a `--capacity` above one, multiply one session’s values by the capacity, because up to that many sessions can run in the container at the same time. The [Kubernetes](#kubernetes) and [Docker Compose](#docker-compose) recipes run `--capacity 4` with no CPU or memory limits, so add limits sized for the capacity you run.


[​](#kubernetes)

Kubernetes

The runner serves `GET /healthz` on port 8080 by default, configurable with `--health-port`, so Kubernetes probes work with no extra setup. The endpoint returns `200` whenever the process is alive, so the probes below detect a dead process, not a stuck one; to catch a runner that stopped polling, alert on the `last_poll_age_seconds` series from [`/metrics`](self-hosted-environments-reference.md#prometheus-metrics). The Deployment below mounts the environment secret from a Kubernetes Secret, points the liveness and readiness probes at `/healthz`, and sets a 90-second termination grace period. See [Shutdown timing](#shutdown-timing) for why the grace period matters. The manifest sets no CPU or memory `resources` on the runner container. Add a block sized for the capacity you run, as [Size CPU and memory for sessions](#size-cpu-and-memory-for-sessions) describes.

```python
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
```

The Deployment above lives in a `claude-runners` namespace. Create the namespace first:

```python
kubectl create namespace claude-runners
```

Create the backing Secret from a local file holding the value you copied in the admin UI’s [**Copy environment key** step](self-hosted-environments-quickstart.md#set-up-manually), so the secret never appears in your shell history. Run `(umask 077 && cat > ./environment-secret)`, paste the secret, press Enter, then Ctrl-D. Then create the Secret and delete the file:

```python
kubectl create secret generic claude-runner-environment-secret -n claude-runners --from-file=environment-secret=./environment-secret
```


[​](#docker-compose)

Docker Compose

The Compose service below restarts the runner whenever it exits, which covers both crashes and the normal exit after draining. A Docker restart policy restarts the same container with its writable layer intact, so the runner comes back on a reused filesystem rather than the fresh one the [hardening posture](#harden-your-deployment) recommends; use this recipe for evaluation, and for production either recreate the container per run or use an orchestrator that does. Docker waits longer before each restart of a container that keeps exiting, up to a ceiling, so a runner that can’t start doesn’t keep restarting in a tight loop under this recipe. [When the runner exits](#when-the-runner-exits) describes what to check when that happens.

```python
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
```


[​](#shutdown-timing)

Shutdown timing

After `SIGTERM`, a runner needs time to shut its sessions down cleanly before your orchestrator kills it. It logs how long at startup, in a line that contains `This runner needs up to 80s` at default settings. Set your orchestrator’s stop timeout to at least that many seconds: `terminationGracePeriodSeconds` on Kubernetes, `stop_grace_period` on Docker Compose, or your platform’s equivalent. Kubernetes defaults to 30 seconds, so without this setting it can stop the pod before the runner finishes. On `SIGTERM`, the runner stops accepting new sessions. It then begins a graceful shutdown, called a drain, unless you set [`--defer-shutdown-max-min`](#defer-the-drain-past-the-first-signal) to delay it. The drain has three steps:

1.  The runner waits up to [`--drain-wait-sec`](self-hosted-environments-reference.md#runner-cli-flags) seconds, `0` by default, for turns still running to finish.
2.  It terminates each session’s process tree, including any command Claude was still running, but not [a process still running after its shell command exited](#processes-a-stopped-session-leaves).
3.  It runs the [`post-session` lifecycle hook](self-hosted-environments-configuration.md#post-session).

The runner keeps polling Anthropic throughout the drain. That keeps its sessions assigned to it, so another runner doesn’t pick one up while your `post-session` hook is still saving uncommitted work. The logged time is the sum of these values:

- `--drain-wait-sec` for step 1, 0 seconds by default
- [`--session-stop-grace-sec`](self-hosted-environments-reference.md#runner-cli-flags) for step 2, 5 seconds by default
- [`--post-session-hook-timeout-sec`](self-hosted-environments-reference.md#runner-cli-flags) for step 3, 60 seconds by default
- A fixed 15 seconds of headroom
- 30 more seconds when [`--push-outcome-on-release`](self-hosted-environments-reference.md#runner-cli-flags) is set

At default settings that comes to 0 + 5 + 60 + 15 = 80 seconds. A higher `--capacity` doesn’t add to it, because the runner drains all of its sessions at the same time. Allow more time if you set either of these flags:

- **With [`--retire-at`](self-hosted-environments-reference.md#runner-cli-flags)**: leave enough time between the retire time and the time the host stops for typical turns to finish, plus the wait for background tasks that [Runner lifecycle](self-hosted-environments.md#runner-lifecycle) describes, plus the logged time. Compute the retire time at each launch, for example `date +%s` plus the runner’s intended lifetime.
- **With [`--defer-shutdown-max-min`](#defer-the-drain-past-the-first-signal)**: the stop timeout must also cover the minutes you configure and a further wait before the drain starts, 75 seconds at default settings. [Defer the drain past the first signal](#defer-the-drain-past-the-first-signal) explains both. The runner logs this longer time at startup too.


[​](#let-running-turns-finish-before-a-restart)

Let running turns finish before a restart

To let running turns finish before a rolling restart, set `--drain-wait-sec`, and raise your stop timeout to at least that wait plus 80 seconds. With the default wait of `0`, a restart cuts off any turn that’s still running, and the session resumes on another runner without its [unpushed work](#additional-limitations).

- **`--drain-wait-sec`**: about as long as the turns and background tasks you want to let finish. It’s a maximum: the wait ends early if they finish first.
- **Your stop timeout**: `terminationGracePeriodSeconds` in Kubernetes, `stop_grace_period` in Docker Compose. For example, with `--drain-wait-sec 120`, replace the `90` in the [Kubernetes](#kubernetes) or [Docker Compose](#docker-compose) recipe with `200`.
- **If you changed other shutdown settings**: the 80 seconds are for default settings. Start one runner with your flags, and use at least the number in its startup line `This runner needs up to`, which includes the wait. With [`--defer-shutdown-max-min`](#defer-the-drain-past-the-first-signal) set, use the larger number in the next startup line, after `Size the stop timeout to at least`. [Shutdown timing](#shutdown-timing) lists what adds to these numbers.

If the stop timeout runs out during the wait, your host kills the runner with work still running. Its sessions get no `post-session` hook. The runner also doesn’t deregister, so the control plane can take a few minutes and sometimes longer to requeue them.


[​](#post-a-status-message-before-a-restart)

Post a status message before a restart

Before a restart that will interrupt sessions, have an [Owner](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) post a status message on the environment from the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments). At [claude.ai/code](https://claude.ai/code), members see the message in their sessions in that environment. Have the Owner set an expiry with the post, or remove the message once the runners are back.


[​](#defer-the-drain-past-the-first-signal)

Defer the drain past the first signal

Set [`--defer-shutdown-max-min <n>`](self-hosted-environments-reference.md#runner-cli-flags) if you want a runner you’re restarting to go on serving the sessions it holds for up to `n` minutes, instead of draining them on the first signal. On the first `SIGTERM` or `SIGINT`, the runner stops taking new work and goes on serving the sessions it holds. It keeps polling so that the control plane doesn’t requeue those sessions. Requires Claude Code v2.1.238 or later.


[​](#what-happens-to-the-sessions-the-runner-holds-after-the-first-signal)

What happens to the sessions the runner holds after the first signal

In the first two stages that follow the signal, the runner releases sessions, and a released session resumes on a fresh runner when its user sends their next message. Counting from the first signal, the runner moves through three stages:

- **For the first `n` minutes**: the runner serves its sessions normally and keeps enforcing `--startup-timeout-min` and `--kill-session-after-min`. If you also set [`--release-idle-session-min`](self-hosted-environments-reference.md#runner-cli-flags), the runner releases any session whose user has been idle that long; without it, idle sessions stay on the runner.
- **When the `n` minutes run out**: the runner releases every session it still holds, idle or not. The runner waits for a mid-turn session’s turn to end, and up to 60 seconds more for a turn’s background tasks, before releasing that session.
- **When the post-release grace runs out**: the runner drains whatever sessions it still holds, and the control plane requeues each drained session to another runner right away. The post-release grace starts when the `n` minutes run out and is 75 seconds at defaults. If you set `--drain-wait-sec` above 60 seconds, the post-release grace is `--drain-wait-sec` plus 15 seconds instead.

At any stage, the runner exits 0 as soon as it holds no sessions. A second signal cuts the stages short: the runner drains immediately, as it does on the first signal without `--defer-shutdown-max-min`. Once a drain is under way, the next signal force-exits the runner. That holds whether a second signal or the post-release grace running out started the drain.


[​](#size-the-stop-timeout)

Size the stop timeout

Give your host’s stop timeout at least the sum of three parts: the `n` minutes you configure, the post-release grace, and the drain that [Shutdown timing](#shutdown-timing) describes. With default settings the post-release grace is 75 seconds and the drain takes up to 80 seconds, so allow `n` minutes plus 155 seconds. The runner prints this sum at startup whenever `--defer-shutdown-max-min` is set. If the stop timeout runs out before the runner finishes, the sessions it still holds get no `post-session` hook. The runner doesn’t deregister, so the control plane can take a few minutes and sometimes longer to requeue those sessions. If you can’t give the stop timeout that sum, leave `--defer-shutdown-max-min` unset so the runner drains on the first signal instead.


[​](#what-reaches-a-running-post-session-hook)

What reaches a running post-session hook

The `post-session` hook and the Claude session child each run in their own POSIX process group, separate from the runner’s, so stop mechanisms reach them differently:

- **A `SIGTERM` while the runner is already draining**: force-exits the runner immediately, skipping whatever remains of the drain. Without [`--defer-shutdown-max-min`](#defer-the-drain-past-the-first-signal), that is the second `SIGTERM` the runner receives. Nothing signals a mid-run `post-session` hook, so on a bare host where an init process adopts orphans, it finishes on its own, but unsupervised: its timeout budget no longer applies, and a write to the closed log pipe can kill it with `SIGPIPE`, so a hook that needs to survive a forced exit there should redirect its own output to a file. In the container recipes on this page the runner is the container’s PID 1 and its exit ends the container, and under systemd’s default `KillMode=control-group` the cgroup-wide kill reaches the hook too, as the **Cgroup-wide kills** entry describes; in both, treat a forced exit as fatal to the hook and rely on the grace period instead.
- **Process-group-wide signals**, such as `kill -- -<pid>` in a wrapper script, shell job control, or a group-wide watchdog: reach the runner and a mid-`checkout`-hook subprocess, which stays group-attached deliberately, but not a mid-run `post-session` hook or the session child.
- **Cgroup-wide kills**, such as systemd’s default `KillMode=control-group` or the `SIGKILL` Kubernetes delivers to the whole container when `terminationGracePeriodSeconds` expires: reach everything, including the hook. Process-group isolation doesn’t protect against these, which is why the grace period must cover the whole drain.
- **The hook’s own timeout**: when a hook exceeds `--post-session-hook-timeout-sec`, the runner sends `SIGTERM` to the hook’s whole process group, then `SIGKILL` two seconds later, so a worker the hook forked, such as tar, rsync, or git, terminates with the wrapper shell instead of surviving as an orphan. The runner’s supervision ends once the hook’s stdio closes: a worker that redirected its own output to a file and outlives the `SIGTERM` stage is past the runner’s reach.

When the drain starts, and again on a forced exit, the runner logs how many `post-session` hooks are still running, so you can tell a quiet drain from one that’s mid-snapshot.


[​](#keep-the-base-directory-and-capacity-identical-across-runners)

Keep the base directory and capacity identical across runners

If a runner dies mid-session, the server requeues the session and another runner in the environment picks it up. That runner derives the checkout path from its own `--base-dir` and `--capacity`: `--capacity 1` checks out directly under `--base-dir`, and a `--capacity` above `1` uses per-session worktrees instead. When runners in the same environment use different values for either flag, the resumed session’s working directory changes, and absolute paths the agent recorded earlier, in edits, tool calls, or its own notes, point at a location that no longer exists. Use the same `--base-dir` and `--capacity` on every runner in an environment, and don’t use a per-host value such as an instance ID or hostname. Pass both flags explicitly, as the [Kubernetes](#kubernetes) and [Docker Compose](#docker-compose) recipes do, so that neither value depends on a default. The base directory defaults to `/workspace`, with the exception the [`--base-dir` reference row](self-hosted-environments-reference.md#runner-cli-flags) records. The runner needs write access to it. At startup, before registering, the runner creates the directory and confirms it can write to it, and exits with `cannot create or write to base directory` when it can’t. A runner started as root creates the default `/workspace` itself. For a non-root runner, create the directory and give the runner’s user ownership before starting the runner, or point `--base-dir` at a directory that user already owns.


[​](#reuse-a-pre-warmed-checkout)

Reuse a pre-warmed checkout

For large repositories, the clone can dominate session startup. To skip the cold clone, supply a clone yourself at the path where the runner keeps its own. With no [`checkout` hook](self-hosted-environments-configuration.md#checkout), the runner keeps one canonical clone per repository at `<base-dir>/<repo-owner>/<repo>` and reuses it across sessions:

- **At `--capacity 1`**: the runner fetches the requested ref, detaches `HEAD`, and resets hard to it, which is near-instant when little has changed.
- **At a `--capacity` above one**: the runner fetches into that clone, then checks out a separate worktree from it for each session. A pre-warmed clone saves the download but not the checkout.

Supply the clone in the image or on a persistent volume:

- **Clone in the image**: build the clone into your runner image at that path. Every fresh container then starts with the warm clone without reusing a disk.
- **Clone on a persistent volume**: on runners you pre-lock to one user’s account with [`--lock-to-account`](self-hosted-environments-reference.md#runner-cli-flags), point `--base-dir` at a persistent volume, so the disk only ever serves that account. A pre-locked runner never picks up Claude Tag channel sessions, so this option doesn’t apply to runners that serve them.

What the reuse path does and doesn’t guarantee:

- **Any clone shape works**: a full, shallow, or single-branch clone at the path is used as-is. The runner never passes `--depth` when fetching into an existing clone, so a full pre-warm keeps its full history and a shallow one stays shallow. `CLAUDE_RUNNER_FETCH_DEPTH` (`full`, `0`, or a number; default 50) controls only the cold clone the runner makes when no clone exists yet.
- **Tracked changes reset, untracked files persist**: at `--capacity 1`, each session starts from a hard reset that wipes the previous session’s tracked modifications, but the runner never runs `git clean`, so untracked files from the locked owner’s earlier sessions stay in the tree.
- **Per-session directories persist too**: alongside the checkout, the runner creates per-session entries under `<base-dir>/_sessions/` for every session it runs. The session’s Claude config directory holds a local copy of the conversation transcript. Next to it sit the session’s uploaded files, when the session has any. The session directory sits there too: it holds any per-session worktrees and `checkout` hook checkouts while the session runs, and it keeps whatever else Claude wrote in it. By default the runner leaves these in place when the session ends, so on a disk that outlives the runner process they accumulate. Every session runs as the runner’s own user, so any later session that disk serves can read them. If you keep a persistent `--base-dir`, size the volume for that growth. The same applies to any setup that restarts the runner on the same filesystem, including the [Docker Compose recipe](#docker-compose).
- **With `--remove-session-state`, per-session directories don’t persist**: start the runner with [`--remove-session-state`](self-hosted-environments-reference.md#runner-cli-flags) to have it delete each session’s per-session directories as the session ends. The deletion is best-effort: the directories stay when the runner is killed before its cleanup runs. The canonical clone and files a session wrote elsewhere on the host, such as the temporary directory, stay regardless.
- **With the git proxy, the reset becomes a checkout**: with [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), the runner sanitizes the clone’s `.git/` before each session. It keeps the object store and shallow state, and deletes the index. Without the index, each session starts with a full working-tree checkout instead of a near-instant reset. The runner still fetches into the existing clone rather than cloning the repository again. Submodule pre-warms aren’t supported under the proxy.
- **With the git proxy, a `reftable` pre-warm loses its refs**: when the runner sanitizes `.git/` before each session, it keeps `.git/refs/` and removes the `.git/reftable/` directory. A clone pre-warmed in git’s `reftable` ref format loses its refs that way, so pre-warm the clone in git’s `files` ref format instead.
- **Long clones need no workaround**: the runner bounds each git operation with a 120-second no-progress watchdog and a 30-minute hard cap, not a flat timeout, so a slow cold clone that keeps reporting progress completes.


[​](#pin-the-version)

Pin the version

Each session’s child process runs the runner’s own binary by default, and the runner turns off auto-update inside the sessions it spawns. With that default, every session runs the version you installed on the host or built into the image. A host-level update takes effect the next time the runner starts. Choose which version your sessions run and when it changes:

- **Before you pin a version**: check [the Claude Code versions that models require](../02-Claude-Code-CLI/model-config.md#available-models) for every model your sessions use. If a model requires a newer version than the one your sessions run, the server rejects requests for that model with [Claude Code does not support this model](../02-Claude-Code-CLI/errors.md#claude-code-does-not-support-this-model).
- **To hold a fleet on one version**: build the image with a pinned version, or on a bare host install a specific version and [disable auto-updates](../01-Getting-Started/setup.md#disable-auto-updates)
- **To upgrade a fixed fleet**: read the [changelog](../02-Claude-Code-CLI/changelog.md) entries between your version and the one you’re installing, then install the newer version or rebuild the image and restart the runners
- **To upgrade on-demand runners**: read the [changelog](../02-Claude-Code-CLI/changelog.md) entries between your version and the one you’re installing, then change the image your [`spawn-runner` hook](self-hosted-environments-configuration.md#the-spawn-runner-hook) starts. Each new runner gets the new version. A runner that’s already up, including a standby runner that [`--min-idle`](self-hosted-environments-reference.md#orchestrator-cli-flags) started, keeps its version until it exits. Don’t restart it, because its work order is single-use.
- **If sessions start a different binary**: if you use [`--exec-path`](self-hosted-environments-reference.md#runner-cli-flags) or a [`command` hook](self-hosted-environments-configuration.md#command) to start a Claude Code binary other than the runner’s own, sessions run that binary’s version. Update that binary when you update the runner.
- **Plugins**: plugin marketplaces don’t auto-update either; set `FORCE_AUTOUPDATE_PLUGINS=1` in the runner’s environment to let plugins auto-update while the binary stays pinned


[​](#scale-the-fleet)

Scale the fleet

Your orchestrator decides when to add or remove runners. Because of the [one-owner-per-runner lock](self-hosted-environments.md#runner-lifecycle), the minimum replica count is the number of users and Claude Tag agents you expect to be active concurrently; `--capacity` controls parallelism within one owner’s sessions, not across owners. Two scaling approaches are available:

- **Fixed fleet**: run a static set of runner replicas and scale on the [Prometheus metrics](self-hosted-environments-reference.md#prometheus-metrics) each runner serves
- **On-demand runners**: run the `claude self-hosted-runner orchestrator` subcommand, which polls Anthropic for sessions that are queued with no runner available and invokes your `spawn-runner` hook to boot one per session. See [On-demand runners](self-hosted-environments-configuration.md#on-demand-runners).


[​](#known-issues-and-limitations)

Known issues and limitations

The following are the limitations in this release, with workarounds where one exists.


[​](#connector-traffic-leaves-your-network)

Connector traffic leaves your network

Anthropic calls connector tools from its own infrastructure rather than from your runner. Connector tools are the claude.ai connectors, such as GitHub, Slack, and Linear. When Claude uses a connector in a self-hosted session, that traffic goes through `api.anthropic.com` rather than originating inside your network boundary. To keep a connector out of self-hosted sessions, filter it with the [`allowedMcpServers` and `deniedMcpServers` policy settings](../06-MCP-Tools/General/managed-mcp.md#policy-based-control-with-allowlists-and-denylists). Claude Code applies these settings to the connectors Anthropic delivers as well as to the servers you seed from the runner host and the servers users add, so if you deploy an allowlist for other servers, Claude Code blocks delivered connectors too. To keep connectors available alongside a URL-based allowlist, add entries that match the Anthropic proxy paths for delivered connectors:

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

If tool traffic must stay inside your network, run the equivalent tools as local MCP servers on the runner image instead. See [MCP servers](self-hosted-environments-configuration.md#mcp-servers).


[​](#some-sessions-don’t-count-as-idle)

Some sessions don’t count as idle

A session holding a background task that never finishes doesn’t count as idle, so `--release-idle-session-min` won’t release that session’s slot. A session that’s waiting on an approval requested from inside a running tool call also doesn’t count as idle. Always set `--kill-session-after-min` alongside it as a hard backstop so no session can hold a slot indefinitely. `--kill-session-after-min` is a backstop for runaway sessions. On a runner on v2.1.260 or later, a session that reaches the limit isn’t terminated outright. The runner gives it a grace window, 15 minutes by default, which you can change with [`SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS`](self-hosted-environments-reference.md#environment-variable-only-settings):

- If the session is waiting on its user, the runner releases it. If its turn has ended and it holds only background tasks, the runner waits up to 60 seconds for those tasks to finish and then releases it. The session resumes when its user sends their next message.
- If a turn is still running, the runner waits for the turn to finish, or for the session to next wait on its user, and then releases it.
- If the session is still on the runner when the grace window ends, the runner terminates it, and any running turn’s work is lost. A turn waiting on an approval requested from inside a running tool call is one way a session outlasts the window.

A released session resumes from a fresh clone, so work it hadn’t pushed is gone either way; see [Resumed sessions lose unpushed work](#additional-limitations). Before v2.1.260, the runner terminated every session at the limit, after waiting at most the grace window for a running turn to finish. Set the flag above your longest expected session, such as `--kill-session-after-min 480` for 8 hours. To free slots from conversations that go idle, use `--release-idle-session-min` instead.


[​](#additional-limitations)

Additional limitations

- **Resumed sessions lose unpushed work**: a fresh runner clones the repository again from its starting branch, so work the session hadn’t pushed is gone.
  - **To keep committed work**: set [`--push-outcome-on-release`](self-hosted-environments-reference.md#runner-cli-flags) on every runner in the environment, because a runner without the flag resumes the session from its starting branch. A runner with the flag makes a best-effort push of the session’s outcome branches before it releases, and the resumed session starts from those commits. The push uses the runner host’s own git credentials, including on a runner that uses [Anthropic-managed git](#use-the-anthropic-git-proxy). Uncommitted changes are still lost.
  - **With a `checkout` hook**: repositories checked out via a [`checkout` lifecycle hook](self-hosted-environments-configuration.md#checkout) aren’t pushed. Snapshot those from the [`post-session` hook](self-hosted-environments-configuration.md#post-session) instead.
  - **Before enabling the flag**: restrict who can push to `claude/*` refs on the source remote. On resume, the runner fetches the previously pushed branch without verifying who pushed it.
- **A repository added mid-session can fail to clone**: Claude clones it with `git clone` over HTTPS. On a runner without [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), the clone fails with a git authentication error if nothing on the host can read the repository. Where you can, select every repository the session needs when you create it.
- **Some connectors don’t appear in self-hosted sessions**: a connector you haven’t yet connected in claude.ai Settings isn’t listed in a self-hosted session, and the session won’t prompt you to connect it. Connect it in Settings first, then start a fresh session. Adding a connector to an already-running session also doesn’t make its tools available to Claude; start a fresh session to pick up a newly added connector.
- **When someone archives or deletes a session, or releases its runner from the session’s menu**: don’t count on the [`--push-outcome-on-release`](self-hosted-environments-reference.md#runner-cli-flags) push. To save commits in those cases, push `HEAD` to a rescue branch from a [`post-session` hook](self-hosted-environments-configuration.md#post-session). If you start from the example there, make it push even when nothing is uncommitted, because it pushes only after a snapshot commit.


[​](#report-an-issue)

Report an issue

For issues with self-hosted environments, contact your Anthropic account team.


[​](#troubleshooting)

Troubleshooting

For guided diagnosis, run the doctor subcommand on the runner host. The doctor subcommand starts an interactive Claude Code session with the runner’s logs and state attached. Sign in with `claude auth login` on that host first so the session can query your environment, its runners, and its queued sessions. Without that sign-in, for example when the host authenticates with an API key, it’s limited to the local health endpoint, metrics, and the runner’s log, and it reads the log only if you started the runner with `--log-file`.

```python
claude self-hosted-runner doctor
```

Common issues:

- **Runner doesn’t appear in the environment**: confirm the host can reach `api.anthropic.com` over HTTPS, the environment secret is current, and the host clock is within five minutes of real time; larger skew causes authentication to fail. The runner logs `[runner:fatal]` with the rejection reason on auth failure.
- **Runner exits at startup with `cannot create or write to base directory`**: the runner can’t create or write to `--base-dir`, which defaults to `/workspace`. Fix the directory’s ownership or point `--base-dir` at a writable path, as described in [Keep the base directory and capacity identical across runners](#keep-the-base-directory-and-capacity-identical-across-runners). If the runner instead logs `[runner:fatal]` saying the base directory check timed out, the directory is on a hung NFS or CSI mount. Check mount health rather than permissions. The runner prints both of these startup failures to stderr before it opens `--log-file`, so look for them in the terminal or your platform’s container logs rather than the log file. Before v2.1.225, the runner didn’t check the base directory at startup, and this misconfiguration failed sessions after pickup instead.
- **Runner exits at startup with `RegisterRunner failed:` and a TLS certificate error**: the error can be `self signed certificate in certificate chain`, for example. The runner doesn’t trust the certificate authority (CA) behind the certificate it received, as happens behind a TLS-inspecting proxy. Set [`NODE_EXTRA_CA_CERTS`](network-config.md#custom-ca-certificates) in the runner’s environment to a PEM file with the proxy’s root CA. You don’t set it again for sessions, which inherit it. Git doesn’t read it: for clones through that proxy, set [`GIT_SSL_CAINFO`](#trust-a-private-certificate-authority-with-anthropic-managed-git) too.
- **Your start script prints a write error containing `Resource temporarily unavailable` and exits**: your script shares its stdout or stderr with the `claude self-hosted-runner` or [orchestrator](self-hosted-environments-configuration.md#on-demand-runners) process it starts, which has switched that shared pipe or socket to non-blocking mode. When the pipe is full, a write from your script fails with this operating-system error instead of waiting, and under `set -e` the script exits. To stop the error, send the runner’s or orchestrator’s output to a file that your script’s user can create. Append `>> "$HOME/claude-runner.log" 2>&1` to the command that starts it, ahead of any `&` that backgrounds it:

  ``` shiki
  claude self-hosted-runner --environment-secret-file /etc/claude/environment-secret >> "$HOME/claude-runner.log" 2>&1
  ```

  Your script’s own output still goes where it went before, while what the runner writes to stdout and stderr, its startup errors included, goes to that file. Use this shell redirect rather than [`--log-file`](self-hosted-environments-reference.md#runner-cli-flags) alone, which mirrors the log to a file while the runner keeps writing to stdout and stderr. If you already pass `--log-file`, keep it as well, because the redirect doesn’t change what that file receives.
- **Orchestrator logs `poll failed:` with a TLS certificate error and keeps retrying**: the orchestrator doesn’t trust the CA behind the certificate it received, as happens behind a TLS-inspecting proxy. Set [`NODE_EXTRA_CA_CERTS`](network-config.md#custom-ca-certificates) in the orchestrator’s environment to a PEM file with the proxy’s root CA. Set it for the runners your `spawn-runner` hook starts too. For example, a line can end `poll failed: self signed certificate in certificate chain — retrying in 1s`, with the TLS library’s words between the colon and the dash. To confirm the fix, check that `connected` is `true` in the orchestrator’s [`/healthz`](self-hosted-environments-reference.md#health-endpoint) body.
- **Sessions stay queued**: every online runner may be locked to a different owner. Check each runner’s `claude_code_self_hosted_runner_locked_account` [metric](self-hosted-environments-reference.md#prometheus-metrics) or the `locked_account` field of its `[runner:health]` log line to see who holds it. Both show the owner’s email only after the runner has been issued a session token carrying an `act.email` claim, which a Claude Tag agent’s sessions never do. Without the claim, the runner emits no `locked_account` series and logs `locked_account=yes`, which tells you the runner is locked but not to which owner. Add replicas, or wait for an existing runner to drain and restart. If the environment uses on-demand runners, check the orchestrator instead; see [On-demand runners](self-hosted-environments-configuration.md#on-demand-runners).
- **Sessions fail immediately after pickup**: open the session in claude.ai/code to see the error. If the error shows that git couldn’t authenticate, check the runner’s [git credentials](#configure-git). On a runner started with `--use-anthropic-git-proxy`, see [When sessions fail to start on a runner with the git proxy](#when-anthropic-doesnt-serve-a-session). An unwritable base directory stops the runner at startup instead of failing sessions. See the **Runner exits at startup with `cannot create or write to base directory`** entry in this list.
- **Sessions fail to start on a runner that set `--use-anthropic-git-proxy`**: look in the runner’s log for `access denied by the git proxy`, or for a git error that names an `api.anthropic.com` address containing `/git_proxy/`. To tell whether Anthropic served the session and fix the cause, see [When sessions fail to start on a runner with the git proxy](#when-anthropic-doesnt-serve-a-session).
- **Sessions can’t reach the network through an authenticating egress proxy**: when the source you set with [`--proxy-authorization-command` or `--proxy-authorization-file`](#authenticate-to-an-egress-proxy) fails, times out after 30 seconds, or yields an empty value, the runner answers that connection `502 Bad Gateway` and logs why. The runner redacts the command’s stderr in that log and never logs the header value. With `--proxy-authorization-command`, run the command yourself on the host to confirm it prints the whole header value on stdout. If the runner instead exits at startup with `could not start the proxy-authorization listener`, it couldn’t open its loopback listener.
- **Runner logs `Poll failed` lines containing `rejecting the malformed poll response`**: the runner received a work-poll response whose body isn’t the queue’s expected JSON. One cause is an intercepting proxy or a captive portal answering with its own page. If the runner connects through a proxy, configure the proxy to pass responses from `api.anthropic.com` through unaltered. The runner rejects the malformed response, counts it under the `transport` kind of the `claude_code_self_hosted_runner_poll_errors_total` [metric](self-hosted-environments-reference.md#prometheus-metrics), and retries on the failed-poll schedule described in [Session lifecycle](self-hosted-environments.md#session-lifecycle). The runner keeps serving its live sessions. Before v2.1.246, the runner read such a response as an empty work queue, which could end its live sessions or make it exit.
- **A session fails with `no longer exists on the remote` or `couldn't find remote ref`**: the runner reads git’s `couldn't find remote ref` as a deleted branch. The branch can still exist. Look for it on your git host, under exactly the name in the message, then check each cause that fits:
  - **The branch is gone**: restore it, as the message asks.
  - **The branch exists and you don’t administer the runner**: send the runner’s administrator the full message and tell them the branch exists.
  - **The branch exists and the runner has a `checkout` hook**: a git command in your hook can print `couldn't find remote ref` to stderr before the hook exits non-zero. Find that command, and correct the ref it asks for or the remote it fetches from. The runner passes your hook the revision the session asked for as [`CLAUDE_RUNNER_REPO_REF`](self-hosted-environments-configuration.md#checkout).
  - **The branch exists and the runner has no `checkout` hook**: the runner fetches the branch itself. If a [`--git-host-rewrite`](#rewrite-git-urls-for-private-networks) applies to the repository, confirm that the host it rewrites to has the branch under the same name, and push the branch there if it doesn’t.
  - **The branch exists and the runner is older than v2.1.301**: upgrade the runner as described in [Pin the version](#pin-the-version), so that a different failure shows its own error. Before v2.1.301, other failures with `couldn't find remote ref` in their error output were also reported as a deleted branch.

  Once the runner is upgraded or the cause is fixed, retry the session or start a new one. If none of these checks finds the cause, [report an issue](#report-an-issue). For the source the session pushes results to, a deleted branch fails the session. For a git source the session only reads from, the runner skips that source and continues on the remaining ones. The runner fails the session when skipping would leave it with no repository at all. Before v2.1.228, such a session started in an empty directory.
- **A session starts without one of its repositories**: on a runner with no [`checkout` hook](self-hosted-environments-configuration.md#checkout), the git host can refuse the runner’s access check for a repository the session only reads from. The runner then skips that repository, logs a `[runner:warn] could not access context source` line naming the refusal, and starts the session on the remaining ones. The runner skips only a clear refusal: the host answers that the repository wasn’t found, git finds no credentials for the host, or authentication fails. A network failure, a timeout, or an HTTP `403` still fails the session start, as does a refusal for a repository the session pushes results to. The runner still fails a session that skipping would leave with no repository at all. With [`--use-anthropic-git-proxy`](#use-the-anthropic-git-proxy), the runner skips only a repository the git proxy itself denies. The access check runs again each time the session starts on a runner, so once the runner’s git identity has read access, the next start clones the repository. Before v2.1.274, each of these refusals failed the session start.
- **A session fails to start with `This runner couldn't read`**: the runner asked the git host for a repository with its [git credentials](#configure-git) and got a not-found answer. The repository doesn’t exist, or those credentials can’t reach the repository.
  - **If you administer the runner**: give its git credentials read access to the repository, then start a new session.
  - **If you started the session**: if the repository doesn’t exist, start a new session with the right repository. If the runner can’t reach the repository, ask the runner’s administrator to grant access, then start a new session.
- **Sessions take minutes to start**: the initial clone usually dominates. Watch the `claude_code_self_hosted_runner_session_init_duration_seconds` [metric](self-hosted-environments-reference.md#prometheus-metrics) to confirm, and cut the clone with a [pre-warmed checkout](#reuse-a-pre-warmed-checkout) or a smaller `CLAUDE_RUNNER_FETCH_DEPTH`.
- **Turns fail with a 401**: when a turn ends with a 401 or 403 from the Anthropic API, the runner fetches a fresh [`CLAUDE_CODE_OAUTH_TOKEN`](self-hosted-environments-configuration.md#wrapper-scripts) from Anthropic and passes it to the session. The failed turn isn’t retried. This token is short-lived, and the runner rotates it over the session’s stdin. When a fetch fails, the runner logs an `inference_token refresh failed` line that says when it will retry, and it keeps retrying for as long as the session runs. If every call starts failing about 30 minutes into a session, a wrapper script has likely severed the session’s stdin, so token rotations can’t reach it; see [Keep stdin and file descriptor 3 attached](self-hosted-environments-configuration.md#keep-stdin-and-file-descriptor-3-attached). Before v2.1.274, the runner stopped retrying a failed fetch after a few attempts and waited for the next scheduled one. A failed turn didn’t trigger a fetch, so every turn failed with a 401 until the next scheduled fetch.
- **A turn fails in claude.ai/code with `This model needs a newer version of Claude Code`**: the Claude Code version that your runners start sessions with is older than the session’s model requires, so the API [rejects the request](../02-Claude-Code-CLI/errors.md#claude-code-does-not-support-this-model). Upgrade the runners to the version the message asks for, or a later one, as described in [Pin the version](#pin-the-version). Until then, people can pick a different model in the session.
- **Pod is killed mid-drain**: raise `terminationGracePeriodSeconds` to at least the value the runner logs at startup. See [Shutdown timing](#shutdown-timing).

Once logging is initialized, the runner writes its lifecycle log, including `[runner:fatal]` lines, to stdout, and debug output to stderr, all as plain-text lines rather than JSON. The base directory failures described in the troubleshooting entries above print to stderr before that point. Capture both streams with `--log-file`, which also lets `self-hosted-runner doctor` tail them, or with your platform’s log collection. Each session’s child process writes a separate debug log. On failure the runner surfaces the log’s tail alongside the session in claude.ai/code. Unless you started the runner with [`--remove-session-state`](self-hosted-environments-reference.md#runner-cli-flags), it also keeps a failed session’s log on disk and prints its path in the runner log.


[​](#when-the-runner-exits)

When the runner exits

Don’t restart an [on-demand runner](self-hosted-environments-configuration.md#on-demand-runners), because its work order is single-use. A runner that exits right after it starts needs different handling from one that exits for any other reason.

- **A normal exit**: the runner finished its sessions and drained, reached its retire time, or was told to stop. Restart it so the environment has capacity again. [Runner lifecycle](self-hosted-environments.md#runner-lifecycle) describes these exits.
- **A failed start**: the runner can’t start with the configuration or host it was given, so it exits seconds after it starts, and it exits the same way every time you restart it. Restarting it faster doesn’t help. Someone needs to read its output and fix the cause.
- **Lost contact**: a runner that can’t reach Anthropic for longer than its [lease](self-hosted-environments.md#session-lifecycle), for example while its host sleeps, can be removed from the environment. When a removed runner reconnects, it exits. Its log can show a `[runner:fatal]` line that contains `runner record gone server-side` or, after a longer outage, [`poll auth failed`](self-hosted-environments-quickstart.md#set-up-an-environment-and-runner). The runner doesn’t register again by itself, so restart it.

Configure your supervisor to restart the runner whenever it exits, to wait longer between restarts when the runner keeps exiting right after it starts, and to tell someone when that keeps happening. For production, have your supervisor restart the runner on a fresh disk, in a new container or VM, as [Harden your deployment](#harden-your-deployment) recommends.


[​](#recognize-a-failed-start)

Recognize a failed start

When the runner can’t start, it prints a line that says why, and then it exits. For most causes the line contains `[runner:fatal]`. For some causes the line begins with `error:` instead, including when the runner can’t parse its flags, can’t read the environment secret, or can’t create or write to the base directory. The next line then points to `--help`. Most log lines start with a timestamp and `[self-hosted-runner]`, which the sample below leaves out. For example, a runner started with the Anthropic git proxy and a capacity above one prints a line like this one:

```python
[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.
```

Look for the line in the runner’s standard output and standard error, in your platform’s container logs, or in the file you set with [`--log-file`](self-hosted-environments-reference.md#runner-cli-flags). The runner prints an `error:` line before it opens the log file, so look for it in the terminal or your container logs, as [Troubleshooting](#troubleshooting) notes. These also help when you read a failed start:

- **No line at all**: a runner that the host kills prints neither. If the output ends with no `[runner:fatal]` line and no `error:` line, check whether the host or your orchestrator stopped the process, for example for exceeding a memory limit.
- **The exit code**: the runner doesn’t set aside an exit code for errors that repeat on every start. It exits with the same code for a configuration error, such as an unsupported combination of flags, and for a failure that can clear by itself, such as the API staying unreachable through the runner’s own retries. Base the decision to wait longer on how soon the runner exited, and read the runner’s output to learn why.
- **An environment that looks healthy**: some startup steps run after the runner registers with your environment, such as [`--configure-git`](#let-the-runner-configure-git) and the Anthropic git proxy’s credential setup. If one of those steps fails, the environment can go on listing that runner for a few minutes after the process has exited, and the **Cloud environments** page can read **Healthy** while no runner is picking up work. If sessions stay queued in an environment that looks healthy, check whether your supervisor is restarting the runner.


[​](#restart-with-a-wait-that-grows)

Restart with a wait that grows

How you get a growing wait depends on your supervisor.

- **Kubernetes**: the [Deployment](#kubernetes) on this page needs no change. After a container exits, the kubelet by default waits before it restarts the container, and the wait grows on each restart up to a ceiling. The wait starts over once the container has run for a while without exiting. The kubelet applies the same wait after a normal exit when the container ran only briefly. A runner that drains often can therefore show the `CrashLoopBackOff` status too, so read the output before you conclude that the runner can’t start. The command below reads the last run’s output from one pod of the Deployment:

  ``` shiki
  kubectl logs --previous -n claude-runners deploy/claude-runner
  ```

  When the last run was a failed start, the `[runner:fatal]` or `error:` line is among the last lines of the output. To read another pod’s last run, name that pod in place of `deploy/claude-runner`.
- **Docker and Docker Compose**: the [Compose recipe](#docker-compose) on this page needs no change. With `restart: always`, Docker waits longer before each restart of a container that keeps exiting, up to a ceiling. Replace `<container>` with the container’s name in the command below, which reads how many times Docker has restarted the container:

  ``` shiki
  docker inspect --format '{{.RestartCount}}' <container>
  ```

  The command prints a number. A number that keeps climbing means Docker keeps restarting the runner.
- **A systemd unit**: by default systemd waits the same `RestartSec` before every restart and doesn’t lengthen it, so a unit with `Restart=always` restarts a runner that can’t start at that same interval each time. When the starts come fast enough to reach the unit’s start rate limit, five starts in 10 seconds by default, systemd stops restarting the unit. The unit stays stopped until someone starts it again, which systemd allows once the rate limit’s interval has passed or after `systemctl reset-failed`. Because `RestartSec` applies to every restart, a longer value also delays the restart after a normal exit. Choose a value that balances the two, and alert on the unit’s restart count.
- **A shell loop or your own supervisor**: apply the same rule yourself. Start with a wait of five seconds. After each run that ended within a minute, double the wait for the next restart, up to five minutes. After a run that lasted a minute or more, go back to five seconds.


[​](#check-why-the-runner-keeps-exiting)

Check why the runner keeps exiting

When the runner has exited right after starting several times in a row, stop and check these before restarting it again.

- **The last `[runner:fatal]` or `error:` line**: it says why the runner stopped. [Troubleshooting](#troubleshooting) lists the common causes.
- **The combination of flags**: the [Anthropic git proxy](#use-the-anthropic-git-proxy) requires `--capacity 1`. The recipes on this page use a higher capacity, so lower it when you add the proxy to one of them.
- **What the service’s environment can reach**: if the runner starts by hand and fails under your supervisor, compare the user, the home directory, the `PATH`, and the memory limit. `--configure-git` and the Anthropic git proxy need git on the `PATH` and a writable `~/.gitconfig`.
- **The environment secret**: if you revoked the secret or mistyped it, the runner prints a line that contains `RegisterRunner auth failed`.
- **The environment’s Activity tab**: open the environment and select **Activity**. If new runners keep appearing there and none picks up work, your supervisor is restarting the runner.

For guided diagnosis on the runner host, run the [doctor subcommand](#troubleshooting).


[​](#what’s-next)

What’s next

- [Customize sessions](self-hosted-environments-configuration.md): wrapper scripts, lifecycle hooks, on-demand runners, MCP servers, and permissions
- [Test end to end](self-hosted-environments-testing.md): verify a new runner image from CI before promoting it
- [Reference](self-hosted-environments-reference.md): every CLI flag, environment variable, and metric

---

## Verify session identity in self-hosted environments - Claude Code Docs

Source: https://code.claude.com/docs/en/self-hosted-environments-identity
Path: 13-Enterprise-Admin/self-hosted-environments-identity.md

## On this page

- [The session token](#the-session-token)
  - [What the token proves](#what-the-token-proves)
  - [Token format](#token-format)
- [Verify the token](#verify-the-token)
  - [Verify the token from your service](#verify-the-token-from-your-service)
  - [Verify the token inside the session](#verify-the-token-inside-the-session)
- [Claims reference](#claims-reference)
  - [The act chain](#the-act-chain)
- [Scope derived credentials](#scope-derived-credentials)
- [Related environment variables](#related-environment-variables)
- [What’s next](#what%E2%80%99s-next)

Self-hosted environments

# Verify session identity in self-hosted environments

Copy pageCopy page

Verify the CLAUDE_CODE_SESSION_ACCESS_TOKEN JWT so services on your network can trust requests from sessions in your self-hosted environment.

Copy pageCopy page

Self-hosted environments are in public beta on Team and Enterprise plans; an [Owner](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) enables them by turning on **Allow self-hosted environments** on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments). This page covers session identity verification; see the [quickstart](self-hosted-environments-quickstart.md) for setup and [Deploy to production](self-hosted-environments-deploy.md) for the fleet recipes.

A [self-hosted environment](self-hosted-environments.md) lets Claude Code [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) run on infrastructure you operate instead of on Anthropic’s. Because the session runs inside your network, Claude can call your internal services directly. Those services need a way to confirm that a request came from a Claude Code session in your environment, and to identify the user or service identity that created that session. Every session in a self-hosted environment receives a signed JSON Web Token (JWT) in the `CLAUDE_CODE_SESSION_ACCESS_TOKEN` environment variable. A session presents the token like any bearer credential; for example, a script Claude runs can call your service with `curl -H "Authorization: Bearer $CLAUDE_CODE_SESSION_ACCESS_TOKEN"`. Anthropic signs the token and publishes the verification keys at a public JWKS endpoint. Your services fetch those keys, verify the signature, and read the claims to decide what access to grant.


[​](#the-session-token)

The session token

Before you write verification code, know what the token establishes and the shape your JWT library will see.


[​](#what-the-token-proves)

What the token proves

A valid token establishes some facts and deliberately not others:

- **Proves**: Anthropic issued the token for a specific session in a specific environment, and how the session was created: by a user in your organization, or by your organization’s service identity, which is how [Claude Tag channel sessions](https://claude.com/docs/claude-tag/concepts/agent-identity) start
- **Doesn’t prove**: which process on the runner host presents it. The token sits in an environment variable inside the session, so any code Claude runs, and any tool or MCP server the session starts, can read and present it.

Two consequences for your services:

- Verify the `aud` claim against your environment ID, the `ccpool_...` value shown with your environment on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments), to reject tokens issued to any other organization’s environment.
- Scope credentials you derive from the token to what a single coding session should be able to do, not to everything the session’s creator can do. See [Scope derived credentials](#scope-derived-credentials).


[​](#token-format)

Token format

The value of `CLAUDE_CODE_SESSION_ACCESS_TOKEN` has an `sk-ant-cc-` prefix followed by a standard three-part JWT:

```python
sk-ant-cc-<base64url header>.<base64url payload>.<base64url signature>
```

Strip the prefix before passing the value to a JWT library. Tokens issued to Anthropic-hosted cloud sessions carry an `sk-ant-si-` prefix instead and are signed by a different key set, so reject any value that doesn’t start with `sk-ant-cc-`. The signature algorithm is `ES256`, which is ECDSA on the P-256 curve with SHA-256. The token header carries a `kid` that identifies which key in the JWKS signed it.


[​](#verify-the-token)

Verify the token

Verification runs in one of two places. Services on your network verify the token cryptographically against Anthropic’s published keys, and wrapper scripts inside the session can use the runner binary’s built-in decoder instead.


[​](#verify-the-token-from-your-service)

Verify the token from your service

Anthropic publishes the verification keys at a public, unauthenticated endpoint:

```python
https://api.anthropic.com/v1/code/.well-known/jwks.json
```

The response is a standard [JSON Web Key Set](https://www.rfc-editor.org/rfc/rfc7517). Anthropic rotates the signing keys periodically, and keys from before a rotation remain in the set long enough that tokens they signed continue to verify, so don’t pin a single key. The endpoint sets `Cache-Control: public, max-age=300`, so caching the key set and refetching every five minutes is safe. Verify each incoming token against these checks:

1

Check the prefix

Reject the value if it doesn’t start with `sk-ant-cc-`, then remove that prefix. The remainder is a standard compact JWT.

2

Verify the signature

Fetch the JWKS, select the key whose `kid` matches the token header, and verify the `ES256` signature. Reject tokens whose `alg` header is not `ES256`. If a token arrives with a `kid` that isn’t in your cached key set, refetch the JWKS once before rejecting it: after a rotation, new tokens are signed with a key your cached set doesn’t have yet.

3

Verify the issuer

Reject the token if `iss` is not exactly `ccr`.

4

Verify the audience against your environment

The `aud` claim is an array. Reject the token unless it contains your environment ID, which has the form `ccpool_...`. The environment ID is shown in your environment’s detail dialog on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments), and appears as the `ccr:pool_id` claim in any of the environment’s session tokens. This check is what scopes the token to your environment and rejects tokens issued to other organizations.

5

Verify the role

Reject the token if `ccr:role` is not exactly `session_worker`. Other tokens issued for self-hosted environments, such as environment secrets, runner tokens, and work orders, are signed by the same key set but carry different roles.

6

Verify expiry

Reject the token if `exp` is in the past. Anthropic issues session tokens with a four-hour lifetime by default and a maximum of eight hours. The runner refreshes the token before expiry and pushes the new value to the session, so subprocesses that Claude starts after a refresh inherit it. One session can therefore present several distinct valid tokens to your service over its lifetime.

7

Read the identity

The creating user’s identity is in the `act` claim: `act.sub` is their Anthropic user ID in the prefixed form `user:<id>`, and `act.email`, when the creating surface recorded one, is their email address. Sessions your organization’s service identity creates, including Claude Tag channel sessions, carry an `agent:` subject instead, so treat a session as user-created only when `act.sub` carries the `user:` prefix, rather than testing whether identity claims are absent. See the [claims reference](#claims-reference) for the full structure and the flat duplicate claims.

The checks map directly onto standard JWT libraries. The examples below implement the full sequence in Node.js with [`jose`](https://www.npmjs.com/package/jose), which handles JWKS fetching, caching, and `kid` selection, and in Python with [`PyJWT`](https://pyjwt.readthedocs.io/) and its built-in JWKS client.

- Node.js (jose)

- Python (PyJWT)

```python
import { createRemoteJWKSet, jwtVerify } from "jose";

const JWKS = createRemoteJWKSet(
  new URL("https://api.anthropic.com/v1/code/.well-known/jwks.json")
);

const PREFIX = "sk-ant-cc-";
const EXPECTED_POOL_ID = "ccpool_...";

export async function verifySessionToken(raw: string) {
  if (!raw.startsWith(PREFIX)) {
    throw new Error("not a self-hosted runner session token");
  }
  const jwt = raw.slice(PREFIX.length);

  const { payload } = await jwtVerify(jwt, JWKS, {
    issuer: "ccr",
    audience: EXPECTED_POOL_ID,
    algorithms: ["ES256"],
  });

  if (payload["ccr:role"] !== "session_worker") {
    throw new Error("token is not a session_worker token");
  }

  const act = payload.act as { email?: string; sub?: string };
  return {
    sessionId: payload["ccr:session_id"] as string,
    poolId: payload["ccr:pool_id"] as string,
    orgId: payload["ccr:org_id"] as string,
    creatorEmail: act?.email,
    creatorSub: act?.sub,
  };
}
```

```python
import jwt
from jwt import PyJWKClient

JWKS_URL = "https://api.anthropic.com/v1/code/.well-known/jwks.json"
PREFIX = "sk-ant-cc-"
EXPECTED_POOL_ID = "ccpool_..."

jwks = PyJWKClient(JWKS_URL)


def verify_session_token(raw: str) -> dict:
    if not raw.startswith(PREFIX):
        raise ValueError("not a self-hosted runner session token")
    token = raw.removeprefix(PREFIX)

    signing_key = jwks.get_signing_key_from_jwt(token)
    payload = jwt.decode(
        token,
        signing_key.key,
        algorithms=["ES256"],
        issuer="ccr",
        audience=EXPECTED_POOL_ID,
    )

    if payload.get("ccr:role") != "session_worker":
        raise ValueError("token is not a session_worker token")

    act = payload.get("act") or {}
    return {
        "session_id": payload["ccr:session_id"],
        "pool_id": payload["ccr:pool_id"],
        "org_id": payload["ccr:org_id"],
        "creator_email": act.get("email"),
        "creator_sub": act.get("sub"),
    }
```


[​](#verify-the-token-inside-the-session)

Verify the token inside the session

[Wrapper scripts](self-hosted-environments-configuration.md#wrapper-scripts) run inside the session, before Claude starts. Instead of calling a JWT library, they can run the runner binary’s `self-hosted-runner decode-token` subcommand. The subcommand reads the token from a positional argument, from `CLAUDE_CODE_SESSION_ACCESS_TOKEN`, or from piped stdin, in that order, then strips the prefix, verifies the signature against the JWKS endpoint, checks expiry, and prints the claims as JSON. The subcommand performs the signature and expiry checks only; it doesn’t check `iss`, `aud`, or `ccr:role`. When your wrapper’s auth decision depends on those claims, read them from the printed JSON and compare them explicitly. This command extracts the creator identity, preferring the email address, then the creator’s `act.sub` subject, `user:<id>` or `agent:<id>`:

```python
"$CLAUDE_RUNNER_CLAUDE_BIN" self-hosted-runner decode-token | jq -re '.act.email // .act.sub'
```

Wrappers receive the absolute path to the runner’s own binary in `CLAUDE_RUNNER_CLAUDE_BIN`; use that path rather than a PATH-resolved `claude` so the decode runs on the same binary the runner itself uses. Use `jq -re` rather than `jq -r` so a missing claim causes a non-zero exit. With `-r` alone, a missing claim prints the literal string `null` and exits zero, which silently passes a bad value downstream. If `decode-token` can’t fetch the keys from the JWKS endpoint or can’t verify the token, it prints the reason to stderr, prints no claims, and exits with code 1. Pass `--no-verify` to `decode-token` only for offline inspection where the JWKS endpoint is unreachable.


[​](#claims-reference)

Claims reference

The table below lists the session token claims relevant to verification. Read identity from the `ccr:*` namespace and the `act` chain; the flat `account_email`, `organization_uuid`, and `account_uuid` claims are backward-compatibility duplicates that may be removed. Sessions your organization’s service identity creates, including Claude Tag channel sessions, carry an `agent:` subject in `act.sub` and omit `act.email`, `ccr:account_id`, `account_email`, and `account_uuid`. The two email claims are optional for user-created sessions too: Anthropic records them at session creation only when the creating request’s credentials carry an email, and a session dispatched from the CLI can lack both, so key identity on `act.sub` or `ccr:account_id` rather than on email. Tokens can also carry additional claims beyond this table; ignore claims you don’t recognize.

| Claim               | Type             | Description                                                                                                                                                                                                                                                                                                                                                                                                     |
|:--------------------|:-----------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `iss`               | string           | Always `ccr`.                                                                                                                                                                                                                                                                                                                                                                                                   |
| `sub`               | string           | `ccr:session:<session_id>`.                                                                                                                                                                                                                                                                                                                                                                                     |
| `aud`               | array of strings | Always contains `anthropic-api`. For sessions in self-hosted environments the array also contains your environment ID, such as `ccpool_...`. Verify the environment ID, not `anthropic-api`.                                                                                                                                                                                                                    |
| `exp`               | number           | Expiry as a Unix timestamp. Four-hour default lifetime, eight-hour maximum.                                                                                                                                                                                                                                                                                                                                     |
| `iat`               | number           | Issued-at as a Unix timestamp.                                                                                                                                                                                                                                                                                                                                                                                  |
| `jti`               | string           | Unique token identifier.                                                                                                                                                                                                                                                                                                                                                                                        |
| `ccr:role`          | string           | Always `session_worker` for session tokens.                                                                                                                                                                                                                                                                                                                                                                     |
| `ccr:session_id`    | string           | The session ID in `cse_...` form. Same value as the suffix of `sub`, and the same string as [`CLAUDE_CODE_REMOTE_SESSION_ID`](self-hosted-environments-configuration.md#wrapper-scripts). To match it to the `CLAUDE_RUNNER_SESSION_ID` that [lifecycle hooks](self-hosted-environments-configuration.md#lifecycle-hooks) receive, replace the `cse_` prefix with `session_`.                       |
| `ccr:pool_id`       | string           | Your environment ID. Same value that appears in `aud`.                                                                                                                                                                                                                                                                                                                                                          |
| `ccr:org_id`        | string           | Your Anthropic organization ID.                                                                                                                                                                                                                                                                                                                                                                                 |
| `ccr:account_id`    | string           | The creating user’s Anthropic account ID: the value of `act.sub` without the `user:` prefix, a tagged `user_...` ID. The same value the [spawn-runner hook](self-hosted-environments-configuration.md#the-spawn-runner-hook)’s `CLAUDE_RUNNER_ACCOUNT_ID` carries and [`--lock-to-account`](self-hosted-environments-reference.md#runner-cli-flags) accepts, so the three compare as equal strings. |
| `account_email`     | string           | Duplicate of `act.email`; absent whenever `act.email` is.                                                                                                                                                                                                                                                                                                                                                       |
| `organization_uuid` | string           | Your Anthropic organization UUID.                                                                                                                                                                                                                                                                                                                                                                               |
| `account_uuid`      | string           | The creating user’s Anthropic account UUID.                                                                                                                                                                                                                                                                                                                                                                     |
| `act`               | object           | [RFC 8693](https://www.rfc-editor.org/rfc/rfc8693) delegation chain. See [The `act` chain](#the-act-chain).                                                                                                                                                                                                                                                                                                     |


[​](#the-act-chain)

The `act` chain

The `act` claim records the full delegation path from the user or service identity that created the session down to the [environment](self-hosted-environments.md#key-concepts) whose secret admitted the runner, and the identity that created that secret. The creator is the outermost actor, so `act.sub` identifies them directly.

| Path              | Description                                                                                                                                                                                                    |
|:------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `act.sub`         | The creating user’s Anthropic user ID, in the form `user:<id>`, or `agent:<id>` when your organization’s service identity created the session, as it does for Claude Tag channel sessions.                     |
| `act.email`       | The creating user’s email address, when one was recorded at session creation. Don’t require it; key on `act.sub`.                                                                                              |
| `act.attested_by` | Reserved for the upstream identity provider’s attestation for the creating user. Expect it to be absent, and don’t depend on it. Key on `act.sub`. If you need an address, read `act.email` when it’s present. |
| `act.act`         | The runner that spawned the session. `act.act.sub` is `ccr:runner:<runner_id>`.                                                                                                                                |
| `act.act.act`     | The environment. `act.act.act.sub` is `ccr:pool:<pool_id>`.                                                                                                                                                    |
| `act.act.act.act` | The identity that created the environment secret the runner registered with. The chain ends here.                                                                                                              |


[​](#scope-derived-credentials)

Scope derived credentials

The session token identifies the user or service identity that created the session, but don’t treat it as equivalent to that creator logging in directly. The token sits in an environment variable inside the session, so any code Claude runs, and any tool or MCP server the session starts, can read and present it. Verification is also offline: a token that verifies against the JWKS stays valid until its `exp`, whatever has happened to the session since, and Anthropic doesn’t publish a revocation feed for session tokens. Bound anything you derive from the token accordingly. When your service exchanges the token for internal credentials, issue credentials scoped to what one coding session should reach:

- **Limit capabilities**: grant read and write access to the resources the session needs for coding tasks, not administrative capabilities the creator holds elsewhere.
- **Limit lifetime**: bound derived credentials to the token’s `exp`, or shorter.
- **Audit as the session**: record the `ccr:session_id` and `jti` alongside the creator identity so you can trace actions back to a specific session.


[​](#related-environment-variables)

Related environment variables

The creator identity also appears in plain environment variables on two surfaces that never verify the token:

- **The [`spawn-runner` hook](self-hosted-environments-configuration.md#the-spawn-runner-hook), on the orchestrator**: the hook runs before any runner exists for a queued session and receives the creator identity in variables such as `CLAUDE_RUNNER_ACCOUNT_EMAIL` and `CLAUDE_RUNNER_ACCOUNT_ID`. The orchestrator reads them from the work order, the signed single-use token that authorizes spawning one runner, without verifying the work order’s signature itself; the claims are trusted because the work order arrives over the orchestrator’s connection to Anthropic, which the environment secret authenticates.
- **[Wrapper scripts](self-hosted-environments-configuration.md#wrapper-scripts), inside the session**: wrappers receive `CCR_SESSION_ACCOUNT_EMAIL`, the creator’s email pre-extracted from the token without signature verification. The variable is suitable for labelling, such as commit trailers, not for auth decisions.

Use the plain variables for orchestrator-side decisions such as selecting a machine image. Use `CLAUDE_CODE_SESSION_ACCESS_TOKEN` when a downstream service needs independent cryptographic proof rather than trusting the runner’s environment.


[​](#what’s-next)

What’s next

- [Self-hosted environments](self-hosted-environments.md): the environment, runner, and session model; the [quickstart](self-hosted-environments-quickstart.md) and [Deploy to production](self-hosted-environments-deploy.md) hold setup and operations
- [Customize sessions](self-hosted-environments-configuration.md): wrapper scripts that consume the token, and the `spawn-runner` hook
- [Reference](self-hosted-environments-reference.md): CLI flags, environment variables, and metrics

---

## Self-hosted environments quickstart - Claude Code Docs

Source: https://code.claude.com/docs/en/self-hosted-environments-quickstart
Path: 13-Enterprise-Admin/self-hosted-environments-quickstart.md

## On this page

- [Prerequisites](#prerequisites)
  - [Organization and roles](#organization-and-roles)
  - [Host and network](#host-and-network)
  - [Software on the runner host](#software-on-the-runner-host)
- [Set up an environment and runner](#set-up-an-environment-and-runner)
  - [Run the guided setup](#run-the-guided-setup)
  - [Set up manually](#set-up-manually)
  - [If the runner exits](#if-the-runner-exits)
- [Send a follow-up message to a running session](#send-a-follow-up-message-to-a-running-session)
- [What’s next](#what%E2%80%99s-next)

Self-hosted environments

# Self-hosted environments quickstart

Copy pageCopy page

Set up your first self-hosted environment: install Claude Code, create the environment, start a runner, and route a session to it.

Copy pageCopy page

Self-hosted environments are in public beta on Team and Enterprise plans; [Availability and limitations](self-hosted-environments.md#availability-and-limitations) covers the enablement path. This page gets your first session running; see [Self-hosted environments](self-hosted-environments.md) for what they are and [Deploy to production](self-hosted-environments-deploy.md) for hardening and fleet recipes.

A [self-hosted environment](self-hosted-environments.md) runs Claude Code [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) on infrastructure your organization operates, executed by runner processes you deploy. This quickstart stands up your first one, the smallest that works: one runner on a single host, running one test session. There are two steps: [create the environment, start a runner, and route a session to it](#set-up-an-environment-and-runner), then [message that session from your terminal](#send-a-follow-up-message-to-a-running-session). You’ll move between two surfaces: claude.ai for creating the environment, checking its status, and routing a session, and a terminal on the host for everything the runner does. By the end you’ll have an environment on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments), a runner polling for work, and a session running on your host. Before you connect real repositories or internal systems, work through [Deploy to production](self-hosted-environments-deploy.md), which covers the security posture, egress control, git credentials, and orchestration.


[​](#prerequisites)

Prerequisites


[​](#organization-and-roles)

Organization and roles

The claude.ai side needs:

- **Allow self-hosted environments** turned on by an [Owner](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments); the **New** button doesn’t appear until it is. If you don’t hold the role, someone who does can create the environment and hand you its secret; the runner and terminal steps on this page need no claude.ai role, and where a step checks status in the admin UI, the runner’s own log lines give you the same signal.
- The [GitHub connector](https://claude.ai/admin-settings/connectors) turned on by an Owner, and each developer’s own [GitHub connection](../02-Claude-Code-CLI/web-quickstart.md#connect-github), so they can pick github.com repositories when they start sessions.


[​](#host-and-network)

Host and network

The runner host needs:

- A Linux or macOS host or container with outbound HTTPS to `api.anthropic.com`, to `claude.ai` and the download hosts it redirects to for the install step below, and to your git host for the clone; the [network requirements table](self-hosted-environments-deploy.md#network-requirements) has the full list. Windows isn’t supported as a runner host; run the runner in a Linux container instead. Developer workstations aren’t affected, since sessions start from claude.ai in a browser.
- A repository for the test session: a public one, or one this host can already clone by its HTTPS URL without being asked for credentials. An SSH key alone isn’t enough, because the runner clones the repository’s `https://` URL. To clone over SSH instead, see [Rewrite git URLs for private networks](self-hosted-environments-deploy.md#rewrite-git-urls-for-private-networks).
- A clock synchronized to real time, for example with NTP. Authentication fails when the clock is more than five minutes off; see [Troubleshooting](self-hosted-environments-deploy.md#troubleshooting).


[​](#software-on-the-runner-host)

Software on the runner host

Install on the host before you start:

- **Claude Code v2.1.224 or later**, with any of the [standard install methods](../01-Getting-Started/setup.md). The runner is part of the standard `claude` binary, and earlier versions don’t recognize the `self-hosted-runner` subcommand. To pin the exact version your fleet runs, see [Install a specific version](../01-Getting-Started/setup.md#install-a-specific-version). For container images, see the Dockerfile in [Deploy to production](self-hosted-environments-deploy.md#build-the-runner-image).
- **Git 2.24 or newer**. Some git options on the deploy page need newer versions; [Configure git](self-hosted-environments-deploy.md#configure-git) states each floor.

Confirm the host is ready:

```python
claude self-hosted-runner --help
```

A ready host prints the runner’s usage text, listing flags such as `--environment-secret-file`. On versions older than 2.1.224, the command prints the general `claude --help` output instead. If you see that output, [update Claude Code](../01-Getting-Started/setup.md#update-claude-code). If you run `claude` from a container image, [rebuild the image](self-hosted-environments-deploy.md#build-the-runner-image) with version 2.1.224 or later.


[​](#set-up-an-environment-and-runner)

Set up an environment and runner

Use either the [guided setup](#run-the-guided-setup) or the [manual steps](#set-up-manually). The guided setup is a single command that starts an interactive Claude Code session and walks you through the rest. Use the manual steps instead on a host where an interactive session isn’t possible. Also use them when someone who holds the Owner role created the environment and handed you its secret, since the guided setup needs an Owner sign-in.


[​](#run-the-guided-setup)

Run the guided setup

The guided setup walks you through creating the environment in the admin UI, starts a local runner with the secret file you save, confirms that the runner registers, and writes a cheat sheet to `./runner-setup/CHEAT-SHEET.md`. Before you run it, confirm your sign-in and version:

- **Sign-in**: run it on a machine where you’ve signed in with `claude auth login` using an account that holds an Owner role. With only an API key or a third-party model provider, the session starts but its organization checks fail.
- **Version**: confirm the [version check](#software-on-the-runner-host) passed. On versions older than 2.1.224, the setup command starts a Claude session with the words as the prompt instead of the guided setup.

To start the guided setup, run the setup subcommand in your shell and follow the prompts:

```python
claude self-hosted-runner setup
```

The setup doesn’t start a test session itself: it tells you to start one at claude.ai/code. The setup’s last step stops the runner it started. If you leave the setup before that step, the runner keeps running. To keep going after the last step, start the runner again in your shell with the command in `./runner-setup/CHEAT-SHEET.md`, then [route a session to the environment](#route-a-session).


[​](#set-up-manually)

Set up manually

Create the environment on claude.ai, start the runner from a terminal on the host, then return to claude.ai to confirm the runner appears and route a session to it. If someone who holds the Owner role already created the environment and handed you its secret, start at step 2.

1

Create an environment

Go to the [**Cloud environments** page](https://claude.ai/admin-settings/cloud-environments) in admin settings. Under **Self-hosted environments**, select **New**, name the environment, and select **Create**. On the wizard’s second step, select **Copy environment key** to copy the environment secret, which the admin UI labels an environment key. claude.ai shows the secret once, and you can’t retrieve it later; it expires 365 days after creation. The environment’s `ccpool_...` ID stays visible in its detail dialog; you’ll need it for the `aud` check in [token verification](self-hosted-environments-identity.md) and for dispatching [test sessions from CI](self-hosted-environments-testing.md#run-the-test-loop).If you lose the secret or need to rotate it, create a new secret from the environment’s **Configuration** tab, roll the new secret out to your runners, then revoke the old one. Runners holding a revoked secret fail their next authenticated poll and exit, logging `poll auth failed`, and your orchestrator restarts them with the new secret.

2

Start a runner

Create the secret directory. This command and the next use `/etc/claude`, which needs root, and the secret file they create is readable only by the user who runs them. If the runner will run as another user, it exits with `error: Failed to read environment secret file <path> (EACCES: permission denied, open '<path>')`. In that case, run both commands as the runner’s user with a directory that user can write to in place of `/etc/claude`, and pass the same path to `--environment-secret-file`. Any path the runner process can read works.

```python
mkdir -p /etc/claude
```

Write the environment secret to a file. The command below reads from your terminal so the secret stays out of shell history: paste the value you copied, press Enter, then Ctrl-D, and the subshell’s `umask` makes the file readable only by its owner.

```python
(umask 077 && cat > /etc/claude/environment-secret)
```

Choose a base directory, replacing `<writable-dir>` in the runner command below with an absolute path that the runner can write to or create. The runner creates the directory at startup, then checks repositories out and creates per-session directories under it. Without `--base-dir` it uses `/workspace`, which only works if that directory already exists and is writable or you start the runner as root.

Use a directory dedicated to the runner, not your home directory or a directory of your own clones. The runner treats whatever it finds at `<writable-dir>/<repo-owner>/<repo>` as its own clone, and can reset it and overwrite or delete files in it.

If the runner can’t create or write to the base directory, it exits at startup with an error naming the directory instead of registering. See [Troubleshooting](self-hosted-environments-deploy.md#troubleshooting).Then start the runner with `--environment-secret-file` and `--base-dir`:

```python
claude self-hosted-runner --environment-secret-file '/etc/claude/environment-secret' --base-dir '<writable-dir>'
```

The runner logs `Registered: runner_id=<runner-id>` once it has registered with your environment, then begins polling for work. Started with this command, it exits by design after serving one session, not after each turn. To serve more sessions, start the runner again each time it exits. See [If the runner exits](#if-the-runner-exits) for when that happens and what the runner logs.

3

Verify the runner appears

Return to the [**Cloud environments** page](https://claude.ai/admin-settings/cloud-environments). Your environment’s status changes from **No runners deployed** to **Healthy** within a few seconds of the runner starting; open the environment and select **Activity** to see the runner itself. If you don’t have access to the admin page, the `Registered: runner_id=<runner-id>` line in the runner’s log from the previous step gives you the same signal.

4

Route a session to the environment

Start a session at claude.ai/code and select your environment from the environment picker, where self-hosted environments appear alongside Anthropic-hosted ones. For the repository, pick the one from the [prerequisites](#host-and-network): a public repository, or one this host can already clone by its HTTPS URL without being asked for credentials.The next available runner picks up the queued session and logs `Picked up session <session-id>` along with its active count and capacity, so you can confirm from the runner’s own output which host took the session. Watch the session work and read Claude’s replies at [claude.ai/code](https://claude.ai/code).If the session doesn’t start working, match what you see:

- **The session sits queued**: see [Troubleshooting](self-hosted-environments-deploy.md#troubleshooting).
- **The session fails to start with a git error**: if the error includes git’s `could not read Username for` followed by your git host’s URL, the runner had no HTTPS credentials for that host.
  - **Supply HTTPS credentials**: see [Configure git](self-hosted-environments-deploy.md#configure-git)
  - **Clone over SSH instead**: if the runner host already has an SSH key, see [Rewrite git URLs for private networks](self-hosted-environments-deploy.md#rewrite-git-urls-for-private-networks)


[​](#if-the-runner-exits)

If the runner exits

If the runner exits during this quickstart, start it again. The runner can exit on its own:

- **Nothing left running**: the log shows `[runner:exit] account workload drained — exiting`. The runner [exits by design](self-hosted-environments.md#runner-lifecycle) once it’s no longer running any session, for example:
  - **You archived or deleted the session**: start the runner again
  - **The session failed to start**: [fix the cause](#session-doesnt-start), then start the runner again
- **Lost contact**: the log shows a `[runner:fatal]` line with `runner record gone server-side` or with `poll auth failed`. If the runner loses contact with Anthropic for a while, for example because the host sleeps, it can exit when it next reaches Anthropic.

A finished turn doesn’t end your test session. After the first turn the session is still attached and the runner is still up, so you can [send the session a follow-up message](#send-a-follow-up-message-to-a-running-session) without restarting the runner first. For production, deploy the runner under an orchestrator that restarts it on exit and waits longer between restarts when the runner keeps exiting right after it starts. See [Deploy to production](self-hosted-environments-deploy.md) and [When the runner exits](self-hosted-environments-deploy.md#when-the-runner-exits).


[​](#send-a-follow-up-message-to-a-running-session)

Send a follow-up message to a running session

Once a session is running on your environment, send it a follow-up from the `claude` CLI on any machine where you’re logged in with `claude auth login`; the command doesn’t need to run from the machine that started the session. The command posts one message:

```python
claude -p "your message" --cloud <session-id>
```

For `<session-id>`, pass the bare `session_...` or `cse_...` ID or the session’s claude.ai/code URL. A successful send prints `Sent to cloud session.` with the session ID and a view link. Accepted ID forms, JSON output, and the account and policy requirements are on [Send follow-ups from the CLI](../02-Claude-Code-CLI/claude-code-on-the-web.md#send-follow-ups-from-the-cli), since the command works the same against Anthropic-hosted sessions.


[​](#what’s-next)

What’s next

- [Deploy to production](self-hosted-environments-deploy.md): harden the deployment, control egress, configure git credentials, and run the fleet under Kubernetes or Compose
- [Customize sessions](self-hosted-environments-configuration.md): wrapper scripts, lifecycle hooks, on-demand runners, MCP servers, and permissions
- [Test end to end](self-hosted-environments-testing.md): a CI smoke test that dispatches a session and reads Claude’s replies

---

## Self-hosted environments reference - Claude Code Docs

Source: https://code.claude.com/docs/en/self-hosted-environments-reference
Path: 13-Enterprise-Admin/self-hosted-environments-reference.md

## On this page

- [Runner CLI flags](#runner-cli-flags)
  - [Auto mode rule lists](#auto-mode-rule-lists)
- [Orchestrator CLI flags](#orchestrator-cli-flags)
  - [SCM connector flags](#scm-connector-flags)
- [Environment-variable-only settings](#environment-variable-only-settings)
- [Telemetry](#telemetry)
- [Health endpoint](#health-endpoint)
- [Prometheus metrics](#prometheus-metrics)
  - [Pass through session-child metrics](#pass-through-session-child-metrics)
  - [Join a session’s metrics to the runner’s](#join-a-session%E2%80%99s-metrics-to-the-runner%E2%80%99s)
  - [Session lifecycle counter semantics](#session-lifecycle-counter-semantics)
- [What’s next](#what%E2%80%99s-next)

Self-hosted environments

# Self-hosted environments reference

Copy pageCopy page

Complete reference for the self-hosted runner and orchestrator: CLI flags, environment variables, and Prometheus metrics.

Copy pageCopy page

Self-hosted environments are in public beta on Team and Enterprise plans; an [Owner](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) enables them by turning on **Allow self-hosted environments** on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments). This page is the flag and metric reference; see the [quickstart](self-hosted-environments-quickstart.md) for setup and [Deploy to production](self-hosted-environments-deploy.md) for the fleet recipes.

This page is the reference for the two processes you run in a [self-hosted environment](self-hosted-environments.md): the runner, which executes Claude Code [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) on your hosts, and the optional autoscaling orchestrator, which starts runners as sessions queue. Each has its own flag table. Both run on Linux or macOS hosts, which the defaults such as `/workspace` and `~/.claude` assume. Run `claude self-hosted-runner --help` for the authoritative list on your installed version. Metric series and a few API fields still use `pool` for what these pages call an environment; both terms name the same thing. The environment ID is the `pool_id` field, with the form `ccpool_...`: wherever these pages show a `pool` identifier, it names the environment. CLI flags and environment variables spell it `environment`, such as `--environment-secret-file`; the deprecated `pool` spellings still work, as the [`--environment-secret-file` row](#runner-cli-flags) describes.


[​](#runner-cli-flags)

Runner CLI flags

Most flags have a corresponding environment variable. When both are set, the flag takes precedence. Duration flags take minutes or seconds on the CLI, but the paired environment variable is always in milliseconds, indicated by the `_MS` suffix, and the Default column shows the flag’s unit: `--exit-if-unused-min 10` is equivalent to `SELF_HOSTED_RUNNER_IDLE_SHUTDOWN_MS=600000`, and a Helm value like `SELF_HOSTED_RUNNER_STARTUP_TIMEOUT_MS: "15"` means 15 milliseconds, not the 15-minute default.

| Flag                                      | Env var                                           | Default                       | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
|:------------------------------------------|:--------------------------------------------------|:------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--api-url <url>`                         | none                                              | `https://api.anthropic.com`   | API base URL. Override only for testing.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `--base-dir <path>`                       | `SELF_HOSTED_RUNNER_BASE_DIR`                     | `/workspace`; none on Windows | Directory for repository checkouts and per-session working directories. The runner needs write access to this path or its parent. The runner creates the directory at startup and exits with `cannot create or write to base directory` when it can’t create or write to it. Before v2.1.225, the runner created the directory when the first session started, so an unusable path failed sessions rather than startup. On Windows, which isn’t a supported runner host, there is no default: the runner exits at startup unless you pass the flag or set the variable. Use the same value on every runner in an environment. See [Keep the base directory and capacity identical across runners](self-hosted-environments-deploy.md#keep-the-base-directory-and-capacity-identical-across-runners).                                                                                                                                                                                                                                                        |
| `--capacity <n>`                          | none                                              | `1`                           | Maximum concurrent sessions this runner handles. All sessions belong to the same locked [owner](self-hosted-environments.md#key-concepts). Use the same value on every runner in an environment; see [Keep the base directory and capacity identical across runners](self-hosted-environments-deploy.md#keep-the-base-directory-and-capacity-identical-across-runners).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `--client-label <label>`                  | `SELF_HOSTED_RUNNER_CLIENT_LABEL`                 | the host’s hostname           | Label the runner sends when it registers. The runner also reports it as the `client_label` label of [`claude_code_self_hosted_runner_info`](#prometheus-metrics). Requires Claude Code v2.1.248 or later.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| `--configure-git`                         | `SELF_HOSTED_RUNNER_CONFIGURE_GIT=1`              | off                           | At startup, write global git identity, enable Anthropic commit signing, turn on git push negotiation, and install commit hooks that append a `Co-authored-by:` trailer. Push negotiation requires Claude Code v2.1.257 or later. See [Configure git](self-hosted-environments-deploy.md#configure-git).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `--confine-repo-settings <mode>`          | `SELF_HOSTED_RUNNER_CONFINE_REPO_SETTINGS`        | `warn`                        | Sets the mode of the guard that flags a session when a repository’s committed settings try to grant write or read access outside that session’s own workspace, set environment variables, or override the operator’s sandbox or hooks posture, such as `sandbox.enabled: false` or `disableAllHooks`. The default `warn` logs the violation and still starts the session, `enforce` refuses the session, and `off` disables the scan. See [Harden your deployment](self-hosted-environments-deploy.md#harden-your-deployment).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--debug-token-dir <path>`                | `SELF_HOSTED_RUNNER_DEBUG_TOKEN_DIR`              | unset                         | Write live tokens to disk for inspection. Debug only; don’t use in production.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `--defer-shutdown-max-min <n>`            | `SELF_HOSTED_RUNNER_DEFER_SHUTDOWN_MAX_MS`        | `0`                           | On the first `SIGTERM` or `SIGINT`, keep serving the sessions already attached instead of draining them, then release whatever is still attached N minutes later and exit. Raise your host’s stop timeout before you set this. See [Defer the drain past the first signal](self-hosted-environments-deploy.md#defer-the-drain-past-the-first-signal). `0` disables. Requires Claude Code v2.1.238 or later.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `--drain-grace-sec <n>`                   | `SELF_HOSTED_RUNNER_DRAIN_GRACE_MS`               | `0`                           | Until the runner receives a shutdown signal or reaches its retire time, controls when the runner exits after its active sessions finish: `0` exits immediately without polling for more, and a positive value keeps the runner alive and re-polling the locked owner’s queue for that many seconds first, at the cost of the per-session container isolation described in the [hardening section](self-hosted-environments-deploy.md#harden-your-deployment). After a first signal you deferred with [`--defer-shutdown-max-min`](self-hosted-environments-deploy.md#defer-the-drain-past-the-first-signal), the runner exits as soon as it holds no sessions, whatever you set here.                                                                                                                                                                                                                                                                                                                                                                 |
| `--drain-marker-file <path>`              | `SELF_HOSTED_RUNNER_DRAIN_MARKER_FILE`            | unset                         | Marker file that your host writes to announce a graceful drain before sending `SIGTERM`. When the file exists as the drain starts, the runner reports its exit to Anthropic as a host drain rather than a plain shutdown signal. The drain itself, including the `--drain-wait-sec` hold, runs the same as without the flag. Name a path on a local filesystem that sessions can’t write to. Requires Claude Code v2.1.271 or later.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--drain-wait-sec <n>`                    | `SELF_HOSTED_RUNNER_DRAIN_WAIT_MS`                | `0`                           | Once the drain starts, which is on `SIGTERM` unless you set [`--defer-shutdown-max-min`](self-hosted-environments-deploy.md#defer-the-drain-past-the-first-signal), wait up to N seconds for each session’s in-flight turn and background tasks to finish before terminating the child. During this wait, the runner counts a background task that has just finished as still running until the follow-up turn that reads its result starts, for at most the [`SELF_HOSTED_RUNNER_BG_RESULT_GRACE_MS`](#environment-variable-only-settings) window. [Raise your stop timeout too](self-hosted-environments-deploy.md#let-running-turns-finish-before-a-restart).                                                                                                                                                                                                                                                                                                                                                                                      |
| `--environment-secret-file <path>`        | `SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET`           | required                      | Path to a file containing the environment secret, or, for runners spawned by the [orchestrator](self-hosted-environments-configuration.md#on-demand-runners), the single-use work-order JWT. `SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET` carries the secret value directly, not a file path. The older `--pool-secret-file` flag and `SELF_HOSTED_RUNNER_POOL_SECRET` variable still work and print a deprecation notice to stderr; preview-program runner builds older than 2.1.216 only recognize those older names.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `--exec-path <path>`                      | `SELF_HOSTED_RUNNER_EXEC_PATH`                    | own binary                    | Binary or wrapper script to spawn for each session. See [Wrapper scripts](self-hosted-environments-configuration.md#wrapper-scripts).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `--exit-if-unused-min <n>`                | `SELF_HOSTED_RUNNER_IDLE_SHUTDOWN_MS`             | `0`                           | Exit after N minutes of polling with no work ever assigned, for autoscaler scale-down. `0` disables.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--git-host-rewrite <from>=<to>`          | none                                              | unset                         | Rewrite `https://<from>/...` source URLs to `https://<to>/...` before cloning, for split-horizon DNS. Repeatable; flag only.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `--git-ssh-rewrite <host>`                | none                                              | unset                         | Rewrite `https://<host>/...` source URLs to `git@<host>:...` before cloning, for SSH-only git hosts. Repeatable; flag only.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| `--health-port <port>`                    | `SELF_HOSTED_RUNNER_HEALTH_PORT`                  | `8080`                        | Port for the `/healthz` and `/metrics` listener. Set `0` to disable.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--hooks-dir <path>`                      | `SELF_HOSTED_RUNNER_HOOKS_DIR`                    | unset                         | Directory of lifecycle hook scripts. See [Lifecycle hooks](self-hosted-environments-configuration.md#lifecycle-hooks).                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `--host-config-snapshot <mode>`           | `SELF_HOSTED_RUNNER_HOST_CONFIG_SNAPSHOT`         | `disk`                        | Where the runner keeps the startup snapshot of the [host config directory](#environment-variable-only-settings) that it seeds each session from. `disk` copies the snapshot into a runner-owned directory under `--base-dir` and, at each session start, verifies every file against an in-memory digest. If a file in the copy has been modified, the session fails and the runner refuses sessions until you restart it. `memory` holds the whole snapshot on the heap, capped at 64 MiB; over the cap, sessions start without host config and show a notice saying so. When the runner can’t write the disk snapshot, it logs the failure and uses `memory` for that run. Requires Claude Code v2.1.271 or later.                                                                                                                                                                                                                                                                                                                                              |
| `--kill-session-after-min <n>`            | `SELF_HOSTED_RUNNER_MAX_LIFETIME_MS`              | `0`                           | Limit a session to N minutes wall-clock, as a safety limit for stuck sessions. On v2.1.260 or later, the runner releases a session that reaches the limit so it can resume on its user’s next message, and terminates it only if it’s still on the runner when the [`SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS`](#environment-variable-only-settings) grace window ends. Before v2.1.260, the runner terminated the session at the limit. See [Some sessions don’t count as idle](self-hosted-environments-deploy.md#some-sessions-don%E2%80%99t-count-as-idle) for the details and how to choose a value. `0` disables.                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `--lock-to-account <id>`                  | `SELF_HOSTED_RUNNER_LOCK_TO_ACCOUNT`              | unset                         | Pre-lock the runner to a specific account at startup instead of locking on first session. Accepts an email address or `user_...` ID in the environment’s organization. A pre-locked runner never picks up Claude Tag channel sessions, which have no account.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| `--log-file <path>`                       | `SELF_HOSTED_RUNNER_LOG_FILE`                     | unset                         | Mirror runner logs to a file in addition to stdout and stderr, created with `0600` permissions. Required for `self-hosted-runner doctor` to tail logs locally.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| `--log-level <level>`                     | none                                              | `info`                        | `info` or `debug`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `--post-session-hook-timeout-sec <n>`     | `SELF_HOSTED_RUNNER_POST_SESSION_HOOK_TIMEOUT_MS` | `60`                          | Budget for the [`post-session` hook](self-hosted-environments-configuration.md#post-session) on every session end, including runner shutdown                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `--proxy-authorization-command <command>` | `SELF_HOSTED_RUNNER_PROXY_AUTHORIZATION_COMMAND`  | unset                         | Shell command the runner runs for every connection to your egress proxy, using its trimmed stdout as the `Proxy-Authorization` header value. Requires `HTTPS_PROXY` or `HTTP_PROXY`, and can’t be combined with `--proxy-authorization-file`. See [Authenticate to an egress proxy](self-hosted-environments-deploy.md#authenticate-to-an-egress-proxy). Requires Claude Code v2.1.238 or later.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `--proxy-authorization-file <path>`       | `SELF_HOSTED_RUNNER_PROXY_AUTHORIZATION_FILE`     | unset                         | File the runner reads for every connection to your egress proxy, using its trimmed contents as the `Proxy-Authorization` header value. Use this flag for a token another process rotates in place. Carries the same requirements as `--proxy-authorization-command`, and can’t be combined with it. See [Authenticate to an egress proxy](self-hosted-environments-deploy.md#authenticate-to-an-egress-proxy). Requires Claude Code v2.1.238 or later.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `--push-outcome-on-release`               | `SELF_HOSTED_RUNNER_PUSH_OUTCOME_ON_RELEASE`      | off                           | On a runner-initiated session end such as a drain or idle release, push tracked outcome branches to `origin` before deleting the workspace, so in-flight commits survive a restart. Best-effort; adds 30 seconds to the shutdown budget, and requires git 2.29 or newer to resume from the pushed branch. Restrict push access to `claude/*` refs before enabling; see [Resumed sessions lose unpushed work](self-hosted-environments-deploy.md#additional-limitations). Repositories checked out via a `checkout` lifecycle hook aren’t pushed; snapshot those from the [`post-session` hook](self-hosted-environments-configuration.md#post-session) instead.                                                                                                                                                                                                                                                                                                                                                                                       |
| `--release-idle-session-min <n>`          | `SELF_HOSTED_RUNNER_SESSION_IDLE_MS`              | `0`                           | Release a session slot after N minutes of inactivity once a turn finishes or the session waits for the user’s action. A session that’s still mid-turn, including one holding a never-finishing background task or an approval requested from inside a running tool call, doesn’t count as idle; pair with `--kill-session-after-min` as the hard backstop. After a session’s background task finishes, the runner considers the session busy until the follow-up turn that reads the result starts, for at most the [`SELF_HOSTED_RUNNER_BG_RESULT_GRACE_MS`](#environment-variable-only-settings) window. Until the runner receives a shutdown signal or reaches its retire time, a release that leaves the runner with no active sessions starts the same exit path as a normal drain, governed by `--drain-grace-sec`. After a first signal you deferred with [`--defer-shutdown-max-min`](self-hosted-environments-deploy.md#defer-the-drain-past-the-first-signal), the runner exits as soon as a release leaves it holding no sessions. `0` disables. |
| `--remove-session-state [bool]`           | `SELF_HOSTED_RUNNER_REMOVE_SESSION_STATE`         | off                           | Remove a session’s per-session directories under `<base-dir>/_sessions/` when the session ends on this runner, whatever the outcome. [Reuse a pre-warmed checkout](self-hosted-environments-deploy.md#reuse-a-pre-warmed-checkout) describes what they hold and who can read them when they stay. The removal is best-effort: the per-session directories stay in place when the runner is killed or reaches its drain deadline before cleanup runs. With the flag on, a failed or interrupted session’s debug log isn’t kept on disk. Requires Claude Code v2.1.268 or later.                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| `--retire-at <epoch-seconds>`             | `SELF_HOSTED_RUNNER_RETIRE_AT`                    | unset                         | Retire the runner at an absolute Unix timestamp in seconds, for infrastructure that kills the runner at a known time; [Runner lifecycle](self-hosted-environments.md#runner-lifecycle) describes the release sequence and how to size the margin. Values before 2001 or after the year 5138 are rejected by the flag and ignored by the environment variable.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `--server-auto-mode-lists <mode>`         | `SELF_HOSTED_RUNNER_SERVER_AUTO_MODE_LISTS`       | `no-allow`                    | Which of the [auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) classifier rule lists that the control plane sends with a session may reach that session: `all`, `no-allow`, or `none`. See [Auto mode rule lists](#auto-mode-rule-lists) for what each value applies. An invalid value stops the runner at startup. Requires Claude Code v2.1.295 or later.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| `--session-stop-grace-sec <n>`            | `SELF_HOSTED_RUNNER_SESSION_STOP_GRACE_MS`        | `5`                           | How long to wait for the Claude process to exit cleanly after a session ends, before force-killing it. Raise the value if the child’s own `SessionEnd` hooks need more time.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `--startup-timeout-min <n>`               | `SELF_HOSTED_RUNNER_STARTUP_TIMEOUT_MS`           | `15`                          | Release a session slot if the child hasn’t signaled on the [activity channel](self-hosted-environments-configuration.md#keep-stdin-and-file-descriptor-3-attached) that it initialized within N minutes. The N minutes start at spawn, after cloning, and start over at each `user` or `assistant` message the child emits before it has initialized or reported its state. Before v2.1.299, this timeout stopped applying at the session’s first such message. For release after the init signal, see `--release-idle-session-min`. `0` disables.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| `--trust-workspace [bool]`                | `SELF_HOSTED_RUNNER_TRUST_WORKSPACE`              | on                            | Seed persisted trust for each session’s repository paths so repo-committed `permissions.allow` and `additionalDirectories` are honored. Set `false` to drop repo-committed permission grants and configure allow rules in the host config’s `settings.json` instead; repository-committed `sandbox.*` settings still apply either way, which is why the [repo-settings guard](self-hosted-environments-deploy.md#harden-your-deployment) scans them regardless of this flag.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |
| `--use-anthropic-git-proxy`               | `CLAUDE_RUNNER_USE_GIT_PROXY=1`                   | off                           | Clone repositories on github.com via the [Anthropic git proxy](self-hosted-environments-deploy.md#use-the-anthropic-git-proxy) instead of customer-managed git auth. Requires `--capacity 1` and git 2.32 or later; the runner refuses to start otherwise. Supersedes the rewrite flags.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |

Most duration flags have a maximum, chosen to keep each timeout inside the runtime’s 32-bit timer ceiling of roughly 24.85 days. The `--*-min` flags cap at 10080 minutes, 7 days; `--drain-grace-sec` at 604800 seconds, also 7 days; and `--drain-wait-sec` at 86400 seconds, 24 hours. `--session-stop-grace-sec` and `--post-session-hook-timeout-sec` are uncapped. Overrunning a cap behaves differently per surface:

- **Flag**: startup fails with an error.
- **Environment variable**: the runner clamps the value to the timer ceiling rather than rejecting it.


[​](#auto-mode-rule-lists)

Auto mode rule lists

`--server-auto-mode-lists` lets you decide which [auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) classifier rules from outside the runner reach the sessions on your runners. Anthropic’s control plane can send rule lists with a session and ask the runner to apply them. Some entries may be rules an admin of your organization wrote. The lists are `environment`, `soft_deny`, and `allow`:

- **`environment`**: an entry can make the classifier allow more as well as less.
- **`soft_deny`**: an entry blocks an action unless the user explicitly asked for it or an `allow` exception applies.
- **`allow`**: the exceptions to `soft_deny` entries.

The flag’s value picks which lists the runner applies:

- **`no-allow`**: the default. Applies `environment` and `soft_deny` and withholds `allow`. An `environment` entry can still make the classifier allow more, so the default doesn’t rule out every loosening.
- **`all`**: applies all three lists.
- **`none`**: applies none of them. Pick `none` to rule out every loosening from these lists. It also drops the `soft_deny` restrictions.

No runner setting makes the control plane ask the runner to apply the lists. When it doesn’t ask, sessions receive no list whatever you set. To see which happened, start the runner with `--log-level debug`. For each session the runner then logs a line containing `the server asked this runner to apply`, or one containing `the server did not ask this runner to apply the auto mode lists it sends`.


[​](#orchestrator-cli-flags)

Orchestrator CLI flags

The `self-hosted-runner orchestrator` subcommand, which spawns [on-demand runners](self-hosted-environments-configuration.md#on-demand-runners), accepts `--api-url`, `--environment-secret-file`, `--hooks-dir`, `--health-port`, and `--log-level` with the same defaults as the runner and, where the runner’s flag has one, the same environment variable, except that `--hooks-dir` is required and must contain a `spawn-runner` hook. It also takes its own flags:

| Flag                             | Default | Description                                                                                                                                                                                                                                                                                                                                                                  |
|:---------------------------------|:--------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `--hook-concurrency <n>`         | `4`     | Maximum `spawn-runner` hooks running in parallel. Also caps how many spawn requests are claimed per poll.                                                                                                                                                                                                                                                                    |
| `--hook-timeout <sec>`           | `60`    | Terminate the hook’s process tree after this many seconds. The timeout plus its 5-second kill grace must stay below `--expected-spawn-seconds`; the orchestrator enforces this at startup.                                                                                                                                                                                   |
| `--expected-spawn-seconds <sec>` | `120`   | Expected p99 time from when the orchestrator receives a spawn request to when the runner registers, including any wait for capacity on your platform. The server enforces a range of 10 to 3600. Sent on every poll as the server-side lease: if no runner registers before it elapses, the session is re-offered with a fresh order ID. All replicas must share this value. |
| `--min-idle <n>`                 | `0`     | Keep at least N idle session slots free by spawning standby runners proactively. `0` disables pre-warming. Pair with the runner’s `--exit-if-unused-min` so surplus standby runners reclaim themselves.                                                                                                                                                                      |
| `--debug-dir <path>`             | unset   | Write each spawn request’s work order and hook stderr to disk. Debug only; never set in production.                                                                                                                                                                                                                                                                          |


[​](#scm-connector-flags)

SCM connector flags

The SCM connector isn’t available, so leave the flags in this section unset. If you set `--scm-connector-host`, the connection doesn’t open, and the orchestrator keeps retrying it. Runners still start as sessions queue. The connector is a standing WebSocket connection from the orchestrator to Anthropic’s control plane. It’s designed to let hosted pre-session flows, such as the repository picker and the branch or ref resolver, reach a GitHub Enterprise Server host that’s only routable from inside your network. See [Network requirements](../02-Claude-Code-CLI/github-enterprise-server.md#network-requirements) on the GitHub Enterprise Server page for what those flows need.

| Flag                                                    | Default                              | Description                                                                                                                    |
|:--------------------------------------------------------|:-------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------|
| `--scm-connector-host <host[:port]>`                    | unset                                | GitHub Enterprise Server hostname to forward requests to. Port defaults to `443`.                                              |
| `--scm-connector-id <n>`                                | required with `--scm-connector-host` | The numeric ID of your organization’s GitHub Enterprise Server connection.                                                     |
| `--scm-connector-provider <slug>`                       | `ghe`                                | Path segment identifying the provider, matching `^[a-z0-9-]{1,32}$`.                                                           |
| `--scm-connector-ca-file <path>`                        | unset                                | Extra CA bundle, in PEM format, for TLS connections to the GitHub Enterprise Server host.                                      |
| `--scm-connector-host-rewrite <from>=<to_host:to_port>` | unset                                | For end-to-end testing only: redirects the TCP connection while keeping the Host header and TLS SNI as `--scm-connector-host`. |

On each connection attempt the orchestrator sends its existing environment secret, and it retries automatically with exponential backoff, capped at 30 seconds plus jitter.


[​](#environment-variable-only-settings)

Environment-variable-only settings

These runner settings are read from the environment only and cover behavior most deployments leave at the default:

| Env var                                      | Default     | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
|:---------------------------------------------|:------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `SELF_HOSTED_RUNNER_BG_RESULT_GRACE_MS`      | `30000`     | How long the runner considers a session busy after a background task finishes while the follow-up turn that reads the result hasn’t started. The [`--drain-wait-sec` and `--release-idle-session-min` rows](#runner-cli-flags) describe where the hold applies on drain and idle release, and [Runner lifecycle](self-hosted-environments.md#runner-lifecycle) describes where it applies on `--retire-at` retirement. `0` or an unusable value falls back to the default, so the hold can’t be turned off. Requires Claude Code v2.1.228 or later. |
| `SELF_HOSTED_RUNNER_HOST_CONFIG_DIR`         | `~/.claude` | Directory captured into the runner’s startup snapshot and seeded into each session’s `CLAUDE_CONFIG_DIR`; changes on disk apply after a runner restart. Setting the variable also moves where the runner reads `.claude.json` for [MCP seeding](self-hosted-environments-configuration.md#mcp-servers), so setting it, including to its own default, relocates that lookup; point at an empty directory to disable seeding entirely.                                                                                                                |
| `SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS`   | `900000`    | How long the runner waits after a session reaches its `--kill-session-after-min` limit, for a running turn to finish or the release to complete, before it terminates the session                                                                                                                                                                                                                                                                                                                                                                         |
| `SELF_HOSTED_RUNNER_POST_TURN_SETTLE_MS`     | `7000`      | Cap on how long the runner counts a session as busy for the `--drain-wait-sec` drain after a turn finishes, while the session’s process reports the turn’s end to Anthropic. `0` or an unusable value falls back to the default, so the hold can’t be turned off. Requires Claude Code v2.1.275 or later.                                                                                                                                                                                                                                                 |
| `SELF_HOSTED_RUNNER_SIGKILL_GRACE_MS`        | `30000`     | How long the runner waits for the OS to deliver `SIGKILL` to a child stuck in uninterruptible I/O before exiting itself. Floored at `--post-session-hook-timeout-sec` plus 15 seconds, and 30 more when `--push-outcome-on-release` is set, so the effective minimum is 75 seconds at defaults.                                                                                                                                                                                                                                                           |
| `CLAUDE_RUNNER_FETCH_DEPTH`                  | `50`        | Git fetch depth for fresh clones. Set a positive integer, or `full` or `0` for a complete fetch. Repositories already present in the workspace keep their existing depth.                                                                                                                                                                                                                                                                                                                                                                                 |
| `CLAUDE_RUNNER_FETCH_SERVER_PROGRESS_CAP_MS` | `600000`    | How long in milliseconds, per attempt, a git fetch may wait for its first data while the git server’s own progress numbers keep rising, as when the server prepares the pack for a large repository. `0` or `off` turns the wait off: such a fetch is then cut off after two minutes without data. Any other whole number is clamped to between `120000` and `1800000`, 2 to 30 minutes. Requires Claude Code v2.1.295 or later.                                                                                                                          |
| `CLAUDE_RUNNER_SKIP_GIT_VERIFY`              | unset       | When `1`, skip the `.git` presence check after a `checkout` hook runs. Set this when your hook materializes a non-git source.                                                                                                                                                                                                                                                                                                                                                                                                                             |
| `FORCE_AUTOUPDATE_PLUGINS`                   | unset       | When `1`, let plugin marketplaces auto-update even though the binary is pinned                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| `CLAUDE_CODE_DISABLE_ARTIFACT`               | unset       | When `1`, disable the Artifact tool in sessions regardless of the organization’s admin setting, and drop the `*.frame.claudeusercontent.com` egress requirement                                                                                                                                                                                                                                                                                                                                                                                           |


[​](#telemetry)

Telemetry

Session children send operational telemetry to Anthropic unless you turn it off. No code or repository content is sent. Set telemetry variables on the runner process; the runner re-asserts them after applying server-provided environment variables, so the operator’s setting always takes precedence. One control is specific to self-hosted environments: `CLAUDE_CODE_BYOC_ENABLE_DATADOG=1` opts in to Datadog operational metrics, which are off by default in self-hosted environments. The general Claude Code telemetry controls, `DISABLE_TELEMETRY`, `DO_NOT_TRACK`, `DISABLE_ERROR_REPORTING`, and `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`, apply to session children as documented in the [environment variable reference](../02-Claude-Code-CLI/env-vars.md). `DISABLE_GROWTHBOOK` is related but different: setting `DISABLE_GROWTHBOOK=1` disables feature-flag fetching, and telemetry stays on unless `DISABLE_TELEMETRY` is also set. `CLAUDE_CODE_ENABLE_TELEMETRY` is unrelated: it enables OpenTelemetry export to your own collector, as described in [Monitoring](monitoring-usage.md), and doesn’t control Anthropic’s analytics.


[​](#health-endpoint)

Health endpoint

The runner serves `GET /healthz` on the configured health port. The response is `200 OK` whenever the process is alive, whatever state the poll loop is in, so an HTTP probe on this endpoint detects a dead process only. The JSON body describes current state:

```python
{
  "status": "ok",
  "runner_id": "ccrunner_...",
  "active_sessions": 2,
  "last_poll_at": "2026-03-31T18:04:11.220Z",
  "last_poll_age_ms": 842
}
```

Use `last_poll_age_ms` as a liveness signal in custom probes; a value that grows unbounded indicates the poll loop is stuck. Both `last_poll_at` and `last_poll_age_ms` are `null` until the first poll completes. The orchestrator’s own `/healthz`, on its health port, always returns `200`. The body carries a `connected` field reporting whether the most recent poll succeeded, plus per-state spawn-queue counts in `queue_counts`. To see why `connected` is `false`, read the top-level `last_error` field, which each successful poll resets to `null`. Gate readiness and alerting on `connected` rather than the status code. When the [SCM connector](#scm-connector-flags) is configured, the orchestrator’s `/healthz` body also carries `scm_connector_connected` and a `scm_connector` object with `connected`, `last_connected_at`, `last_error`, `reconnects`, and `requests_forwarded`. Both fields are `null` when `--scm-connector-host` isn’t set.


[​](#prometheus-metrics)

Prometheus metrics

Each runner serves Prometheus metrics at `GET /metrics` on the same port as `/healthz`. Key series:

| Series                                                                            | Notes                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
|:----------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `claude_code_self_hosted_runner_info{runner_id,version,client_label}`             | Always `1`; useful for fleet inventory and version-drift detection                                                                                                                                                                                                                                                                                                                                                                                                       |
| `claude_code_self_hosted_runner_capacity`                                         | Configured `--capacity`                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| `claude_code_self_hosted_runner_active_sessions`                                  | Sessions currently running                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| `claude_code_self_hosted_runner_locked_account{email}`                            | Present once the runner has locked to a user and a session token carrying an `act.email` claim has been issued. The series is absent on a runner locked to a Claude Tag agent, whose session tokens carry no `act.email`. The label value is the account email; if your metrics store is broadly readable, drop or hash the label at scrape time, for example with Prometheus `metric_relabel_configs`.                                                                  |
| `claude_code_self_hosted_runner_last_poll_age_seconds`                            | Seconds since the last successful poll. Alert if over 60.                                                                                                                                                                                                                                                                                                                                                                                                                |
| `claude_code_self_hosted_runner_poll_errors_total{error_kind}`                    | Cumulative PollWork failures by kind: `transport`, `timeout`, `5xx`, `429`, or `4xx`. All five series are present from process start; alert on `rate(...[5m]) > 0`.                                                                                                                                                                                                                                                                                                      |
| `claude_code_self_hosted_runner_sessions_started_total{client_platform}`          | Session child processes spawned over the runner’s lifetime, one series per session origin such as `web_claude_ai`, `ios`, `android`, `desktop_app`, or `claude_code_cli`, or `unknown` when the server didn’t send one. Slack sessions carry either `claude_in_slack` or `claude-in-slack` depending on which Slack integration created them, so match both with a regex selector such as `{client_platform=~"claude[-_]in[-_]slack"}`. Use `sum()` for the fleet total. |
| `claude_code_self_hosted_runner_sessions_completed_total{client_platform}`        | Sessions that ended cleanly, labeled the same way. Broader than a plain clean exit: see [session lifecycle counter semantics](#session-lifecycle-counter-semantics) for what counts.                                                                                                                                                                                                                                                                                     |
| `claude_code_self_hosted_runner_sessions_failed_total{client_platform}`           | Sessions that ended in failure, labeled the same way. Same caveat: see [session lifecycle counter semantics](#session-lifecycle-counter-semantics).                                                                                                                                                                                                                                                                                                                      |
| `claude_code_self_hosted_runner_sessions_interrupted_total{client_platform}`      | Sessions the runner terminated for an operational reason rather than a session outcome, labeled the same way. See [session lifecycle counter semantics](#session-lifecycle-counter-semantics).                                                                                                                                                                                                                                                                           |
| `claude_code_self_hosted_runner_initializing_sessions`                            | Sessions currently in the init phase, from assignment until the child’s init event                                                                                                                                                                                                                                                                                                                                                                                       |
| `claude_code_self_hosted_runner_session_init_duration_seconds`                    | Histogram of session init durations                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| `claude_code_self_hosted_runner_session_init_errors_total`                        | Sessions that failed before reaching init: a checkout hook failure, git prep, token issue, or a pre-init child crash                                                                                                                                                                                                                                                                                                                                                     |
| `claude_code_self_hosted_runner_session_start_hook_errors_total`                  | `SessionStart` hooks that reported an error outcome, one per failing hook execution                                                                                                                                                                                                                                                                                                                                                                                      |
| `claude_code_self_hosted_runner_session_idle_seconds{session_id,client_platform}` | Per-session gauge of seconds since the session went idle. Useful for terminating sessions stuck on an unanswered permission prompt.                                                                                                                                                                                                                                                                                                                                      |

The orchestrator serves its own series at `GET /metrics` on the same port as its `/healthz`:

| Series                                                                                  | Notes                                                                                                                                                                                                                                                                                                                                                  |
|:----------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `claude_code_self_hosted_orchestrator_info{version,pool_id,orchestrator_uuid,hostname}` | Always `1`                                                                                                                                                                                                                                                                                                                                             |
| `claude_code_self_hosted_orchestrator_connected`                                        | `1` when the most recent poll succeeded; drops to `0` after any failed poll, whatever the failure kind                                                                                                                                                                                                                                                 |
| `claude_code_self_hosted_orchestrator_last_poll_age_seconds`                            | Seconds since the last poll attempt, success or failure, unlike the runner’s identically-named metric, which measures since the last success; pair with `connected` to catch failing polls. The orchestrator’s poll loop waits on hook execution, so alert above `--hook-timeout` plus a margin, around 90 seconds at defaults, rather than a flat 60. |
| `claude_code_self_hosted_orchestrator_poll_errors_total{error_kind}`                    | Cumulative PollSpawnHints failures by kind: `transport`, `timeout`, `5xx`, `429`, or `4xx`. All five series are present from process start; alert on `rate(...[5m]) > 0`.                                                                                                                                                                              |
| `claude_code_self_hosted_orchestrator_queue_pending_sessions`                           | Spawn requests claimable right now                                                                                                                                                                                                                                                                                                                     |
| `claude_code_self_hosted_orchestrator_queue_backing_off_sessions`                       | Spawn requests in retry backoff after a retryable hook failure                                                                                                                                                                                                                                                                                         |
| `claude_code_self_hosted_orchestrator_queue_circuit_broken_sessions`                    | Sessions blocked from spawning. Each stays blocked until a user sends it a new message or an Owner retries it from the environment’s **Activity** tab. The count can stay above zero after you fix the cause. Alert if above zero.                                                                                                                     |
| `claude_code_self_hosted_orchestrator_pool_pending_sessions`                            | Total sessions waiting on a runner for this environment. Environment-wide aggregate, identical on every orchestrator instance: use `MAX` rather than `SUM` across instances.                                                                                                                                                                           |
| `claude_code_self_hosted_orchestrator_pool_active_sessions`                             | Sessions currently assigned to an alive runner in this environment. Environment-wide aggregate, identical on every orchestrator instance: use `MAX` rather than `SUM` across instances.                                                                                                                                                                |
| `claude_code_self_hosted_orchestrator_spawn_hooks_total{result}`                        | Cumulative `spawn-runner` hook outcomes: `ok`, `retryable`, `non_retryable`. Counts orchestrator hook invocations, not session children the runners spawn: not comparable to `sessions_started_total`, since capacity above one, warm pools, and runners spawned again for the same session all diverge the two.                                       |
| `claude_code_self_hosted_orchestrator_spawn_hook_duration_seconds`                      | Histogram of hook durations                                                                                                                                                                                                                                                                                                                            |
| `claude_code_self_hosted_orchestrator_warm_hints_dispatched_total`                      | Standby spawn requests dispatched since process start                                                                                                                                                                                                                                                                                                  |
| `claude_code_self_hosted_orchestrator_session_queue_wait_seconds`                       | Histogram of seconds each session waited in the queue before the orchestrator claimed it for spawn, recorded from the queue-wait timestamp the control plane sends with each session’s spawn request. Use for p50/p99 queue-time alerting. Pre-warming spawns aren’t sampled.                                                                          |
| `claude_code_self_hosted_orchestrator_clock_skew_seconds`                               | Local-minus-server clock skew; diagnostic, present once measured                                                                                                                                                                                                                                                                                       |
| `claude_code_self_hosted_orchestrator_scm_connector_connected`                          | `1` when the [SCM connector](#scm-connector-flags)’s WebSocket is open; `0` while dialing or backing off. Absent when `--scm-connector-host` isn’t set.                                                                                                                                                                                                |
| `claude_code_self_hosted_orchestrator_scm_connector_requests_forwarded_total`           | Cumulative HTTP requests proxied to the configured SCM host since process start. Absent when `--scm-connector-host` isn’t set.                                                                                                                                                                                                                         |

For autoscaling, pick the series that matches your scaling style and gate it before it feeds the scaler:

- **Queue-depth scaling**: feed `claude_code_self_hosted_orchestrator_pool_pending_sessions` into your HPA or KEDA scaler, not `queue_pending_sessions`.
- **Capacity scaling**: scale on the ratio of the runner’s `active_sessions` to `capacity`.
- **Gate on `connected`**: filter the query with `claude_code_self_hosted_orchestrator_connected == 1` per instance, so a disconnected replica’s stale value doesn’t feed the scaler.

During a full poll outage, every replica disconnected, the gated query returns no data. HPA holds the current replica count on a missing metric, but KEDA’s Prometheus scaler at its default `ignoreNullValues: "true"` reads the empty result as zero and scales in; set `ignoreNullValues: "false"` on the ScaledObject, optionally with a `fallback` replica floor. The following Prometheus Operator `PodMonitor` covers both processes. It selects pods by the `app.kubernetes.io/part-of: claude-code-self-hosted-runner` label and the named `health` port that the [Kubernetes recipe](self-hosted-environments-deploy.md#kubernetes) sets; adjust the namespaces to match your deployment:

```python
# Example Prometheus Operator PodMonitor for the Claude Code self-hosted
# runner + orchestrator. Adjust the namespace and label selectors to match
# your deployment. Both the runner and the orchestrator serve /metrics on
# their --health-port (default 8080).
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
  name: claude-code-self-hosted-runner
  namespace: monitoring
spec:
  namespaceSelector:
    matchNames:
      - claude-runners
  selector:
    matchExpressions:
      # Matches the runner Deployment from the Kubernetes recipe, plus any
      # on-demand runner Jobs and orchestrator pods you label the same way
      # and give a named 'health' containerPort.
      - key: app.kubernetes.io/part-of
        operator: In
        values: [claude-code-self-hosted-runner]
  podMetricsEndpoints:
    - port: health
      path: /metrics
      interval: 30s
```

These sample alert rules are a starting point; tune the thresholds for your fleet size:

```python
# Example Prometheus alert rules for the Claude Code self-hosted runner
# + orchestrator. Tune thresholds for your fleet size and SLOs.
groups:
  - name: claude-code-self-hosted-runner
    rules:
      - alert: ClaudeRunnerPollStale
        expr: claude_code_self_hosted_runner_last_poll_age_seconds > 60
        for: 2m
        labels: {severity: warning}
        annotations:
          summary: "Runner {{ $labels.pod }} has not polled in >60s"
      - alert: ClaudeRunnerVersionDrift
        expr: count(count by (version) (claude_code_self_hosted_runner_info)) > 1
        for: 30m
        labels: {severity: info}
        annotations:
          summary: "Runners are running mixed versions"
      - alert: ClaudeRunnerInitErrorsHigh
        expr: increase(claude_code_self_hosted_runner_session_init_errors_total[10m]) > 3
        for: 5m
        labels: {severity: warning}
        annotations:
          summary: "Runner {{ $labels.pod }}: >3 session init failures in 10m (checkout hook / git / token / pre-init crash)"
      - alert: ClaudeRunnerPollErrors
        expr: sum by (pod) (rate(claude_code_self_hosted_runner_poll_errors_total[5m])) > 0
        for: 2m
        labels: {severity: warning}
        annotations:
          summary: "Runner {{ $labels.pod }}: PollWork failing ({{ $value | humanize }}/s over 5m)"
      - alert: ClaudeRunnerSessionStartHookErrors
        expr: increase(claude_code_self_hosted_runner_session_start_hook_errors_total[10m]) > 3
        for: 5m
        labels: {severity: warning}
        annotations:
          summary: "Runner {{ $labels.pod }}: >3 SessionStart hook failures in 10m"

  - name: claude-code-self-hosted-orchestrator
    rules:
      - alert: ClaudeOrchestratorDisconnected
        expr: claude_code_self_hosted_orchestrator_connected == 0
        for: 2m
        labels: {severity: critical}
        annotations:
          summary: "Orchestrator {{ $labels.pod }} cannot reach the Anthropic control plane"
      - alert: ClaudeOrchestratorPollStale
        expr: claude_code_self_hosted_orchestrator_last_poll_age_seconds > 90
        for: 2m
        labels: {severity: warning}
        annotations:
          summary: "Orchestrator {{ $labels.pod }} has not polled in >90s (poll loop waits on hook execution)"
      - alert: ClaudeOrchestratorCircuitBroken
        expr: claude_code_self_hosted_orchestrator_queue_circuit_broken_sessions > 0
        for: 1m
        labels: {severity: critical}
        annotations:
          summary: "Sessions blocked from spawning: {{ $value }}. Read each one's error in the Activity tab, fix the cause, then select Retry"
      - alert: ClaudeOrchestratorPollErrors
        expr: sum by (pod) (rate(claude_code_self_hosted_orchestrator_poll_errors_total[5m])) > 0
        for: 2m
        labels: {severity: warning}
        annotations:
          summary: "Orchestrator {{ $labels.pod }}: PollSpawnHints failing ({{ $value | humanize }}/s over 5m)"
      - alert: ClaudeOrchestratorSpawnHookFailing
        expr: sum by (pod) (increase(claude_code_self_hosted_orchestrator_spawn_hooks_total{result!="ok"}[5m])) > 3
        for: 5m
        labels: {severity: warning}
        annotations:
          summary: "Orchestrator {{ $labels.pod }}: >3 spawn-runner hook failures in 5m"
```


[​](#pass-through-session-child-metrics)

Pass through session-child metrics

Each session runs in its own child process with its own OpenTelemetry metrics; at `--capacity` above one, the runner rewrites how those child metrics are exposed. Setting `OTEL_METRICS_EXPORTER=prometheus` on the runner host and `CLAUDE_CODE_ENABLE_TELEMETRY=1` in the session’s environment, for example from your [wrapper script](self-hosted-environments-configuration.md#wrapper-scripts) or the runner’s own environment, which sessions inherit, re-exposes each child’s counter and gauge instruments on the runner’s own `/metrics` endpoint, alongside the runner’s series. The runner rewrites the child’s exporter to push over OTLP to a loopback-only receiver on the health port, tags each series with `session_id` and `client_platform` labels, and evicts a session’s series when that session ends. Histograms don’t pass through, and a child metric whose name would collide with the runner’s own prefix is dropped. At the default `--capacity 1`, the rewrite doesn’t apply: the session’s child binds its own Prometheus endpoint on port 9464 as usual.


[​](#join-a-session’s-metrics-to-the-runner’s)

Join a session’s metrics to the runner’s

To put a session’s own metrics beside `claude_code_self_hosted_runner_session_idle_seconds`, join on the runner’s `session_id`:

- **At `--capacity` above one**: join directly. On the runner’s `/metrics`, the session’s series carry the same `session_id` as that metric, in `session_...` form.
- **At `--capacity 1`**: join on a converted `ccr_session_id`. By default, each Claude Code metric on port 9464 carries that label, which holds the session ID in `cse_...` form. In a new label, replace its `cse_` prefix with `session_` to get the runner’s `session_id`. The metric’s own `session_id` holds Claude Code’s own [`session.id`](monitoring-usage.md#standard-attributes), which matches no runner series.


[​](#session-lifecycle-counter-semantics)

Session lifecycle counter semantics

The `sessions_started_total`, `sessions_completed_total`, `sessions_failed_total`, and `sessions_interrupted_total` counters classify each session by how it ended. Every spawned session child increments `sessions_started_total` at spawn time, and exactly one of the other three increments at exit, so `sessions_started_total` minus the sum of the other three equals the number of session children currently running.

- `completed`: the session ended cleanly. This covers the child exiting on its own with code `0`, the session being archived or deleted while the child was still connected, and the runner handing the slot back cleanly: releasing the session at the idle timeout, the retire time, or the `--kill-session-after-min` limit; a startup timeout; or a server-side deassign the poll loop noticed before the child exited. Increments `sessions_completed_total`.
- `failed`: the child exited on its own with a non-zero code, either a crash or a setup failure after spawn. Increments `sessions_failed_total`.
- `interrupted`: the runner terminated the child for an operational reason that’s neither a session success nor a runner fault, such as a drain, or terminating a session that was still on the runner when the [`SELF_HOSTED_RUNNER_MAX_LIFETIME_GRACE_MS`](#environment-variable-only-settings) grace window after its `--kill-session-after-min` limit ended. A Kubernetes rolling restart sending `SIGTERM` is one example of a drain. Increments `sessions_interrupted_total`.

Before v2.1.260, the runner terminated every session that reached its `--kill-session-after-min` limit and counted it in `sessions_interrupted_total`. The [`post-session` hook](self-hosted-environments-configuration.md#post-session)’s `CLAUDE_RUNNER_EXIT_REASON` classifies clean handoffs differently. The hook reports these as `interrupted`, because the runner stopped the child: a release, a startup timeout, a server deassign, and an archive or delete that the poll noticed first. These counters record the same events as `completed`, because the slot was handed back cleanly. If you reconcile hook receipts against `sessions_completed_total` directly, you undercount completions. Use the hook for per-session guarantees and the counters for aggregate rates. On a one-shot environment, `--capacity 1` with the default `--drain-grace-sec 0`, each runner process exits moments after its one session ends. `sessions_completed_total`, `sessions_failed_total`, and `sessions_interrupted_total` increment only at session end, right before that exit, so a Prometheus scrape every 15 to 60 seconds rarely catches the increment before the runner’s series disappears; these three end-of-session counters are the terminal counters the rest of this section refers to. `sessions_started_total` increments at spawn and stays visible for the life of the session, so it reliably shows up, but on a one-shot environment it reads closer to “sessions currently running” than a cumulative count. Use the series in this table for the corresponding goal instead of the terminal counters:

| Goal        | Use                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
|:------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Throughput  | `claude_code_self_hosted_orchestrator_spawn_hooks_total{result="ok"}`, a counter on the long-lived orchestrator that increments once per successful `spawn-runner` hook and stays meaningful under `rate()`. It counts hook invocations rather than sessions, so pre-warming and repeated spawns for the same session diverge it from session counts.                                                                                                                                                                |
| Utilization | `sum(claude_code_self_hosted_runner_active_sessions)` against `sum(claude_code_self_hosted_runner_capacity)`, both gauges valid at every scrape regardless of runner lifetime                                                                                                                                                                                                                                                                                                                                        |
| Backlog     | `claude_code_self_hosted_orchestrator_pool_pending_sessions` for queue depth, and `claude_code_self_hosted_orchestrator_queue_circuit_broken_sessions`, alerting if above zero                                                                                                                                                                                                                                                                                                                                       |
| Failures    | `claude_code_self_hosted_runner_sessions_failed_total`, best effort: real crashes after spawn do increment it, and `rate()` is meaningful on runners that outlive their sessions with `--drain-grace-sec` above `0`. A one-shot environment has the same scrape-window problem as the other terminal counters, so treat any non-zero value you do see as worth investigating. Failures before spawn, such as a checkout hook failure, git preparation, or a token issue, appear only in `session_init_errors_total`. |

The `orchestrator_*` rows exist only on environments running the [on-demand orchestrator](self-hosted-environments-configuration.md#on-demand-runners). On a fixed fleet whose runners outlive their sessions, with `--drain-grace-sec` above `0`, use `sum(rate(claude_code_self_hosted_runner_sessions_started_total[5m]))` for throughput; on a one-shot fleet that series has the same scrape-window problem as the terminal counters, so rely on the queued-sessions count instead. Check backlog on the environment’s **Activity** tab, on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments): the runners don’t export a queue-depth series. For per-session outcome reporting, use the [`post-session` hook](self-hosted-environments-configuration.md#post-session) instead: it fires at every session end where a child process was spawned, apart from abrupt runner termination such as a VM preemption, per the [hook’s own contract](self-hosted-environments-configuration.md#post-session).


[​](#what’s-next)

What’s next

- [Self-hosted environments](self-hosted-environments.md): the environment, runner, and session model; the [quickstart](self-hosted-environments-quickstart.md) and [Deploy to production](self-hosted-environments-deploy.md) hold setup and operations
- [Customize sessions](self-hosted-environments-configuration.md): wrapper scripts, lifecycle hooks, and on-demand runners
- [Verify session identity](self-hosted-environments-identity.md): the session token, its claims, and how to verify it

---

## Test self-hosted environments end to end - Claude Code Docs

Source: https://code.claude.com/docs/en/self-hosted-environments-testing
Path: 13-Enterprise-Admin/self-hosted-environments-testing.md

## On this page

- [Install the capture hook on your test runner](#install-the-capture-hook-on-your-test-runner)
  - [Save the hook files](#save-the-hook-files)
  - [Before you start the runner](#before-you-start-the-runner)
- [Run the test loop](#run-the-test-loop)
  - [--environment dispatch behavior](#environment-dispatch-behavior)
- [Example script](#example-script)
- [Remote test runners](#remote-test-runners)
- [Authenticate from CI](#authenticate-from-ci)
  - [Long-lived CI host](#long-lived-ci-host)
  - [Ephemeral CI runners](#ephemeral-ci-runners)
- [Create a dedicated test environment](#create-a-dedicated-test-environment)
  - [Mint the admin token](#mint-the-admin-token)
  - [Create the environment](#create-the-environment)
  - [Delete the environment](#delete-the-environment)

Self-hosted environments

# Test self-hosted environments end to end

Copy pageCopy page

Verify a self-hosted runner image from CI: dispatch a session with the CLI, read Claude’s replies through a Stop hook, and script the full loop.

Copy pageCopy page

Self-hosted environments are in public beta on Team and Enterprise plans; [Availability and limitations](self-hosted-environments.md#availability-and-limitations) covers the enablement path. This page is the CI test recipe; see the [quickstart](self-hosted-environments-quickstart.md) for setup and [Deploy to production](self-hosted-environments-deploy.md) for the fleet recipes.

In a [self-hosted environment](self-hosted-environments.md), Claude Code [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) run on a runner image you build and maintain. Before rolling a new image to your production environment, drive a full session against a test environment from a script: create a session, read Claude’s reply, send a follow-up, and read that reply too. This is the shape of a CI smoke test that verifies your runner image, git access, and any custom tools before you promote a change. This recipe assumes you’ve already [set up an environment and a runner](self-hosted-environments-quickstart.md#set-up-an-environment-and-runner), and that your CI job starts the runner process on the same host as the test script, the natural setup for testing a new runner image. A Stop hook you install on the runner writes each turn’s final reply to a local file, and the script reads it from there, so the only calls to the Anthropic API are the two dispatches themselves. If your test runners are on separate infrastructure, see [Remote test runners](#remote-test-runners).


[​](#install-the-capture-hook-on-your-test-runner)

Install the capture hook on your test runner

The read-back works through a Claude Code [Stop hook](../07-Hooks/hooks.md#stop): when Claude finishes a turn, the hook receives the final assistant message as `last_assistant_message` in its stdin JSON and appends it to `$E2E_REPLY_DIR/<session_id>.txt`. Install it the same way as the [commit-nudge Stop hook](self-hosted-environments-configuration.md#prompt-sessions-to-push-their-work), on the runner host’s `~/.claude/`, which the runner seeds into every session.


[​](#save-the-hook-files)

Save the hook files

Save the two files below on the runner host:

- The settings block: merge into `~/.claude/settings.json` on the runner host
- The script: save as `~/.claude/hooks/e2e-stop-hook-capture.sh` on the runner host and make it executable

```python
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "timeout": 10,
            "command": "\"$CLAUDE_CONFIG_DIR/hooks/e2e-stop-hook-capture.sh\""
          }
        ]
      }
    ]
  }
}
```

```python
#!/bin/sh
# Stop hook for testing a self-hosted environment end to end: writes each
# turn's final assistant reply to $E2E_REPLY_DIR/<session_id>.txt so a
# co-located test driver can read it without calling the Anthropic API.
# Install on the TEST runner only. Requires jq.

# No-op unless the driver is listening. Never fail the turn.
[ -n "${E2E_REPLY_DIR:-}" ] && [ -d "$E2E_REPLY_DIR" ] || exit 0

# CLAUDE_CODE_REMOTE_SESSION_ID is exported in cse_... form; the session
# id the dispatch CLI prints is in session_... form. Same id, different
# prefix.
sid=$(printf '%s' "${CLAUDE_CODE_REMOTE_SESSION_ID:-}" | sed 's/^cse_/session_/')
[ -n "$sid" ] || exit 0

# last_assistant_message is absent when the final assistant turn had no
# text, such as a tool-use-only turn. The `// empty` filter makes that a
# zero-byte write rather than the literal string "null".
jq -r '.last_assistant_message // empty' >> "$E2E_REPLY_DIR/$sid.txt" 2>/dev/null
exit 0
```


[​](#before-you-start-the-runner)

Before you start the runner

The hook has these requirements:

- Install it before you start the runner. The runner snapshots `~/.claude/` once at startup, so a hook added to a running runner takes effect only after a restart.
- Export `E2E_REPLY_DIR` to the runner process. The hook is a no-op when the variable is unset or the directory doesn’t exist, so set it wherever you start the runner, such as the systemd unit, pod spec, or CI step. The test script below requires it too.

Install this hook only on runners serving your test environment. It writes every session’s final reply to disk whenever `E2E_REPLY_DIR` exists, which is harmless on a throwaway CI runner but not something to carry into a production-environment runner image where the variable might be set by accident.


[​](#run-the-test-loop)

Run the test loop

The `--environment` and `--ref` dispatch flags require Claude Code v2.1.224 or later on the machine that runs the script, the same floor as the runner itself. With the hook in place and a runner started on this host, the test script:

1.  Creates a session on the test environment with `claude -p "<prompt>" --environment <environment-id> --output-format json`. Run the command from a git checkout so the CLI can auto-detect the repository from the `origin` remote. The optional `--ref <branch>` bases the session’s checkout on a named ref instead of local HEAD. The command exits without waiting for Claude’s reply. What it prints tells your script the outcome:
    - **Session created**: one line of JSON such as `{"ok":true,"session_id":"session_...","title":"...","url":"...","pool_id":"..."}`
    - **Session creation failed**: the line `{"ok":false,"error":"..."}`, and the command exits with status 1
    - **Some earlier errors**, such as cloud sessions being unavailable for your organization or a missing prompt: the error on stderr with no JSON line, and the command exits with status 1
2.  Waits for the reply to appear in `$E2E_REPLY_DIR/<session_id>.txt`, written by the Stop hook on the runner once the turn completes.
3.  Sends a follow-up with `claude -p "<message>" --cloud <session_id> --output-format json` (see [Send a follow-up message to a running session](../02-Claude-Code-CLI/claude-code-on-the-web.md#send-follow-ups-from-the-cli)), which posts a user event to the existing session and exits.
4.  Waits for the follow-up’s reply the same way as step 2.


[​](#environment-dispatch-behavior)

`--environment` dispatch behavior

Claude Code creates the session, prints the session ID and a link to it, and exits. The flag takes precedence over the [`remote.defaultEnvironmentId`](../02-Claude-Code-CLI/settings-reference.md#remote-defaultenvironmentid) setting. It doesn’t support `--output-format stream-json`, and can’t be combined with flags that resume, attach to, or preconfigure a session, such as `--resume`, `--continue`, `--teleport`, `--session-id`, or `--init-only`. `--cloud` is rejected with a session ID or URL, and in non-interactive runs when it carries a description. A bare `--cloud` is treated as absent. From a terminal, you can pass the task as the `--cloud` description instead of a positional prompt.


[​](#example-script)

Example script

The example script runs on the same machine as the test runner. Before you run it, prepare that machine:

- **Repository checkout**: run the script from a git checkout of the repository you want the session to work in.
- **Runner**: start a runner on this host with the capture hook installed and `E2E_REPLY_DIR` exported.
- **Sign-in**: sign in with a claude.ai account on the machine that runs the script, as [Authenticate from CI](#authenticate-from-ci) describes.
- **Environment ID**: set `CLAUDE_TEST_ENVIRONMENT_ID` to your test environment’s `ccpool_...` ID, shown in the environment’s detail dialog on the admin page or returned by the [create-environment call](#create-a-dedicated-test-environment).

The script below runs the full loop against `$CLAUDE_TEST_ENVIRONMENT_ID` and asserts on a sentinel phrase in each reply.

```python
#!/usr/bin/env bash
# End-to-end test against a self-hosted environment, using Stop-hook read-back.
# Prereqs: `claude auth login` has been run on this machine (see "Authenticate
# from CI" below); jq is installed; CLAUDE_TEST_ENVIRONMENT_ID names an
# environment whose runner is the one on this host, with the capture hook
# installed and E2E_REPLY_DIR in its environment.

set -euo pipefail

: "${CLAUDE_TEST_ENVIRONMENT_ID:=${CLAUDE_TEST_POOL_ID:-}}"  # CLAUDE_TEST_POOL_ID is the legacy spelling
: "${CLAUDE_TEST_ENVIRONMENT_ID:?set CLAUDE_TEST_ENVIRONMENT_ID to a ccpool_... id served by a runner on this host}"
: "${E2E_REPLY_DIR:?set E2E_REPLY_DIR to the directory the Stop hook on your test runner writes to, and export it to the runner process}"
: "${TEST_REPO_REF:=main}"

[ -d "$E2E_REPLY_DIR" ] || {
  echo "FAIL: E2E_REPLY_DIR ($E2E_REPLY_DIR) does not exist. The Stop hook on the runner needs it." >&2
  exit 1
}

# Waits until $E2E_REPLY_DIR/<session_id>.txt contains $2, or fails after
# 90 seconds. Tune the timeout to your environment's cold-start time. The
# file is written by the Stop hook on the runner.
await_reply() {
  local expect="$2" f="$E2E_REPLY_DIR/$1.txt"
  local deadline=$(($(date +%s) + 90))
  while :; do
    if [ -f "$f" ] && grep -qF -- "$expect" "$f"; then
      return
    fi
    [ "$(date +%s)" -lt "$deadline" ] || {
      echo "FAIL: '$expect' not in $f within 90s. The Stop hook on the runner did not write it." >&2
      echo "-- $E2E_REPLY_DIR contents --" >&2; ls -la "$E2E_REPLY_DIR" >&2
      [ -f "$f" ] && { echo "-- $f --" >&2; cat "$f" >&2; }
      exit 1
    }
    sleep 1
  done
}

# 1. Create the session on the test environment. Run from a git checkout
# so the CLI can auto-detect the repo. --ref pins the checkout to a named
# ref regardless of local HEAD.
TURN1="e2e-probe-$(date +%s)-$$: say exactly 'ok: custom tools are reachable' and nothing else"
EXPECT1="ok: custom tools are reachable"
create_json=$(claude -p "$TURN1" --environment "$CLAUDE_TEST_ENVIRONMENT_ID" \
  --ref "$TEST_REPO_REF" --output-format json < /dev/null)
echo "create: $create_json"
SESSION_ID=$(jq -er '.session_id' <<<"$create_json")

# 2. Wait for the turn-1 reply.
await_reply "$SESSION_ID" "$EXPECT1"
echo "turn-1 reply ok"

# 3. Post a follow-up via the CLI.
TURN2="e2e-probe-followup-$(date +%s): say exactly 'ok: follow-up delivered' and nothing else"
EXPECT2="ok: follow-up delivered"
followup_json=$(claude -p "$TURN2" --cloud "$SESSION_ID" --output-format json < /dev/null)
echo "followup: $followup_json"
jq -e '.ok == true' <<<"$followup_json" >/dev/null

# 4. Wait for the turn-2 reply.
await_reply "$SESSION_ID" "$EXPECT2"
echo "turn-2 reply ok"

echo "PASS: test-environment round-trip (session $SESSION_ID)"
```

Replace the `TURN1`/`TURN2` prompts and `EXPECT1`/`EXPECT2` sentinels with whatever exercises your setup, such as asking Claude to run one of your custom MCP tools and asserting on its output.


[​](#remote-test-runners)

Remote test runners

If your test runners are on separate infrastructure, such as a persistent Kubernetes fleet your CI job can’t share a filesystem with, swap the file write in the Stop hook for a POST to an endpoint your driver listens on:

```python
#!/bin/sh
# Variant of the capture hook for runners on separate infrastructure.
# Set E2E_REPLY_URL on the runner to an endpoint the driver controls.
[ -n "${E2E_REPLY_URL:-}" ] || exit 0
sid=$(printf '%s' "${CLAUDE_CODE_REMOTE_SESSION_ID:-}" | sed 's/^cse_/session_/')
[ -n "$sid" ] || exit 0
jq -r '.last_assistant_message // empty' | \
  curl -fsS -X POST --data-binary @- "$E2E_REPLY_URL/$sid" >/dev/null 2>&1
exit 0
```

On the driver side, run anything that accepts the POST and holds the reply until the test asks for it, such as a small HTTP listener inside the CI job or a webhook receiver you already run. The hook runs on your infrastructure, so the endpoint only needs to be reachable from your runners.


[​](#authenticate-from-ci)

Authenticate from CI

Both `claude -p ... --environment` and `claude -p ... --cloud` authenticate with a claude.ai OAuth token; API keys, such as `sk-ant-xxxxx`, aren’t accepted for either call. Two approaches make a token available in CI.


[​](#long-lived-ci-host)

Long-lived CI host

Run `claude auth login` once interactively on the machine that executes the script, using a dedicated user account for automation. Claude Code stores the token in the OS keychain on macOS, or in `~/.claude/.credentials.json` on Linux and Windows. On a macOS host whose Keychain can’t be written, as is typical in an SSH session where the login Keychain stays locked, Claude Code stores the token in `~/.claude/.credentials.json` there too. See [Credential management](../02-Claude-Code-CLI/authentication.md#credential-management). The CLI refreshes the short-lived access token automatically on each invocation, but the underlying refresh-token grant is capped at 30 days from the initial login, so re-run `claude auth login` interactively on that host every 30 days.


[​](#ephemeral-ci-runners)

Ephemeral CI runners

There is no long-lived CI token for this today. The scope that grants cloud-session control, `user:sessions:claude_code`, is capped server-side at 30 days, so `claude setup-token`, which mints a one-year inference-only token, doesn’t cover it. The [environment secret](self-hosted-environments-quickstart.md#set-up-an-environment-and-runner) isn’t accepted either, since it only authorizes a runner to register with the environment, not to create sessions. To provision a stored login onto an ephemeral runner, set [`CLAUDE_CODE_OAUTH_REFRESH_TOKEN` and `CLAUDE_CODE_OAUTH_SCOPES`](../02-Claude-Code-CLI/env-vars.md#variables) so `claude auth login` exchanges the token without a browser; the same 30-day cap applies to the refresh grant. Contact your Anthropic account team if you need a machine-identity path that isn’t bound to a human account.


[​](#create-a-dedicated-test-environment)

Create a dedicated test environment

Create and delete environments programmatically so each CI run gets a clean one; the runner your CI job starts registers into the fresh environment. The create and delete calls below are the same endpoints that the **Cloud environments** admin page on claude.ai uses, and they require the `anthropic-beta: ccr-byoc-2025-07-29` header.


[​](#mint-the-admin-token)

Mint the admin token

`$ADMIN_TOKEN` is a claude.ai OAuth access token for an account that holds an Owner role, minted the same way as [Authenticate from CI](#authenticate-from-ci):

- **Mint it**: run `claude auth login` with an account that holds an Owner role, then read the current access token from wherever [Long-lived CI host](#long-lived-ci-host) says Claude Code stored it.
- **Read it fresh each run**: the CLI rotates the access token, and the same 30-day refresh-grant cap applies, so don’t store a copy.
- **Pass it via stdin**: as the example does, so the token never lands in curl’s argument list or your build log.


[​](#create-the-environment)

Create the environment

Capture the response without echoing it: `pool_secret` is a long-lived credential that can register runners into the environment, so store it as a masked CI secret and print only the environment ID. The `-H @-` form that keeps the token out of the process list requires curl 7.55 or later; older curl treats `@-` as a literal header and sends the request without authorization.

```python
create=$(curl -fsS -X POST -H @- \
  -H "anthropic-beta: ccr-byoc-2025-07-29" -H "anthropic-version: 2023-06-01" \
  -H "content-type: application/json" \
  -d '{"name":"ci-test-environment"}' \
  https://api.anthropic.com/v1/code/runners/self-hosted/pools \
  <<<"Authorization: Bearer $ADMIN_TOKEN")
ENVIRONMENT_ID=$(jq -er .pool.pool_id <<<"$create")
ENVIRONMENT_SECRET=$(jq -er .pool_secret <<<"$create")
```

Until an [Owner turns on **Allow self-hosted environments**](self-hosted-environments.md#availability-and-limitations) for the organization, the call fails with a `403` `permission_error` reading `self-hosted runners are disabled by your organization's policy`. Start a runner on this host with `SELF_HOSTED_RUNNER_ENVIRONMENT_SECRET=$ENVIRONMENT_SECRET`, plus the capture hook and `E2E_REPLY_DIR` per [Install the capture hook](#install-the-capture-hook-on-your-test-runner), then run the test script.


[​](#delete-the-environment)

Delete the environment

Delete the environment when the run finishes, so each CI run starts clean:

```python
curl -fsS -X DELETE -H @- \
  -H "anthropic-beta: ccr-byoc-2025-07-29" -H "anthropic-version: 2023-06-01" \
  "https://api.anthropic.com/v1/code/runners/self-hosted/pools/$ENVIRONMENT_ID" \
  <<<"Authorization: Bearer $ADMIN_TOKEN"
```

---

## Self-hosted environments - Claude Code Docs

Source: https://code.claude.com/docs/en/self-hosted-environments
Path: 13-Enterprise-Admin/self-hosted-environments.md

## On this page

- [How self-hosted environments work](#how-self-hosted-environments-work)
- [Availability and limitations](#availability-and-limitations)
- [Why self-host](#why-self-host)
- [Environments, runners, and sessions](#environments-runners-and-sessions)
  - [Key concepts](#key-concepts)
  - [Session lifecycle](#session-lifecycle)
  - [Runner lifecycle](#runner-lifecycle)
  - [Network paths](#network-paths)
- [What stays on your infrastructure](#what-stays-on-your-infrastructure)
- [Get started](#get-started)

Self-hosted environments

# Self-hosted environments

Copy pageCopy page

Run Claude Code cloud sessions on infrastructure you control: set up a self-hosted environment, deploy runners, and route sessions to your own compute.

Copy pageCopy page

Self-hosted environments are in public beta on Team and Enterprise plans and are off by default. See [Availability and limitations](#availability-and-limitations) for the enablement path and what’s excluded.

A self-hosted environment executes Claude Code cloud sessions on infrastructure your organization operates. A [cloud session](../02-Claude-Code-CLI/claude-code-on-the-web.md) is any session that runs somewhere other than the developer’s machine. Developers start them from claude.ai, the mobile and desktop apps, the terminal with [`claude --cloud`](../02-Claude-Code-CLI/claude-code-on-the-web.md#from-terminal-to-cloud), and [cloud scheduled tasks](../02-Claude-Code-CLI/web-scheduled-tasks.md). By default, cloud sessions execute on Anthropic’s infrastructure. In a self-hosted environment, those same sessions execute inside your network. The developer experience is otherwise the same apart from the differences in [Availability and limitations](#availability-and-limitations) and the deploy page’s [known issues](self-hosted-environments-deploy.md#known-issues-and-limitations). If your team doesn’t use cloud sessions, there’s nothing here to configure: sessions in a terminal or IDE always run on the developer’s own machine. If you want to run Claude Code on your own always-on machine and drive it from other devices, use [Remote Control](../02-Claude-Code-CLI/remote-control.md), which is also available on Pro and Max plans. When you’re ready to set up, go straight to the [quickstart](self-hosted-environments-quickstart.md); to review the security posture first, start with [Deploy to production](self-hosted-environments-deploy.md). The rest of this page explains how self-hosting works and when to choose it.


[​](#how-self-hosted-environments-work)

How self-hosted environments work

Self-hosting has three parts:

- **Environment**: a named destination that cloud sessions can be sent to. Your organization creates environments in claude.ai admin settings, and each one groups a set of runners.
- **Runner**: a program running on hosts inside your network. Runners execute the sessions; the idea is the same as a self-hosted CI runner.
- **Session**: one Claude Code task a developer started.

When a developer starts a cloud session, the session-start UI shows an environment picker listing Anthropic-hosted environments alongside any your organization has created. If they choose yours, Anthropic’s control plane places the session on your environment’s queue, where a runner claims it, clones the repository the developer chose, and starts a Claude Code process on your host to run it. The runner authenticates to your git host with credentials you configure; [Configure git](self-hosted-environments-deploy.md#configure-git) covers the options. Sessions reach your internal services from inside your network, and your git host the same way when it’s internal; the traffic to Anthropic, queue polling, the session’s event stream, and by default model inference, is outbound HTTPS to `api.anthropic.com`, with the short list of further hosts sessions can reach in [Network requirements](self-hosted-environments-deploy.md#network-requirements). Anthropic never connects into your network.

The two Claude Code boxes in the diagram are session processes: one runner executing two sessions at once, up to its configured capacity. A runner serves one [owner](#key-concepts) at a time and locks to that owner when it claims its first session, so checked-out code never mixes between owners; [Runner lifecycle](#runner-lifecycle) covers the rule. You can start runners yourself and keep them running, or run the [autoscaling orchestrator](self-hosted-environments-configuration.md#on-demand-runners), a second process you host, which starts runners as sessions queue; each runner exits on its own when its work finishes. Either way, you set the environment up once, and it appears in the picker on every supported surface.


[​](#availability-and-limitations)

Availability and limitations

Check these before planning a rollout:

- **Plans**: public beta for Team and Enterprise organizations. Self-hosted environments are off by default; an [Owner](../02-Claude-Code-CLI/cloud-environments.md#organization-shared-environments) turns on **Allow self-hosted environments** on the [**Cloud environments** admin page](https://claude.ai/admin-settings/cloud-environments), which requires [cloud sessions](../02-Claude-Code-CLI/claude-code-on-the-web.md) to be enabled for the organization.
- **Zero Data Retention and HIPAA**: unavailable for organizations with [Zero Data Retention](../02-Claude-Code-CLI/zero-data-retention.md) enabled or with the [HIPAA configuration](../02-Claude-Code-CLI/hipaa-setup.md) applied.
- **Model inference**: sessions use the Anthropic API unless you configure a runner to [send model requests to Amazon Bedrock or Google Cloud’s Agent Platform](self-hosted-environments-configuration.md#send-model-requests-to-bedrock-or-agent-platform). Session content goes to Anthropic in both cases. On a runner configured this way, [server-managed settings](server-managed-settings.md) and organization policies from claude.ai don’t reach sessions.
- **Surfaces**: sessions started from [claude.ai/code](https://claude.ai/code), the mobile and desktop apps, [cloud scheduled tasks](../02-Claude-Code-CLI/web-scheduled-tasks.md), and the terminal, with [`claude --cloud`](../02-Claude-Code-CLI/claude-code-on-the-web.md#from-terminal-to-cloud) or an [`--environment` dispatch](self-hosted-environments-testing.md#run-the-test-loop), can run in self-hosted environments. [Claude Tag](https://claude.com/docs/claude-tag/overview) sessions can run in them too, but Claude can’t use [Access bundles](https://claude.com/docs/claude-tag/concepts/glossary#access-bundle) in those sessions yet. [Claude Security](../02-Claude-Code-CLI/claude-security.md) and [Code Review](../02-Claude-Code-CLI/code-review.md) sessions don’t route to them yet. Support for those two surfaces follows separately.
- **Repositories**: sessions check out repositories from GitHub; see [GitHub authentication options](../02-Claude-Code-CLI/claude-code-on-the-web.md#github-authentication-options). For a GitHub Enterprise Server host, see its [network requirements](../02-Claude-Code-CLI/github-enterprise-server.md#network-requirements).
- **Billing**: sessions in a self-hosted environment consume your organization’s Claude Code usage the same way sessions in Anthropic-hosted environments do.


[​](#why-self-host)

Why self-host

Most teams are better served by Anthropic-hosted environments, which need no infrastructure to run or maintain. Self-hosting is for teams whose network, tooling, or compliance requirements call for keeping session execution on infrastructure they control. If that’s you, plan for the operational ownership it carries: you build and maintain the runner image, operate the fleet, and control its network. In exchange, self-hosting gives you network access, custom tooling, and compliance control:

- **Network access**: sessions run inside your network and can reach internal services, databases, and registries without exposing them to the public internet
- **Custom tooling**: pre-install compilers, SDKs, and internal CLIs in your runner image so every session starts ready to build
- **Compliance**: repository checkouts and build artifacts stay on infrastructure you control. Session content still goes to `api.anthropic.com`.


[​](#environments-runners-and-sessions)

Environments, runners, and sessions

Environments are managed on the **Cloud environments** page in claude.ai admin settings; runners are processes you start and manage on your own infrastructure.


[​](#key-concepts)

Key concepts

These terms appear throughout the self-hosted pages:

| Term               | What it is                                                                                                                                                                                           |
|:-------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Environment        | A named group of your runners, created in claude.ai settings. Sessions are routed to an environment, not to an individual runner.                                                                    |
| Environment secret | The single shared credential runners use to authenticate and register with the environment. Shown once at environment creation, labeled **environment key** in the admin UI.                         |
| Runner             | The long-lived process you deploy. A runner registers with the environment, receives a runner token, and polls for sessions.                                                                         |
| Session            | One Claude Code task, started from claude.ai, the mobile app, or another Anthropic surface such as a scheduled task or an agent. Each session runs as a child Claude Code process the runner spawns. |

In API fields, token claims, and metric names, the environment appears as `pool`, and the environment ID is the `pool_id`. The [reference](self-hosted-environments-reference.md) maps the two spellings, including the deprecated `pool` flag names. A runner serves one owner at a time. The first session a runner picks up locks the runner to that session’s owner, and the runner then runs sessions only for that owner, up to a configured capacity. Who the owner is depends on how the session started:

- **Sessions a user starts**: the owner is that user’s account.
- **Claude Tag channel sessions**: Claude runs them with no user account attached, so the owner is the [Claude Tag agent](https://claude.com/docs/claude-tag/concepts/glossary#agent-identity) that started the session. Every channel session that agent starts has the same owner, whoever sent the Slack message, so a runner locked to it serves sessions that different people started when you run it at a `--capacity` above one or with a positive `--drain-grace-sec`.

The minimum fleet size is therefore the number of owners you expect to be active at once, counting users and Claude Tag agents.


[​](#session-lifecycle)

Session lifecycle

When a developer starts a session and selects your environment, Anthropic’s control plane places the session on the environment’s queue. From there:

1.  A runner with free capacity claims the session and holds a lease on it.
2.  The runner clones the repository into its working directory and spawns a child Claude Code process.
3.  The child streams events back over HTTPS while the runner keeps polling; each poll refreshes the lease and doubles as the heartbeat.
4.  If the runner stops polling, its lease lapses after about 60 seconds. The server can then take a few minutes and sometimes longer to requeue the session for another runner.

The runner gives each poll request 10 seconds. When a request times out, is lost, or gets a response the runner can’t parse, the runner keeps serving its live sessions and retries after a second or two instead of waiting for the next scheduled poll. For example, an intercepting proxy that answers the poll with its own page produces a response the runner can’t parse. Each time another request fails in one of those ways, the runner doubles the gap before the next retry, up to 20 seconds, and shortens the gap whenever the lease is close to expiring.


[​](#runner-lifecycle)

Runner lifecycle

The first session a runner picks up locks the runner to that session’s owner, and the runner runs up to `--capacity` concurrent sessions for that owner. While the runner has active sessions and hasn’t received a shutdown signal or reached its retire time, the runner keeps claiming the locked owner’s queued work. What happens once they finish depends on [`--drain-grace-sec`](self-hosted-environments-reference.md#runner-cli-flags):

- **At the default of `0`**: the runner exits as soon as its active sessions finish, without polling for more, so the orchestrator you deploy it under, such as Kubernetes, can restart it with a fresh disk, ready to serve any owner.
- **At a positive value**: the runner keeps polling the locked owner’s queue for that many seconds before exiting.

This lifecycle isolates each owner’s checked-out code without requiring the runner to delete disk state between owners. How your infrastructure stops a runner decides whether you need `--retire-at`. A kill that delivers `SIGTERM` needs no flag: the runner drains as [Shutdown timing](self-hosted-environments-deploy.md#shutdown-timing) describes, or keeps serving the sessions it already holds when you set [`--defer-shutdown-max-min`](self-hosted-environments-deploy.md#defer-the-drain-past-the-first-signal). If your infrastructure instead destroys hosts at a known wall-clock time without a signal, or with a grace period too short to drain, such as a sandbox lifetime cap or spot-instance reclamation, pass `--retire-at <epoch-seconds>` set to a few minutes before that time. At the retire time:

1.  The runner stops taking new work.
2.  The runner releases each active session through the same release path the [`--release-idle-session-min`](self-hosted-environments-reference.md#runner-cli-flags) flag uses, so the session resumes on a fresh runner when the user sends their next message. When the runner releases each session depends on its state:
    - The runner releases a session that’s mid-turn after that turn finishes. It first waits for the session’s process to report the turn’s end to Anthropic, for no longer than [`SELF_HOSTED_RUNNER_POST_TURN_SETTLE_MS`](self-hosted-environments-reference.md#environment-variable-only-settings). Before v2.1.280, the runner released the session as soon as the turn finished.
    - When a turn finishes and leaves background tasks running, the runner waits up to 60 seconds for them, then releases the session even if they’re still running. If the tasks have finished but the follow-up turn that reads their results hasn’t run yet, the runner keeps the session until that turn finishes, and waits no longer than [`SELF_HOSTED_RUNNER_BG_RESULT_GRACE_MS`](self-hosted-environments-reference.md#environment-variable-only-settings) for that turn to start.
3.  The runner exits 0 once all its sessions are released.

A turn that outlives the kill is still lost; [Shutdown timing](self-hosted-environments-deploy.md#shutdown-timing) covers sizing the margin. Without `--retire-at`, a signal-less host kill is indistinguishable from a crash: the control plane records a lost worker rather than a clean release, and the session requeues to another runner.


[​](#network-paths)

Network paths

The runner and its sessions make several kinds of outbound connection, and no inbound connectivity from Anthropic is required:

- **Control plane**: the runner polls `api.anthropic.com` for work and posts setup-progress and failure events, all outbound HTTPS. Polling doubles as the runner’s heartbeat.
- **Git**: the runner clones from and pushes to your git host over HTTPS or SSH, authenticated with credentials your deployment provides. See [Configure git](self-hosted-environments-deploy.md#configure-git) for the options, including per-session minted credentials. With the [Anthropic git proxy](self-hosted-environments-deploy.md#use-the-anthropic-git-proxy), git traffic for repositories on github.com goes through `api.anthropic.com` instead.
- **Session child**: the child Claude Code process holds the session’s event stream to `api.anthropic.com`, and makes its own outbound calls for model inference and for git commands run during the session. In a session that uses [Anthropic-managed git](self-hosted-environments-deploy.md#use-the-anthropic-git-proxy), the child sends its `git` and `gh` traffic for github.com over a WebSocket connection it opens to `api.anthropic.com`.
- **SCM connector**: the optional orchestrator [SCM connector](self-hosted-environments-reference.md#scm-connector-flags) isn’t available, so its tunnel doesn’t open. The tunnel is a WebSocket connection to `api.anthropic.com`.

See [Network requirements](self-hosted-environments-deploy.md#network-requirements) for the full egress list. The [diagram above](#how-self-hosted-environments-work) shows these paths, apart from the optional SCM connector and the Anthropic-managed git connection. By default, model inference uses the Anthropic API. The control plane delivers the API endpoint to each session, and the session authenticates with an Anthropic-issued, session-scoped OAuth token. To send model requests to your own cloud account instead, see [Send model requests to Bedrock or Agent Platform](self-hosted-environments-configuration.md#send-model-requests-to-bedrock-or-agent-platform). Corporate egress proxies are supported. The runner and the optional [autoscaling orchestrator](self-hosted-environments-configuration.md#on-demand-runners) honor the proxy and mTLS environment variables described in [Network configuration](network-config.md), such as `HTTPS_PROXY` and `NO_PROXY`; set them in each process’s environment. The variables cover control-plane calls, the orchestrator’s [SCM connector](self-hosted-environments-reference.md#scm-connector-flags) WebSocket, and the built-in clone for HTTPS remotes, and sessions inherit them from the runner. Session streaming uses server-sent events over HTTPS, so a proxy in the path must not buffer responses. If your proxy also requires a `Proxy-Authorization` header, the runner can add it to each connection it opens to the proxy; see [Authenticate to an egress proxy](self-hosted-environments-deploy.md#authenticate-to-an-egress-proxy).


[​](#what-stays-on-your-infrastructure)

What stays on your infrastructure

Repository checkouts, build artifacts, secrets, and any files a session creates or modifies stay on the machines you provision. The conversation itself, including prompts, responses, and tool results, goes to `api.anthropic.com`, and Anthropic stores the session transcript so you can resume the session from another [supported surface](#availability-and-limitations). When a runner [sends model requests to Amazon Bedrock or Google Cloud’s Agent Platform](self-hosted-environments-configuration.md#send-model-requests-to-bedrock-or-agent-platform), the conversation still goes to `api.anthropic.com` in the session’s event stream. A self-hosted environment moves session execution into your network. The control plane remains Anthropic-hosted: session orchestration, queueing, and the claude.ai interface continue to run on Anthropic’s infrastructure.


[​](#get-started)

Get started

The self-hosted environments pages are organized by what you’re doing:

- [Quickstart](self-hosted-environments-quickstart.md): install Claude Code, create an environment, start a runner, and route your first session
- [Deploy to production](self-hosted-environments-deploy.md): security hardening, network egress, git credentials, Kubernetes and Compose recipes, known issues, and troubleshooting
- [Customize sessions](self-hosted-environments-configuration.md): wrapper scripts for per-session credentials, lifecycle hooks, on-demand runners, MCP servers, and permissions
- [Test end to end](self-hosted-environments-testing.md): a CI smoke test that verifies a runner image before you promote it
- [Reference](self-hosted-environments-reference.md): every CLI flag, environment variable, metric, and the health endpoint
- [Verify session identity](self-hosted-environments-identity.md): validate the session token from your own services before granting access

---

## Configure server-managed settings - Claude Code Docs

Source: https://code.claude.com/docs/en/server-managed-settings
Path: 13-Enterprise-Admin/server-managed-settings.md

## On this page

- [Requirements](#requirements)
- [Choose between server-managed and endpoint-managed settings](#choose-between-server-managed-and-endpoint-managed-settings)
- [Configure server-managed settings](#configure-server-managed-settings)
  - [Verify settings delivery](#verify-settings-delivery)
  - [Access control](#access-control)
  - [Managed-only settings](#managed-only-settings)
  - [Current limitations](#current-limitations)
- [Settings delivery](#settings-delivery)
  - [Settings precedence](#settings-precedence)
  - [Per-key exceptions across managed sources](#per-key-exceptions-across-managed-sources)
  - [Fetch and caching behavior](#fetch-and-caching-behavior)
  - [Invalid entries in delivered settings](#invalid-entries-in-delivered-settings)
  - [Enforce fail-closed startup](#enforce-fail-closed-startup)
  - [Security approval dialogs](#security-approval-dialogs)
  - [Approval memory](#approval-memory)
  - [Environment variables and the approval dialog](#environment-variables-and-the-approval-dialog)
- [Platform availability](#platform-availability)
- [Audit logging](#audit-logging)
- [Security considerations](#security-considerations)
- [See also](#see-also)

Setup and access

# Configure server-managed settings

Copy pageCopy page

Centrally configure Claude Code for your organization through server-delivered settings, without requiring device management infrastructure.

Copy pageCopy page

Server-managed settings let organization Owners centrally configure Claude Code from [**Organization settings \> Claude Code \> Managed settings**](https://claude.ai/admin-settings/claude-code) in the claude.ai console. Claude Code clients fetch these settings automatically when users authenticate with an eligible credential on a platform where server-managed delivery is supported. See [Platform availability](#platform-availability) for the credentials and platforms that qualify.

Server-managed settings are available for [Claude for Teams](../17-Billing-Plans/pricing.md#team-&-enterprise) and [Claude for Enterprise](https://anthropic.com/contact-sales?utm_source=claude_code&utm_medium=docs&utm_content=server_settings_enterprise) customers.


[​](#requirements)

Requirements

To use server-managed settings, you need:

- Claude for Teams or Claude for Enterprise plan
- The Owner or Primary Owner role in your Claude organization, to view and edit the configuration
- Network access to `api.anthropic.com`


[​](#choose-between-server-managed-and-endpoint-managed-settings)

Choose between server-managed and endpoint-managed settings

Claude Code supports two approaches for centralized configuration. Server-managed settings deliver configuration from Anthropic’s servers. [Endpoint-managed settings](managed-settings.md#delivery-mechanisms) are deployed directly to devices through native OS policies (macOS managed preferences, Windows registry) or managed settings files.

| Approach                                                                       | Best for                                                 | Security model                                                                                                |
|:-------------------------------------------------------------------------------|:---------------------------------------------------------|:--------------------------------------------------------------------------------------------------------------|
| **Server-managed settings**                                                    | Organizations without MDM, or users on unmanaged devices | Settings that Claude Code fetches from Anthropic’s servers at startup and refreshes hourly during the session |
| **[Endpoint-managed settings](managed-settings.md#delivery-mechanisms)** | Organizations with MDM or endpoint management            | Settings deployed to devices via MDM configuration profiles, registry policies, or managed settings files     |

If your devices are enrolled in an MDM or endpoint management solution, endpoint-managed settings provide stronger security guarantees because the settings file can be protected from user modification at the OS level. Endpoint-managed settings don’t reach [cloud sessions](../02-Claude-Code-CLI/model-config.md#surface-coverage) in Anthropic-hosted environments, so organizations whose developers run cloud sessions should configure server-managed settings as well. Sessions in a [self-hosted environment](self-hosted-environments.md) also read the managed settings file in the runner image. The [settings precedence](#settings-precedence) below says when that file applies.


[​](#configure-server-managed-settings)

Configure server-managed settings

1

Open the admin console

In the claude.ai console, go to [**Organization settings \> Claude Code \> Managed settings**](https://claude.ai/admin-settings/claude-code).In a Team or Enterprise organization, if the page says you don’t have access, ask an [Owner or Primary Owner](#access-control) to make the change.

2

Define your settings

Add your configuration as JSON. All [settings available in `settings.json`](../02-Claude-Code-CLI/settings-reference.md#all-settings) are supported except those restricted to OS-level policy delivery; see [Current limitations](#current-limitations) for that short list. This includes [hooks](../07-Hooks/hooks.md), [environment variables](../02-Claude-Code-CLI/env-vars.md), and [managed-only settings](managed-settings.md#managed-only-settings) like `allowManagedPermissionRulesOnly`.This example enforces a permission deny list, prevents users from bypassing permissions, and restricts permission rules to those defined in managed settings. The `Bash(curl *)` rule matches `curl` [as Claude writes it](../02-Claude-Code-CLI/permissions.md#bash-rule-limits), not `/usr/bin/curl` or `sh -c 'curl …'`; for network enforcement that doesn’t depend on the command text, add a [`sandbox` block with `allowManagedDomainsOnly`](../02-Claude-Code-CLI/sandboxing.md#configure-the-sandbox-for-your-organization).

```python
{
  "permissions": {
    "deny": [
      "Bash(curl *)",
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)"
    ],
    "disableBypassPermissionsMode": "disable"
  },
  "allowManagedPermissionRulesOnly": true
}
```

Hooks use the same format as in `settings.json`.This example runs an audit script after every file edit across the organization:

```python
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "/usr/local/bin/audit-edit.sh" }
        ]
      }
    ]
  }
}
```

Because hooks execute shell commands, users in interactive sessions see a [security approval dialog](#security-approval-dialogs) before Claude Code applies them.To configure the [auto mode](../02-Claude-Code-CLI/permission-modes.md#eliminate-prompts-with-auto-mode) classifier so it knows which repos, buckets, and domains your organization trusts, deliver an `autoMode` block the same way; see [Configure auto mode](../02-Claude-Code-CLI/auto-mode-config.md) for how the `autoMode` entries affect what the classifier blocks and important warnings about the `environment`, `allow`, `soft_deny`, and `hard_deny` fields.

3

Save and deploy

Save your changes. Claude Code clients receive the updated settings on their next startup or hourly polling cycle.The editor checks your JSON against the published JSON schema for Claude Code settings. If it finds a problem in JSON that parses, it shows a warning and relabels the save button. The label is **Update with errors** when settings are already saved, and **Add with errors** when no settings are saved yet. That button still saves, because a schema warning doesn’t block the save.The schema [can lag behind the newest releases](../02-Claude-Code-CLI/settings.md#edit-a-settings-file), so the editor can flag a key or value that the [settings reference](../02-Claude-Code-CLI/settings-reference.md#all-settings) documents. Claude Code receives the keys and values you saved and runs [its own validation](#invalid-entries-in-delivered-settings) when it loads them.


[​](#verify-settings-delivery)

Verify settings delivery

To confirm that settings are being applied, ask a user to restart Claude Code. If the configuration includes settings that trigger the [security approval dialog](#security-approval-dialogs), the user sees a prompt describing the managed settings the next time Claude Code fetches them: at the next start, or within an hour in a running interactive session. You can also verify that managed permission rules are active by having a user run `/permissions` to view their effective permission rules. To check the fetch outcome on a specific machine, have the user run `claude doctor` and read the `Managed settings (remote)` line. Requires Claude Code v2.1.248 or later. The line reports one of four outcomes:

- The delivered settings loaded
- Your organization has no server-managed settings configured
- The fetch failed, with the cause and whether a cached policy still applies
- Claude Code skipped the fetch, with the reason. See [Platform availability](#platform-availability) for the providers and configurations that skip it

While the fetch is still in progress, the line reports that instead. In a running session, `/status` shows the same line after a failed fetch, and for some skipped-fetch causes, such as a third-party provider variable or a custom `ANTHROPIC_BASE_URL` exported in the user’s shell.


[​](#access-control)

Access control

The following roles can manage server-managed settings:

- **Primary Owner**
- **Owner**

Restrict access to trusted personnel, as settings changes apply to all users in the organization.


[​](#managed-only-settings)

Managed-only settings

Most [settings keys](../02-Claude-Code-CLI/settings-reference.md#all-settings) work in any scope. A handful of keys are only read from managed settings and have no effect when placed in user or project settings files. See [managed-only settings](managed-settings.md#managed-only-settings) for the permission and plugin controls, or read the Scope column of the [All settings](../02-Claude-Code-CLI/settings-reference.md#all-settings) index for the full set.


[​](#current-limitations)

Current limitations

Server-managed settings have the following limitations:

- Settings apply uniformly to all users in the organization. Per-group configurations are not yet supported.
- You can’t distribute a [`managed-mcp.json`](../06-MCP-Tools/General/managed-mcp.md) file through server-managed settings. Deliver the `allowedMcpServers` and `deniedMcpServers` policy keys there instead. On Claude Code v2.1.259 or later, you can also provide remote servers with [`managedMcpServers`](../06-MCP-Tools/General/managed-mcp.md#provide-servers-through-managed-settings), which accepts `http` and `sse` servers only and doesn’t take exclusive control the way the file does. Claude Code reads a `managed-mcp.json` deployed at its [system path](../06-MCP-Tools/General/managed-mcp.md#exclusive-control-with-managed-mcp-json) separately from the managed settings tier, so the file still applies when server-managed settings are in effect.
- Settings restricted to OS-level policy sources, such as `policyHelper` and `wslInheritsWindowsSettings`, aren’t honored. Deploy them through MDM or a system `managed-settings.json` file instead. A `policyHelper` deployed that way runs only when its source is the one selected under [precedence within the managed tier](managed-settings.md#precedence-within-the-managed-tier).


[​](#settings-delivery)

Settings delivery


[​](#settings-precedence)

Settings precedence

Server-managed settings and [endpoint-managed settings](managed-settings.md#delivery-mechanisms) both occupy the highest tier in the Claude Code [settings hierarchy](../02-Claude-Code-CLI/settings.md#settings-precedence). A key you set here takes precedence over the same key in a user’s own settings files or in a `--settings` value, apart from the [exceptions to managed settings precedence](../02-Claude-Code-CLI/settings.md#exceptions-to-managed-settings-precedence). Within the managed tier, Claude Code by default uses the first source that delivers at least one policy key, checking server-managed settings first and then endpoint-managed settings, apart from the [exception keys covered next](#per-key-exceptions-across-managed-sources). [How Claude Code combines managed sources](managed-settings.md#precedence-within-the-managed-tier) has the full ranking, the carve-out for the control keys, and the opt-in that applies every source. If the selected source is an MDM policy or managed settings file whose [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) supplies managed settings, the helper’s output replaces that source as the only managed configuration for the run. Claude Code doesn’t consult a `policyHelper` configured in MDM or file-based settings while server-managed settings deliver a policy key. If a later fetch finds the server-managed settings removed, Claude Code runs that helper right away instead of at the next launch. The [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) entry covers what happens when that run fails. If you clear your server-managed configuration in the admin console with the intent of falling back to an endpoint-managed plist or registry policy, be aware that [cached settings](#fetch-and-caching-behavior) persist on client machines until the next successful fetch, and the keys that [apply only at the next launch](#fetch-and-caching-behavior), such as `model`, stay in effect until each client relaunches. Run `/status` to see which managed source is active.


[​](#per-key-exceptions-across-managed-sources)

Per-key exceptions across managed sources

These keys are exceptions to the no-merge rule:

- **Cross-source lock keys**: a small set of keys, such as the sandbox allowlist locks, [listed on the managed settings page](managed-settings.md#precedence-within-the-managed-tier). Claude Code honors them when any admin-controlled managed source sets them; the user-writable HKCU registry tier is excluded. When a [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) supplies managed settings, its output is the only source these checks read, apart from [`forceRemoteSettingsRefresh`](../02-Claude-Code-CLI/settings-reference.md#forceremotesettingsrefresh), which Claude Code reads from the admin sources directly at startup.
- **The `env` block**: apart from the telemetry unit and routing variables paired with a credential key, both covered below, it merges per key across the admin-controlled sources. For each environment variable, the highest-priority source defining it wins, and lower admin sources fill in variables the higher sources leave unset. An endpoint-managed `env` entry therefore applies whenever the server-managed configuration leaves that variable unset, or while a cached server value for it is [withheld pending server confirmation](#fetch-and-caching-behavior). Requires Claude Code v2.1.223 or later. Before v2.1.223, Claude Code applies the selected source’s whole `env` block only.
  - **Telemetry unit**: the `OTEL_EXPORTER_OTLP_*` exporter keys, the `OTEL_LOG_*` content-capture toggles, `OTEL_LOGS_EXPORTER`, and the beta tracing variables `ENABLE_BETA_TRACING_DETAILED` and `BETA_TRACING_ENDPOINT` follow the highest source that sets any of them as a unit. A source that delivers the `otelHeadersHelper` credential key claims the unit too, but lands these variables only when it is the selected source: a source that isn’t selected but delivers the key contributes none of them and still blocks lower sources from filling them in. Either way, an exporter endpoint from one source can never pair with credentials from another.
  - **Credential-paired routing**: a source that pairs routing variables with a selected-source-only credential key, such as `apiKeyHelper` or `otelHeadersHelper`, contributes those routing variables only when it wins the slot.
- **`allowedProviders`**: a list set on the machine and a server-managed list combine as [its entry’s Scope note](../02-Claude-Code-CLI/settings-reference.md#allowedproviders) states. Requires Claude Code v2.1.285 or later
- **Gateway sign-in keys**: Claude Code never reads [`forceLoginGatewayUrl`](../02-Claude-Code-CLI/settings-reference.md#forcelogingatewayurl), [`gatewayInternalNetworks`](../02-Claude-Code-CLI/settings-reference.md#gatewayinternalnetworks), or the `"gateway"` value of [`forceLoginMethod`](../02-Claude-Code-CLI/settings-reference.md#forceloginmethod) from server-managed settings, so a value there neither applies nor hides one set in an MDM policy or managed settings file. The [`managedSourcesBehavior` entry](../02-Claude-Code-CLI/settings-reference.md#managedsourcesbehavior) says which admin source on the machine supplies them.


[​](#fetch-and-caching-behavior)

Fetch and caching behavior

Claude Code fetches settings from Anthropic’s servers at startup and polls for updates hourly during active sessions. A client signed in through a [Claude apps gateway](#platform-availability) fetches its settings from the gateway and waits for that fetch before the session starts, so the fetch in the lists below doesn’t apply to it. [Enforce fail-closed startup](#enforce-fail-closed-startup) covers what happens when that fetch fails. **First launch without cached settings:**

- When a developer signs in at startup, such as on a first run or after `/logout`, Claude Code waits up to five seconds for the fetch before it opens the session. When the policy arrives in time, Claude Code enforces it from the first screen and shows your [`companyAnnouncements`](../02-Claude-Code-CLI/settings-reference.md#companyannouncements) on it. When the payload needs [security approval](#security-approval-dialogs), Claude Code ends the wait and applies the payload once the developer approves
- In any other startup, and when that five-second wait runs out, Claude Code opens the session while the fetch continues, so a brief window passes before the settings load and the restrictions take effect
- If the fetch fails, Claude Code continues without server-managed settings and warns in interactive sessions that no remote policy applies; endpoint-managed settings still apply. If a managed source sets [`forceRemoteSettingsRefresh`](#enforce-fail-closed-startup), Claude Code exits instead

**Subsequent launches with cached settings:**

- Cached settings apply immediately at startup, except for the cached `modelPricing` and `managedMcpServers` values and the environment variables that Claude Code withholds until the server confirms the payload
- A cached [`modelPricing`](../02-Claude-Code-CLI/settings-reference.md#modelpricing) doesn’t apply until the session’s fetch confirms the payload. Until then, the cost figures developers see in `/usage` and the status line are at list price
- A cached [`managedMcpServers`](../02-Claude-Code-CLI/settings-reference.md#managedmcpservers) block doesn’t apply until the session’s fetch confirms the payload. Claude Code waits up to 30 seconds for that fetch before connecting MCP servers. If the fetch fails or times out, the session starts without the organization’s servers, `/status` says so, and they connect once a later fetch confirms them. See [When provided servers connect](../06-MCP-Tools/General/managed-mcp.md#when-provided-servers-connect) for the full behavior, including first launch. Requires Claude Code v2.1.259 or later
- Claude Code fetches fresh settings in the background
- Cached settings persist through network failures. If the startup fetch fails, Claude Code warns in interactive sessions that the cached policy is in effect
- Until a fetch succeeds, the values withheld at startup stay withheld

Claude Code withholds several categories of variables in the cached `env` block until the server confirms the payload for the session. This keeps a cached proxy, certificate authority, endpoint, or credential value from redirecting, intercepting, or re-authenticating the settings fetch that confirms the payload. The hardening applies only to the server-fetched settings cache: [endpoint-managed settings](managed-settings.md#delivery-mechanisms) deployed through MDM or `managed-settings.json` are unaffected. The withheld categories include:

- Proxy and TLS configuration, such as `HTTPS_PROXY`, `NODE_EXTRA_CA_CERTS`, and the mTLS client certificate variables `CLAUDE_CODE_CLIENT_CERT` and `CLAUDE_CODE_CLIENT_KEY`
- API routing and provider selection, including `ANTHROPIC_BASE_URL`, the provider selection variables such as `CLAUDE_CODE_USE_BEDROCK` and `CLAUDE_CODE_USE_VERTEX`, and the provider endpoint URLs such as `ANTHROPIC_BEDROCK_BASE_URL`
- Authentication credentials, such as `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and `CLAUDE_CODE_OAUTH_TOKEN`
- The configuration-directory selector `CLAUDE_CONFIG_DIR`
- Credential-source and configuration-directory selectors, in Claude Code v2.1.223 or later: the Workload Identity Federation variables such as `ANTHROPIC_FEDERATION_RULE_ID` and `ANTHROPIC_IDENTITY_TOKEN`, the profile and configuration-directory selectors `ANTHROPIC_PROFILE` and `ANTHROPIC_CONFIG_DIR`, and the operating-system directory variables `HOME`, `XDG_CONFIG_HOME`, `APPDATA`, and `USERPROFILE`

Claude Code reads the Workload Identity Federation variables and the `ANTHROPIC_PROFILE` and `ANTHROPIC_CONFIG_DIR` selectors only at startup, so a server-delivered value for them doesn’t switch the session’s credential source even after the fetch succeeds. To deliver those selectors on Claude Code v2.1.223 or later, use [endpoint-managed settings](managed-settings.md#delivery-mechanisms) such as MDM or `managed-settings.json`. For `CLAUDE_CONFIG_DIR` and the operating-system directory variables, the withholding itself is the protection: the cached value stays out of the environment until the server confirms the payload. Every other key in the cached `env` block applies at startup. Once the server confirms the payload, and you approve it if it needs [security approval](#security-approval-dialogs), the withheld variables apply for the rest of the session. If your organization needs a proxy to reach `api.anthropic.com`, the withholding only affects the server-delivered `env` block itself: a proxy set in an [endpoint-managed](managed-settings.md#delivery-mechanisms) `env` block through MDM or `managed-settings.json`, in the shell environment, or in [user settings](../02-Claude-Code-CLI/settings.md#where-settings-live) reaches the settings fetch. The endpoint-managed source requires Claude Code v2.1.223 or later: the cached server-managed proxy value is withheld until the fetch confirms it, so the endpoint-managed value fills in per key and reaches the fetch itself. Before v2.1.223, use the shell environment or user settings so the proxy applies alongside a cached server payload. The first launch has no cache, so an endpoint-managed source, the shell environment, or user settings is still required for the initial fetch. Claude Code applies most settings updates to running sessions without a restart. Some updates apply only at the next launch, including OpenTelemetry exporter configuration, the `model` key, and the removal of a variable from the `env` block.


[​](#invalid-entries-in-delivered-settings)

Invalid entries in delivered settings

When part of a payload fails schema validation, Claude Code surfaces a validation error and applies every remaining valid setting; [Invalid entries in managed settings](managed-settings.md#invalid-entries-in-managed-settings) says what it drops and which keys fall back to a stricter value. Requires Claude Code v2.1.169 or later. Server-managed delivery adds these behaviors:

- A startup that runs on the cache at `~/.claude/remote-settings.json` treats invalid entries the way the fetch that wrote the cache did:
  - Entries that failed validation stay dropped.
  - [Keys that fail closed](managed-settings.md#keys-that-fail-closed) keep their stricter values.
  - An invalid `cleanupPeriodDays` or `desktopSessionCleanupPeriodDays` value stays in the cached copy and is never applied.
- Claude Code applies nothing from a payload and leaves the cache unchanged when all three of these are true:
  - Every setting in the payload fails validation.
  - None of them falls back to a stricter value.
  - The payload holds a key other than those two retention keys.

  The startup notice, `/status`, and `claude doctor` then report the [failed load](../02-Claude-Code-CLI/errors.md#remote-managed-settings-failed-to-load) with the cause `no setting in the server response could be applied as written`, and that entry says which policy the session runs on. Clients that [enforce fail-closed startup](#enforce-fail-closed-startup) exit at startup instead.
- The [security approval dialog](#security-approval-dialogs) evaluates the salvaged payload, so a stripped invalid entry is never presented for approval and never executes.

To debug delivery issues, run `claude --debug-file <path>` and search the log for `Remote settings`. Validate a payload change with `claude doctor` on a test machine before rolling it out to the organization.


[​](#enforce-fail-closed-startup)

Enforce fail-closed startup

By default, if the remote settings fetch fails at startup, the CLI continues with the settings cached from the last successful fetch, except for the [values Claude Code withholds](#fetch-and-caching-behavior) until a fetch succeeds. On a machine that has never fetched them, the CLI continues without server-managed settings and still applies any [endpoint-managed settings](managed-settings.md#delivery-mechanisms) on the device. To stop clients from starting on cached or absent server-managed settings, set `forceRemoteSettingsRefresh: true` in your managed settings. Clients signed in through a [Claude apps gateway](#platform-availability) wait for the startup fetch whether or not you set this, and handle a failed fetch as follows:

- If the gateway answers an attended interactive launch with a `401` and this setting is off, the gateway has ended that sign-in. Claude Code prints [`Cloud gateway session expired — run /login to reconnect.`](../02-Claude-Code-CLI/errors.md#cloud-gateway-session-expired) and opens the session signed out of the gateway until the user runs `/login`.
- When the fetch fails in any other way, or in any other kind of launch except a `claude auth` subcommand, the client exits with an error.

When this setting is active in a session that fetches server-managed settings, the CLI blocks at startup until remote settings are freshly fetched. If the fetch fails, the CLI exits rather than proceeding without the policy. This setting self-perpetuates: once delivered from the server, it is also cached locally so that subsequent startups enforce the same behavior even before the first successful fetch of a new session. A session that [doesn’t fetch server-managed settings](#platform-availability) starts without waiting. To enable this, add the key to your managed settings configuration:

```python
{
  "forceRemoteSettingsRefresh": true
}
```

You can also set this key in an [endpoint-managed](managed-settings.md#delivery-mechanisms) MDM profile or system `managed-settings.json` file to enforce fail-closed behavior on first launch, before any server payload has arrived. This flag is an exception to the [precedence rule](#settings-precedence) above: Claude Code honors it when any admin-controlled managed source sets it, even if a cached server-managed payload is also present, so it doesn’t ignore an MDM-delivered value when server-managed settings exist. When a [`policyHelper`](../02-Claude-Code-CLI/settings-reference.md#policyhelper) supplies managed settings, its output replaces every other managed source for the keys Claude Code reads after startup. For the sources Claude Code reads this key from, see [its settings entry](../02-Claude-Code-CLI/settings-reference.md#forceremotesettingsrefresh). The `policyHelper` entry says which sources Claude Code reads the helper from and when it runs. The settings fetch also sends a `Cache-Control: no-cache` header so intermediate HTTP proxies don’t serve a stale response. Before enabling this setting, ensure your network policies allow connectivity to `api.anthropic.com`. If that endpoint is unreachable, the CLI exits at startup and users cannot start Claude Code. The `claude auth` subcommands such as `claude auth login` are exempt from this check and from the gateway startup exit, so users can re-authenticate when expired credentials are the reason the settings fetch fails.


[​](#security-approval-dialogs)

Security approval dialogs

Certain settings that could pose security risks require explicit user approval before Claude Code applies them in an interactive session:

- **Shell command settings**: settings that execute shell commands, such as `apiKeyHelper`, `statusLine`, and `otelHeadersHelper`
- **Sandbox binary settings**: `sandbox.bwrapPath`, `sandbox.socatPath`, and `sandbox.ripgrep`. Each of these settings points at an executable, and Claude Code runs that executable
- **Sandbox network and isolation settings**: [sandbox](../02-Claude-Code-CLI/sandboxing.md) settings that let the sandbox proxy read, reroute, or authenticate traffic, or that weaken the sandbox’s isolation: `sandbox.network.tlsTerminate`, `sandbox.network.httpProxyPort`, `sandbox.network.socksProxyPort`, `sandbox.credentials`, `sandbox.allowAppleEvents`, `sandbox.allowNestedUserNamespaces`, `sandbox.enableWeakerNestedSandbox`, `sandbox.enableWeakerNetworkIsolation`, `sandbox.filesystem.disabled`, `sandbox.network.allowAllUnixSockets`, `sandbox.network.allowUnixSockets`, and `sandbox.network.allowMachLookup`. A `sandbox.credentials` block that contains only `deny` rules doesn’t need approval, since it restricts the sandbox without giving the proxy a credential. Before v2.1.251, Claude Code applied these settings without approval
- **Custom environment variables**: delivered `env` variables that require the user’s approval, such as proxy and base-URL variables; see [Environment variables and the approval dialog](#environment-variables-and-the-approval-dialog)
- **Hook configurations**: any hook definition

When these settings are present, users see a security dialog explaining what is being configured. Users must approve to proceed. If a user rejects the settings, Claude Code exits. A managed CLAUDE.md delivered through the [`claudeMd`](../02-Claude-Code-CLI/settings-reference.md#claudemd) key doesn’t require approval, because it’s instruction text for Claude rather than a command Claude Code runs. Claude Code still checks [permissions](../02-Claude-Code-CLI/permissions.md) for the tools Claude uses while following those instructions. Before v2.1.260, a `claudeMd` value required approval too.


[​](#approval-memory)

Approval memory

Claude Code records your approval in your configuration directory, `~/.claude` unless you set [`CLAUDE_CONFIG_DIR`](../02-Claude-Code-CLI/env-vars.md). What it records depends on the credential the settings fetch uses:

- **A claude.ai login saved by `/login` or `claude auth login`, or the [keyless Console sign-in](../02-Claude-Code-CLI/authentication.md#sign-in-without-an-api-key)**: one approval per organization, held by the account that approved most recently.
- **A [Claude apps gateway](claude-apps-gateway.md) sign-in**: one approval per gateway. If you sign out and back in to the same gateway, Claude Code doesn’t show the dialog again while the settings that require approval are unchanged. Claude Code shows it again when those settings change, when you sign in to a different gateway, and when you accept a new certificate for the same gateway. Claude Code saves no approval for a loopback development gateway reached over plain HTTP, so the dialog appears again after each sign-in.
- **Any other credential**, such as an API key or `CLAUDE_CODE_OAUTH_TOKEN`: one approval for the delivered settings, kept with the cached copy of the settings in that configuration directory. Claude Code shows the dialog again when the settings that require approval change, and after you run `/logout` or `claude auth logout`, either of which deletes the cached copy.

An approval for `sandbox.credentials` or `sandbox.network.tlsTerminate` also covers the [`sandbox.network.allowedDomains`](../02-Claude-Code-CLI/settings-reference.md#sandbox-network-alloweddomains) entries in those same delivered settings, because both settings act on that allowlist. The dialog appears again when your administrator adds or removes one of those entries, even though `sandbox.network.allowedDomains` doesn’t require approval on its own. With a saved claude.ai login:

- If you sign out and back in, or switch to another organization and later return, Claude Code doesn’t show the dialog again while those settings are unchanged, unless another account approved them for that organization in the same configuration directory in between.
- If you sign in to the same organization with a different account, Claude Code shows the dialog again even when the settings are unchanged. That account’s approval replaces the previous one, so when you switch back, Claude Code shows the dialog once more.

Claude Code can’t always show the dialog. Each case below says which settings apply when it can’t and when you next see the dialog:

- **An interactive session that can’t show the dialog**: Claude Code doesn’t apply the delivered settings and keeps the last-approved settings. The dialog appears in the next session that can show it.
- **`claude install` or `claude update`**: Claude Code doesn’t show the dialog during either command. The command runs with the last-approved settings, and the dialog appears in your next interactive session. If Claude Code waits for the settings fetch at startup, such as with [`forceRemoteSettingsRefresh`](#enforce-fail-closed-startup) set or on a [Claude apps gateway](claude-apps-gateway.md) deployment, it shows the dialog during the command instead, and an install run from a pipe fails; see [`Raw mode is not supported` during install](../02-Claude-Code-CLI/troubleshoot-install.md#raw-mode-is-not-supported-during-install). Before v2.1.246, Claude Code tried to show the dialog during these commands too.
- **A `claude plugin` command that prints to a terminal but can’t read from the keyboard**: when Claude Code doesn’t wait for the settings fetch at startup, the command doesn’t apply the delivered settings. Without [cached settings](#fetch-and-caching-behavior), a command that changes your plugins or marketplaces [stops with an error](../02-Claude-Code-CLI/errors.md#managed-settings-need-your-approval). With cached settings, it runs with the last-approved settings. The dialog appears when you next run `claude` in a terminal. The command can’t read from the keyboard when you pipe or redirect its input and, on macOS and Linux, it also can’t open `/dev/tty`. If you pipe or redirect its output too, the run is non-interactive instead, and the delivered settings apply for that run only.
- **An error closes the dialog before you answer**: Claude Code doesn’t apply the delivered settings and keeps the last-approved settings. It shows the dialog again in the next session that can show it.
- **A non-interactive run**, such as `claude -p`, an Agent SDK session, or a session in the VS Code extension’s chat panel or the desktop app’s Code tab: Claude Code can’t show the dialog, so when the delivered settings would require approval, it applies them for that run only. It doesn’t record them as approved or write them to the [local cache](#fetch-and-caching-behavior), and the next interactive session shows the dialog. Until a user approves in an interactive session, each non-interactive run fetches the settings again at startup. Before v2.1.207, a non-interactive run saved the settings as approved, so later interactive sessions never showed the dialog for them.


[​](#environment-variables-and-the-approval-dialog)

Environment variables and the approval dialog

Claude Code applies some delivered `env` variables without showing the user the approval dialog, including:

- Feature and command toggles
- Model selection and behavior settings, such as `ANTHROPIC_MODEL`, `DISABLE_PROMPT_CACHING`, and `CLAUDE_CODE_EFFORT_LEVEL`
- Context window and compaction settings, such as `DISABLE_AUTO_COMPACT`, but not `CLAUDE_CODE_DISABLE_1M_CONTEXT`
- Terminal UI and accessibility options
- Numeric limits, budgets, and timeouts

Other delivered variables can require the user’s approval before they take effect. For example, in interactive sessions these always do, at any non-empty value:

- A proxy or base-URL variable
- `OTEL_EXPORTER_OTLP_ENDPOINT`
- `CLAUDE_CODE_DISABLE_1M_CONTEXT`

When a delivered variable needs approval, the dialog names it, so the user sees exactly what the policy is asking to set. Before v2.1.218, Claude Code applied fewer variables without asking the user, so settings such as `DISABLE_AUTO_COMPACT` triggered the dialog at any non-empty value. Claude Code decides whether four privacy toggles need approval by the delivered value rather than by the variable name: `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC`, `DISABLE_ERROR_REPORTING`, `DISABLE_TELEMETRY`, and `DO_NOT_TRACK`. A truthy value such as `1` or `true` only turns tracking, reporting, or other nonessential traffic off, so Claude Code applies it without asking the user. For any other non-empty value, Claude Code shows the dialog. Before v2.1.218, all of them except `DO_NOT_TRACK` applied without approval at any value, and `DO_NOT_TRACK` triggered the dialog at any non-empty value. Claude Code also decides whether [`API_FORCE_IDLE_TIMEOUT`](../02-Claude-Code-CLI/env-vars.md) needs approval by the delivered value: a truthy value only turns the [body idle timeout](network-config.md#streaming-idle-watchdogs) on, so Claude Code applies it without asking the user. For any other non-empty value, Claude Code shows the dialog. Before v2.1.248, any non-empty value triggered the dialog. Whether [`ANTHROPIC_CUSTOM_HEADERS`](../02-Claude-Code-CLI/env-vars.md#variables) needs approval also depends on the delivered value. Headers that only tag requests, such as `Accept-Language`, apply without the dialog. A line that names a credential, an org or tenant selector, a routing or host override, or an API-behavior header, such as `Authorization`, `X-Api-Key`, `Host`, `anthropic-beta`, or the `X-Amzn-Bedrock-*` headers, requires approval. A line also requires approval when its name isn’t a valid HTTP header token or its value contains a character an HTTP header can’t carry. The check matches words inside the header name, so `X-Client-Version`, which contains `client` and `version`, requires approval too. Before v2.1.251, any `ANTHROPIC_CUSTOM_HEADERS` value applied without it. A falsy value such as `0` or `false` for [`ENABLE_BETA_TRACING_DETAILED`](../02-Claude-Code-CLI/env-vars.md#variables) or [`OTEL_LOG_RAW_API_BODIES`](../02-Claude-Code-CLI/env-vars.md#variables) applies without the dialog, because it only turns detailed tracing or raw API body capture off. Any other non-empty value for either variable requires approval.


[​](#platform-availability)

Platform availability

Server-managed settings require a direct connection to `api.anthropic.com`. Delivery also requires the session to authenticate with one of these credentials:

- A Team or Enterprise OAuth login
- An OAuth token supplied through `CLAUDE_CODE_OAUTH_TOKEN`
- A directly configured API key
- A `user_oauth` [Anthropic profile](../02-Claude-Code-CLI/authentication.md#anthropic-profiles-and-federation-credentials), unless the profile sets a `base_url` other than the Anthropic API. Requires Claude Code v2.1.257 or later.

Neither keys returned by an [`apiKeyHelper`](../02-Claude-Code-CLI/settings-reference.md#apikeyhelper) script nor [Workload Identity Federation](../04-API-Reference/Other/manage-claude-workload-identity-federation.md) credentials trigger the settings fetch. A session receives the managed settings of the organization that owns the credential it authenticates with. An API key from the [Claude Console](../04-API-Reference/Other/playground.md) belongs to the Console organization it was created in, which is a separate organization from your claude.ai Team or Enterprise organization. Settings you configure in claude.ai Admin Settings therefore don’t reach a session that authenticates with that key, such as a CI job that uses your company’s Console API key. To apply them to that job, use one of these options. The OAuth token option doesn’t apply to a job that runs with [`--bare`](../02-Claude-Code-CLI/headless.md#start-faster-with-bare-mode), because bare mode doesn’t read `CLAUDE_CODE_OAUTH_TOKEN`.

- **OAuth token**: generate a token with [`claude setup-token`](../02-Claude-Code-CLI/authentication.md#generate-a-long-lived-token), authorize it for your Team or Enterprise organization, and set it as `CLAUDE_CODE_OAUTH_TOKEN` in the job’s environment. Remove any credential that [takes precedence](../02-Claude-Code-CLI/authentication.md#authentication-precedence) over the token from that environment, such as `ANTHROPIC_API_KEY`.
- **Endpoint-managed settings**: deploy a [managed settings file](managed-settings.md#delivery-mechanisms) to the machine that runs the job.

In a [Cowork](https://claude.com/docs/cowork/overview) session in the Claude Desktop app, Claude Code doesn’t fetch server-managed settings from the claude.ai admin console, even when the user signs in with a Team or Enterprise account. [Where and when a policy applies](managed-settings.md#where-and-when-a-policy-applies) covers which policy reaches Cowork sessions on the user’s machine and remote Cowork sessions. claude.ai still applies your [`strictKnownMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#strictknownmarketplaces) and [`blockedMarketplaces`](../02-Claude-Code-CLI/settings-reference.md#blockedmarketplaces) lists itself when a Cowork user adds a marketplace from a git repository on claude.ai or from **Customize** in Cowork. [How restrictions work](../08-Plugins-Skills/plugins-org.md#restrict-what-users-can-install) describes that check. If you export a `CLAUDE_CODE_USE_*` provider variable or a non-default `ANTHROPIC_BASE_URL` in your shell, Claude Code skips the settings fetch for your sessions. [`claude doctor` and `/status` report the skipped fetch and its cause](#verify-settings-delivery). You can’t clear the export with a server-managed `env` block, because the block arrives through the fetch that the export prevents. An [endpoint-managed settings](managed-settings.md#delivery-mechanisms) `env` block doesn’t restore the fetch either: Claude Code checks eligibility before it applies managed `env` blocks, so the endpoint-managed value changes the session’s provider selection but the fetch stays skipped. To restore server-managed delivery, remove the export from your shell, or set the variable to `""` in your user settings `env` block, which applies before the eligibility check. To enforce policy without relying on users to change their shells, deliver the settings through the endpoint-managed channel instead. For Amazon Bedrock, Google Cloud’s Agent Platform, Microsoft Foundry, and [Claude Platform on AWS](../02-Claude-Code-CLI/claude-platform-on-aws.md) deployments, a self-hosted [Claude apps gateway](claude-apps-gateway.md) provides the equivalent remote managed-settings delivery: gateway-signed-in clients fetch managed settings from the gateway instead of `api.anthropic.com`. The failure semantics differ at startup: a gateway client that can’t reach the gateway exits with an error instead of falling back to cached settings, while the hourly background refresh is fail-open on both channels.


[​](#audit-logging)

Audit logging

Audit log events for settings changes are available through the compliance API or audit log export. Contact your Anthropic account team for access. Audit events include the type of action performed, the account and device that performed the action, and references to the previous and new values.


[​](#security-considerations)

Security considerations

Server-managed settings provide centralized policy enforcement, but they operate as a client-side control, not a security boundary. On unmanaged devices, a user doesn’t need admin or sudo access to bypass them.

| Scenario                                                               | Behavior                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
|:-----------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| User edits the cached settings file                                    | Tampered file applies at startup, except for the [values Claude Code withholds](#fetch-and-caching-behavior) until the server confirms the payload. The next server fetch restores the correct settings, except for the [keys that apply only at the next launch](#fetch-and-caching-behavior), such as `model` or a variable added to the `env` block, which stay in effect until relaunch                                                                                                                                                                                                                                                                       |
| User deletes the cached settings file                                  | [First-launch behavior](#fetch-and-caching-behavior) occurs                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
| User runs a modified Claude Code binary                                | A user who can run a modified client can bypass any client-side control                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| User runs an older Claude Code version                                 | Versions that predate server-managed settings don’t fetch or apply them                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| API is unavailable                                                     | Cached settings apply if available, except for the [values Claude Code withholds](#fetch-and-caching-behavior) until a fetch succeeds. Without a cache, Claude Code enforces no server-managed settings until the next successful fetch and still applies any [endpoint-managed settings](managed-settings.md#delivery-mechanisms) on the device. With `forceRemoteSettingsRefresh: true`, the CLI exits instead of continuing, except for [`claude auth` subcommands](#enforce-fail-closed-startup). Clients signed in through a [Claude apps gateway](#platform-availability) exit at startup without that setting, with the same `claude auth` exemption |
| User authenticates with a different organization                       | Settings are not delivered for accounts outside the managed organization, including a session that authenticates with a [Console API key](#platform-availability)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| User configures a [third-party model provider](#platform-availability) | Server-managed settings are bypassed. This includes setting `CLAUDE_CODE_USE_BEDROCK`, `CLAUDE_CODE_USE_MANTLE`, `CLAUDE_CODE_USE_VERTEX`, `CLAUDE_CODE_USE_FOUNDRY`, `CLAUDE_CODE_USE_ANTHROPIC_AWS`, or a non-default `ANTHROPIC_BASE_URL`                                                                                                                                                                                                                                                                                                                                                                                                                      |
| Network traffic is intercepted or redirected                           | Disabled TLS validation or intercepted traffic can alter the settings the client receives                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |

To log edits to local settings files, including `managed-settings.json`, use [`ConfigChange` hooks](../07-Hooks/hooks.md#configchange). Claude Code doesn’t run them when server-managed settings arrive or refresh, or when an MDM profile or registry policy changes, and a hook can’t block a `policy_settings` change. To restrict which organizations your users can access with credentials the client supplies, see [Enforce network-level access control with Tenant Restrictions](enforce-network-level-access-control-with-tenant-restrictions.md) in the Claude Help Center. For stronger enforcement guarantees, use [endpoint-managed settings](managed-settings.md#delivery-mechanisms) on devices enrolled in an MDM solution.


[​](#see-also)

See also

Related pages for managing Claude Code configuration:

- [All settings](../02-Claude-Code-CLI/settings-reference.md): every settings key
- [Endpoint-managed settings](managed-settings.md#delivery-mechanisms): managed settings deployed to devices by IT
- [Authentication](../02-Claude-Code-CLI/authentication.md): set up user access to Claude Code
- [Security](security.md): security safeguards and best practices

---

## Set a default model for your organization | Claude Help Center

Source: https://support.claude.com/en/articles/15330088-set-a-default-model-for-your-organization
Path: 13-Enterprise-Admin/set-a-default-model-for-your-organization.md

# Set a default model for your organization


This guide explains how to choose the Claude model that new conversations start on across your organization. You can set one default for your whole organization, or set different defaults for specific custom roles. You can also make every new conversation start on your default model and effort level, even after users pick a different model.

Default model settings are available for Enterprise plan organizations. Primary Owners, Owners, and members whose custom role grants the Identity & Access permission can manage them in **[Organization settings \> Models](https://claude.ai/admin-settings/models)**.

You can also set the default effort level that new conversations start on, or leave it on Anthropic's recommended default effort. To control which models members can use and cap the effort level they can select, see **[Manage model access for your organization](https://support.claude.com/en/articles/15694740)**.

------------------------------------------------------------------------

## How default models work

When you set or change a default model, it replaces the model currently selected in each member’s model picker. New conversations in chat, Cowork, Claude Code, Claude for Office, Claude Design, and Claude Science then start on the model you’ve chosen.

**Note:** Not all models are available in every product. If the selected model is not available in the product, Anthropic’s recommendation is used as default.

Users can still select a different model for any conversation. By default, Claude remembers each user's last selection, so their next conversation starts on whichever model they last used. When you update the default again, the new default replaces their selection. To have every new conversation start on the default instead, see **[Always start new chats on the default model](#h_bc399beba0)** below.

For example: you set the organization default to Claude Sonnet 4.6, and every member’s new conversations start on Sonnet 4.6. A member switches a conversation to Claude Opus 4.7, so their next conversations start on Opus 4.7. When you later change the organization default, the new default replaces their selection again. If you turn on **Always start with the default model and effort level**, their next chat starts on Sonnet 4.6 no matter which model they used last.

You can set a default at two levels:

- Organization default: applies to every member of your organization.

- Custom role default: applies to members assigned to that role and takes precedence over the organization default.

Alongside the default model, you can set a default effort level. New conversations on the default model start at that effort level. Members can still change the effort level for any conversation, within the organization's effort cap for that model and any cap set by their custom role. If the default effort is higher than a cap that applies to a member, their conversations start at the highest level they're allowed.

**Note:** Members on Claude Code CLI versions earlier than 2.1.199 won't pick up the organization default. Versions 2.1.196 through 2.1.198 also had a bug where setting a specific organization default caused other enabled models to disappear from the model picker in the CLI and VS Code extension; updating to 2.1.199 or later resolves both.

For member-facing CLI instructions, see **[Claude Code model configuration](https://support.claude.com/en/articles/11940350)**.

For member-facing instructions on switching models, see **[Change the model, effort, and thinking settings](https://support.claude.com/en/articles/8664678-change-the-model-effort-and-thinking-settings)**.

------------------------------------------------------------------------

## Set the organization default model

The organization default applies to every member. To set it:

1.  Navigate to **[Organization settings \> Models](https://claude.ai/admin-settings/models)**.

2.  Under **Default model**, select an option under the model dropdown list:

    1.  “Use Anthropic’s recommended default”: Anthropic’s recommended model that updates automatically when new models are released.

    2.  “Choose a specific model”: a specific model that won’t change when new models are released.

3.  If you select “Choose a specific model,” choose a model from the list. Only models enabled under **Model access** on the same page can be selected.

4.  Click “Save changes.”


------------------------------------------------------------------------

## Set the default effort level

The default effort level applies to new conversations on the organization default model. To set it:

1.  Navigate to **[Organization settings \> Models](https://claude.ai/admin-settings/models)**.

2.  Under **Default model**, select an option under the effort level dropdown list:

    1.  "Use Anthropic's recommended default effort": Anthropic's recommended effort level for the default model, which updates automatically when recommendations change.

    2.  Choose a level from the list. Available effort levels differ depending on the model, and some models don't support effort level settings at all.

3.  Click "Save changes."

The default effort level can't be higher than the organization's effort cap for the default model. If you lower that cap below the current default effort, the default effort is lowered to match.

------------------------------------------------------------------------

## Always start new chats on the default model (beta)

By default, each user's next conversation starts on the model they last used. Turn on this setting to have every new conversation start on your organization's default model and default effort level instead.

1.  Navigate to **[Organization settings \> Models](https://claude.ai/admin-settings/models)**.

2.  Turn on **Always start with the default model and effort level**.

3.  Click "Save."

With this setting on:

- Every new conversation starts on the default model at the default effort level.

- Users can still change the model and effort level within a conversation. Their next conversation starts on the default again.

- When the default model changes, new conversations move to the new default. For example, if you use Anthropic's recommended default and the recommendation moves to a newer model, every user's next conversation starts on the newer model.

This setting applies in chat, Cowork, Claude Code, Claude for Office, Claude Design, and Claude Science.

### In projects

Projects keep their own model selection. Claude remembers the model each user last picked in each project, and this setting doesn’t override that choice in existing chat projects. With **Always start with the default model and effort level** turned on:

- New projects start on the default model.

- Existing chat projects start on the model the user last selected in that project.

- New Cowork tasks in a project start on the default model.

### For a custom role

Custom roles that set their own default model have the same switch in the role editor.

1.  Navigate to **[Organization settings \> Roles](https://claude.ai/admin-settings/roles)**.

2.  Click the role you want to edit.

3.  Select the “Models” tab and choose a default model for the role.

4.  Turn on **Always start with the default model and effort level**.

5.  Click "Save role."

------------------------------------------------------------------------

## Set a default model for a custom role

Custom role defaults let you set different starting models for different teams. For example, you can keep most of your organization on the recommended default while a specific group starts on a different model.

1.  Navigate to **[Organization settings \> Roles](https://claude.ai/admin-settings/roles)**.

2.  Click the role you want to edit, or create a new role.

3.  Select the **Models** tab, then under **Default model**, select a model. Roles are set to "Organization default" unless you choose a specific model. Only models the role has access to can be selected.

4.  Click “Save role” to save your changes.

A role’s default model takes precedence over the organization default for members assigned to that role.

If a member belongs to multiple groups whose custom roles set different default models, the most capable model will be the default. Capability is determined first by model family (Haiku, Sonnet, Opus), then release date, so more capable model families take precedence, and newer models within the same family take precedence.

The role's effort cap for the default model, if any, still applies. A role can't set an effort cap higher than the organization's cap for that model. For details, see **[Limit the maximum effort level for a custom role](manage-model-access-for-your-organization.md#h_e693614582)**.

**Note:** Custom roles only affect members whose role is set to “Custom.” Members with the User, Admin, or Owner roles get the default model from the organization setting, not from custom roles.

For details on creating roles and assigning them to groups, see **[Manage custom roles on Enterprise plans](https://support.claude.com/en/articles/13930452)**.

------------------------------------------------------------------------

## Set the default effort level for a custom role

The default effort level applies to new conversations on the role's default model. To set it:

1.  Navigate to **[Organization settings \> Roles](https://claude.ai/admin-settings/roles)**.

2.  Click the role you want to edit, or create a new role.

3.  Select the **Models** tab, then under **Default model**, select a model. Roles are set to "Organization default" unless you choose a specific model. Only models the role has access to can be selected.

4.  Under **Default model**, select an option under the effort level dropdown list:

    1.  **Use Anthropic's recommended default effort:** Anthropic's recommended effort level for the default model, which updates automatically when recommendations change.

    2.  **Choose a level from the list.** Available effort levels differ depending on the model, and some models don't support effort level settings at all.

5.  Click "Save changes."

The default effort level can't be higher than the organization's effort cap for the default model. If you lower that cap below the current default effort, the default effort is lowered to match.

------------------------------------------------------------------------

## Default model and Claude Code managed settings

If your organization also configures Claude Code through `managed-settings.json`, the model setting there takes precedence. When model is set in managed settings, Claude Code CLI and IDE start on that model and ignore the default you set in **[Organization settings \> Models](https://claude.ai/admin-settings/models)**.

If `managed-settings.json` specifies `availableModels` that doesn't contain the default model, Claude CLI bypasses `availableModels` and starts on the selected default model unless `enforceAvailableModels` is set.

Managed settings for models apply only to local Claude Code sessions in your terminal or IDE, not to cloud sessions or to Claude Code in Claude Desktop. For consistent behavior across all Claude Code surfaces, we recommend setting the default here alone. For more on managed settings, see **[Claude Code settings](../02-Claude-Code-CLI/settings.md#settings-files)**.

---

## Set organization instructions | Claude Help Center

Source: https://support.claude.com/en/articles/14546867-set-organization-instructions
Path: 13-Enterprise-Admin/set-organization-instructions.md

# Set organization instructions

August 31, 2026


Organization instructions let owners of Team and Enterprise plans set custom instructions that Claude follows in every conversation across your organization. Use them to apply communication standards, formatting requirements, compliance guidance, or domain-specific context that should show up everywhere your team works with Claude.

Organization instructions are available to Owners and Primary Owners on Team and Enterprise plans.

## How organization and user instructions interact

Claude supports two levels of instructions. Understanding how they interact helps owners and the people on your team get the most out of both.

[TABLE]

When both are set, organization instructions take precedence. If an individual instruction directly contradicts an organization instruction, Claude favors the organization-level instruction. For example, if an organization instruction says “Always respond in formal English” but an individual instruction says “use a casual tone,” Claude responds formally.

Individual instructions still apply for anything the organization instructions don’t address.

**Note:** Instruction prioritization relies on prompt-level instructions. In rare edge cases involving directly contradictory instructions, behavior may vary. Test your instructions to confirm they produce the results you expect.

## Set up organization instructions

You need at least an Owner role to configure organization instructions.

1.  Go to **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)**.

2.  Find the **Organization instructions** section.

3.  Enter your instructions in the text area. The maximum length is 3,000 characters.

4.  Click “Save changes.”

5.  Changes may take up to an hour to take effect across Claude products.

To remove instructions entirely, clear the text area and click “Save changes.”

------------------------------------------------------------------------

## Best practices

**Keep instructions concise and clear.** Organization instructions are included in every message sent by everyone in your organization, so shorter instructions help keep conversations efficient. Aim for direct, specific guidance rather than lengthy explanations.

**Be specific about what you want.** Instead of vague instructions like “be professional,” give concrete direction such as “Respond in formal English. Don’t use contractions, slang, or emojis.”

**Focus on consistent behaviors.** Organization instructions work best for instructions that should apply uniformly across every conversation—response formatting standards, tone requirements, or organization-wide context.

**Avoid conflicting instructions.** If your organization instructions contradict each other, Claude may not follow either one reliably. Review your instructions as a whole to make sure they’re consistent.

**Don’t try to override safety behaviors.** Organization preferences can’t disable Claude’s built-in safety guidelines or content policies. Instructions that conflict with Claude’s core training won’t be followed.

**Test your instructions.** After saving, start a new conversation to verify Claude is following your instructions. Try a few different types of questions to confirm the instructions work across a range of topics.

**Review and update regularly.** As your organization’s needs change, revisit your instructions to make sure they’re still relevant. Removing outdated instructions keeps Claude’s responses focused.

------------------------------------------------------------------------

## Example instructions

**Team identity.** “Address the team as the Acme Platform team. When users ask about ‘our product,’ they mean Acme Cloud.”

**Communication standards.** “Respond in formal English. Use active voice. Avoid contractions and emojis.”

**Response formatting.** “Prefer concise responses under 300 words. Use bullet points for lists with three or more items.”

**Domain context.** “Our team works in healthcare claims processing. When users mention ‘claims,’ they’re referring to insurance claims, not legal claims.”

**Referral guidance.** “When users ask about HR policies, direct them to [\[email protected\]](https://support.claude.com/cdn-cgi/l/email-protection#7e160c3e1f1d131b501d1113) rather than giving specific policy advice.”

**Data handling reminders.** “Don’t include customer names, account numbers, or other personally identifiable information in responses or generated artifacts.”

---

## Set up and manage the new unified Claude experience for your Enterprise organization | Claude Help Center

Source: https://support.claude.com/en/articles/17444462
Path: 13-Enterprise-Admin/set-up-and-manage-the-new-unified-claude-experience-for-your-enterprise-organization.md

# Set up and manage the new unified Claude experience for your Enterprise organization

Updated today


The new unified Claude experience brings chat and Cowork together on web, desktop, and mobile. Members no longer choose between chat and Cowork before they start. Every conversation starts as a chat, and when a task needs to run code or work with files, Claude continues the same conversation in an isolated cloud sandbox managed by Anthropic, then returns the finished work for review.

This article explains how admins turn on the new unified Claude experience, which settings apply to it, and how it fits your security, compliance, and data retention setup.

The new unified Claude experience is in public beta for Claude Enterprise plans. In your admin settings it appears as **Chat and Cowork unified (beta)**. The new unified Claude experience is off by default. Nothing changes for your organization until an admin turns it on.

------------------------------------------------------------------------

## How the new unified Claude experience works

1.  One conversation, two modes. The new unified Claude experience starts as a chat and moves into a Cowork cloud session when a task needs agentic work, and goes back to chat when it doesn't. Members aren't asked to switch modes.

2.  Agentic work runs in an isolated cloud sandbox. Each conversation that needs one gets its own virtual machine, managed by Anthropic. After a few minutes idle, the virtual machine is stopped and its disk is saved as an encrypted snapshot. The sandbox environment expires 30 days after it was created.

3.  The conversation stays on record. Chat history, sharing, export, the Compliance API, and your data retention setting all work from the conversation.

4.  Work moves across devices. Members can start a task on the desktop app and pick it up on web or mobile.

5.  Local access stays optional. Through the Claude desktop app, members can link their computers so Claude can work with local folders, local MCP servers, and applications. Your Computer access and device policies govern this.

------------------------------------------------------------------------

## Before you begin

The new unified Claude experience requires two capabilities to be on for your organization:

1.  Chat, since chat is where every conversation starts.

2.  Cloud code execution and file creation, since agentic work runs remotely.

It doesn't require Cowork or Cowork in the cloud to be on. It is meant to replace both over time.

[TABLE]

The new unified Claude experience isn't available yet for organizations that use zero data retention (ZDR), the HIPAA-ready configuration, or customer-managed encryption keys (CMEK). See **[Current limitations](#h_8b973139dd)** below.

------------------------------------------------------------------------

## Turn on the new unified Claude experience

### For your whole organization

1.  Sign in to Claude as an Owner or Primary Owner.

2.  Go to **[Organization settings \> Capabilities](https://claude.ai/admin-settings/capabilities)**.

3.  Turn on **Chat and Cowork unified (beta)**.

4.  Ask members to refresh the Claude app if they don't see the new experience right away.

This turns on the new unified Claude experience for every member of your organization. To limit access to specific members, use roles instead.

Scheduled tasks and session history will continue working after the change. You don’t need to update or recreate anything.

### For specific members, using roles

If your organization uses custom roles, you can grant the new unified Claude experience to specific roles:

1.  Go to **[Organization settings \> Roles](https://claude.ai/admin-settings/roles)**.

2.  Open the role you want to update and go to “Capabilities.”

3.  Turn on **Chat and Cowork unified (beta)** for that role.

4.  Assign the role to the members who should have access. You can sync role membership through SCIM to add members in waves.

Access is checked on every request. If you remove a member's role or turn the setting off, they lose access to the new unified Claude experience.

To learn how to create a role, see **[Set up role-based permissions on Enterprise plans](https://support.claude.com/en/articles/13930458)**.

### Recommended rollout

1.  Share this article with your IT, security, and procurement teams.

2.  Complete your security review. Most teams review two components they already know: Claude chat and Cowork in the cloud. If a review of Cowork in the cloud is already underway, it carries over.

3.  Start in your sandbox organization, if you have one, or with a pilot group through a role.

4.  Review your network egress, connector, sharing, and Computer access settings before you turn the experience on.

5.  Monitor usage and gather feedback for two to four weeks, then expand in waves.

------------------------------------------------------------------------

## Admin settings that apply to the new unified Claude experience

[TABLE]

Identity settings carry over unchanged. Members sign in to Claude as members of your organization, and your SSO (SAML/OIDC), SCIM provisioning, and tenant restrictions apply. The new unified Claude experience adds no separate sign-in.

For recommended Security settings and architecture, see **[Unified Claude and Cowork security best practices](https://trust.anthropic.com/resources?s=uukz8hyx7jmdmo80lys36s&name=claude-cowork-security-best-practices)**.

------------------------------------------------------------------------

## Data retention and deletion

1.  **Conversations.** Conversations are kept until the member deletes them or according to your organization's custom data retention setting if you’ve set one (minimum 30 days). See **[custom data retention](https://support.claude.com/en/articles/10440198)**. Conversations in a project follow the project’s retention period. The period is measured from the most recent activity in the conversation or in any cloud session Claude started from it, so a conversation isn’t deleted while one of its cloud sessions is in use.

2.  **Cloud sessions started from a conversation.** When a conversation is deleted, whether by a member, by deleting its project, or under your retention period, the cloud sessions Claude started in it are deleted too, including their transcripts.

3.  **Artifacts.** When a conversation is deleted under your retention period, the artifacts made in it aren't deleted with it. They follow the retention period that covers artifacts, if you have one. Research reports and artifacts in some older chats are the exception, and they're deleted with their conversation.

4.  **Not deleted with a conversation.** Custom data retention doesn’t cover:

    1.  Cowork cloud sessions a member starts directly, including in a project. These are kept until the member deletes them.

    2.  Scheduled tasks and their runs, including tasks created from a conversation. These keep running after that conversation is deleted. To prevent this, turn off Scheduled tasks.

    3.  Cowork history stored on a member’s computer.

5.  **Memory during the public beta.** Memory isn’t covered by your retention setting during the beta. When you turn on the new experience, each member’s existing memory is copied into it, and memory from conversations that are later deleted can remain. If you turn the experience off, that memory is kept. We plan to apply your retention setting to memory, including memory created during the beta, at general availability, and will update this article.

As with chat, we may retain content flagged by our trust and safety systems or as required by law. See **[How long do you store my organization’s data?](https://privacy.claude.com/en/articles/7996866-how-long-do-you-store-my-organization-s-data)**

------------------------------------------------------------------------

## Manage access and spend

1.  Pricing stays usage-based, and the spend controls you have today apply.

2.  Spend limits can be set at the organization, group, and member level.

3.  Usage analytics break out chat, Cowork, and the new unified Claude experience.

4.  Models: The model allowlist you set applies to both chat and agentic work in the new unified Claude experience.

------------------------------------------------------------------------

## Usage and analytics

The new unified Claude experience gets its own line in your reports. When a member has access to the new unified Claude experience, their chat and Cowork usage is reported under **Chat and Cowork unified (beta)**, not under Chat or Cowork. Think of it as a new folder for the same work: only where the usage is filed changes, so moving usage to this line doesn't change how it's counted or billed.

### How usage is counted

- **Counting follows each member.** Usage counts toward the new unified Claude experience if the member has access to it when the usage happens. If you turn it on for some roles first, your reports can show Chat, Cowork, and **Chat and Cowork unified (beta)** on the same day.

- **It covers chat and Cowork.** A member's chats, their cloud sessions, and their use of the Cowork desktop app count toward the new unified Claude experience.

- **Other products keep their own line.** For example, Claude Code and Claude in Chrome usage is still reported under those products.

- **Past usage stays where it is.** Data for the new unified Claude experience starts the day a member first gets access. Usage from before then stays under Chat or Cowork. If you turn the new unified Claude experience off, past unified usage keeps its **Chat and Cowork unified (beta)** label, and new usage goes back to Chat and Cowork.

- **Include it when you add things up.** When you add up spend, tokens, or messages for Chat and Cowork, include **Chat and Cowork unified (beta)** too. Otherwise your Chat and Cowork numbers will seem to drop as members move over. For the number of people, use the overall active users count rather than adding products together, since one person can be active in more than one.

### In the analytics dashboard

- **A page of its own.** Admins who can view analytics will find a Chat and Cowork unified page under Apps, marked Beta. It shows daily active users.


- **Across the dashboard.** You can pick "Chat and Cowork unified (beta)" in the Active users, Active members, Total spend, and Spend by model charts. The See all members table on the Overview page has a "Messages in Chat and Cowork unified (beta)" column. The Top connectors and Top skills cards don't include it yet.


- **Who sees it.** The page appears for every organization that can turn on the new unified Claude experience, whether or not it is on yet.

- **Give it a day or two.** New activity can take up to two days to show up.

### In the APIs

- **Your keys already work.** If you already use these APIs, your existing access keys work. If your scripts check responses against a fixed list of fields or products, add the new ones.

- **Cost and Usage API.** Tokens and spend for the new unified Claude experience are reported under the product value `chat_cowork_unified`. You can filter to it with `products[]`. The `chat` and `cowork` values still work, and cover usage from members who didn't have the new unified Claude experience at the time.

- **Analytics API.** Member, skill, and connector rows have a `chat_cowork_unified_metrics` object with two parts: `chat`, for chat activity, and `sessions`, for cloud sessions and the Cowork desktop app. Daily summaries add daily, weekly, and monthly active user counts for the new unified Claude experience, such as `chat_cowork_unified_daily_active_user_count`.

### What members see

Members with usage-based seats see their own usage of the new unified Claude experience on their usage page, listed as **Chat and Cowork unified (beta)**.

### Unified smart report and analytics chat

To learn how to create a smart report, refer to **[Get started with smart reports](../15-Claude-AI-Features/get-started-with-smart-reports.md#h_85974ef359)**. When choosing products to run the report for, choose “Chat and Cowork unified (beta)” to run the report against the new unified Claude experience.

------------------------------------------------------------------------

## Efficient mode for the new unified Claude experience

When on, org members can use only Sonnet and Haiku models in the new unified Claude experience, and new sessions start on Claude Sonnet 5.5 at low effort. Other products aren’t affected. As lower-cost models become available, Anthropic may update the default model and effort level to keep the best balance of performance and cost.

**How to access it:** Owners can turn it on for the whole org on the Models page, or for specific custom roles on each role's Models tab.

------------------------------------------------------------------------

## Current limitations

1.  **ZDR, HIPAA, and CMEK.** The new unified Claude experience isn't available to organizations with zero data retention, the HIPAA-ready configuration, or customer-managed encryption keys. These organizations stay on their current configuration. HIPAA and CMEK support is expected after general availability.

2.  **Per-role access for Computer access and scheduled tasks.** These are set for the whole organization, not per role.

---

## Set up Salesforce in Claude for your organization | Claude Help Center

Source: https://support.claude.com/en/articles/16952184-set-up-salesforce-in-claude-for-your-organization
Path: 13-Enterprise-Admin/set-up-salesforce-in-claude-for-your-organization.md

# Set up Salesforce in Claude for your organization


Salesforce in Claude is a plugin built by Salesforce that brings your organization's Salesforce data and workflows into Claude. This article walks admins through enabling the plugin and connecting Salesforce for your organization.

For end-user instructions about how to use Salesforce in Claude, see **[Use Salesforce in Claude](https://support.claude.com/en/articles/16952186)**.

Salesforce in Claude is available in beta on all paid plans for organizations Salesforce approves through its beta sign-up. It currently works in chat and Claude Cowork (web and desktop).

## What's included

Salesforce in Claude bundles:

- 37 sales skills built by Salesforce, covering workflows like renewal prep, QBR decks, pipeline coverage, and meeting follow-up.

- The Salesforce and Slack connectors, so the plugin works without connecting each service separately.

- A setup skill that runs the first time a user opens the plugin. It learns the user's role and book of business and personalizes the skills to them.

## Before you begin

Your organization needs access to the latest Sales Cloud enterprise edition to be eligible for beta.

## Step 1: Request access to the Salesforce in Claude plugin (Salesforce admins)

To install the Salesforce in Claude plugin, admins on your Salesforce account can request access through AgentExchange:

1.  Go to **[AgentExchange](https://agentexchange.salesforce.com/sales-cloud-in-claude-beta-access)**.

2.  Fill out the form to request access to the Salesforce in Claude plugin. Admins will receive an acceptance email with a link to a Salesforce help article for setup instructions.

3.  Complete the setup instructions in your acceptance email.

## Step 2: Enable Salesforce in Claude for your organization (Claude admins)

After you complete step 1, choose your installation preference and connect the MCP connector. You need Primary Owner or Owner access for the Claude organization to complete these steps.

To choose your installation preference:

1.  Go to **[Organization settings \> Plugins](https://claude.ai/admin-settings/plugins)**.

2.  Find **Salesforce Marketplace**.

3.  Choose an installation preference for the groups you want to use it: "Installed by default," "Available to install," or "Required." Learn more about **[controlling plugin distribution](../22-Safety-Policy/manage-plugins-for-your-organization.md#h_cef6a5f497)**.

To activate and configure the Salesforce MCP Connector:

1.  Your Salesforce admin follows Salesforce's setup guide to turn on the Salesforce MCP server.

2.  The admin sends you the External Client App's consumer key and consumer secret.

3.  In Claude, go to **Organization settings \> Connectors** and select "Salesforce (Beta)." Enter the consumer key in the **OAuth client ID** field and the consumer secret in the **OAuth client secret** field, then save.

After that, each member signs in to Salesforce with their own account the first time they use the plugin.

## Step 3: Activate the Salesforce in Claude plugin (members of your organization)

After you complete steps 1 and 2, members of your organization can turn on and use the plugin.

If you’re a member of an organization and want to learn more about using the Salesforce in Claude plugin, see **[Use Salesforce in Claude](https://support.claude.com/en/articles/16952186)**.

## What Claude can see and do in Salesforce

Claude signs in as each user's own Salesforce account and sees only what that user's existing Salesforce permissions already allow. By default, Claude asks you to approve each proposed change before it's written.

---

## Set up JIT or SCIM provisioning | Claude Help Center

Source: https://support.claude.com/en/articles/13133195-setting-up-jit-or-scim-provisioning-to-manage-user-assignments-on-team-or-enterprise-plans
Path: 13-Enterprise-Admin/setting-up-jit-or-scim-provisioning-to-manage-user-assignments-on-team-or-enterprise-plans.md

# Set up JIT or SCIM provisioning


This guide covers how to configure user provisioning and role assignment for your Claude or Claude Console organization.

JIT provisioning is available for Team plans, Enterprise plans, and Console organizations. SCIM provisioning is available for Enterprise and Console organizations only.

**Before you begin:** This guide assumes you have already completed the steps in **[Set up single sign-on (SSO)](../21-Account-Support/setting-up-single-sign-on-sso.md)**, including domain verification and SSO configuration with your Identity Provider (IdP), and you have an Admin (Console) or Owner (Claude) role.

------------------------------------------------------------------------

## Step 1: Choose your provisioning mode

Once SSO is configured, you need to decide how users will be provisioned to your organization. This is controlled via the **User provisioning** section in **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)**.

### Provisioning options

**Invite only** is the default. Users are added and removed directly in Claude or Console settings.

**Just-in-time (JIT):** Users assigned to your Anthropic IdP app are automatically provisioned when they first log in. This option is available to all plans.

**SCIM directory sync:** Users are automatically provisioned and deprovisioned based on assignments in your IdP, without requiring them to log in first. SCIM is available for Enterprise plans and Console organizations with their own parent organization or joined to an Enterprise parent organization. SCIM is not available for Team plans or Console organizations joined to a Team plan's parent organization.

When SCIM directory sync is on, Claude sends each newly provisioned member a welcome email. To turn this off, Owners and Primary Owners can navigate to **Organization settings \> Organization and access \> User provisioning**, find **SCIM directory sync**, and turn off "Send a welcome email to new members."

**Note:** Claude doesn't send this welcome email to organizations with additional data-handling or compliance requirements such as customer-managed encryption keys (CMEK), HIPAA, or FERPA, so the setting isn't shown for those organizations. Changes apply to members provisioned after you save, and emails already sent stay delivered.

### Provisioning behavior overview

Use this table to help decide which provisioning mode is right for your organization:

[TABLE]

**Note:** To remove a JIT user permanently, remove them in Claude and unassign them in your IdP. A user you remove only in Claude will be re-added the next time they log in with SSO.

Both JIT and SCIM can be combined with **Enable group mappings** to control role or seat tier assignment based on IdP group membership. If you select either of these options for your provisioning mode, **Enable group mappings** will appear within the **User provisioning** section:


**Important:** Group mappings set a user’s role type and seat tier only. Users with the Custom role get their permissions from groups in Claude, and those groups sync from your IdP only when your provisioning mode is SCIM directory sync. With JIT, you need to create groups and add users to them manually in **[Organization settings \> Groups](https://claude.ai/admin-settings/groups)**. If you map an IdP group to the Custom role under JIT without doing this, those users have no permissions when they log in. Learn more about **[managing groups on Enterprise plans](https://support.claude.com/en/articles/13799932)**.

### Available roles and seat tiers

[TABLE]

For information on purchasing seats or adjusting your plan's seat allocation, see our guides for **[Team plans](https://support.claude.com/en/articles/12004354-purchasing-and-managing-seats)** and **[Enterprise plans](../17-Billing-Plans/purchasing-and-managing-seats-on-enterprise-plans.md)**.

------------------------------------------------------------------------

## Step 2: Set up SCIM directory sync (if using SCIM)

**Note:** Skip this step if you're using Invite only or JIT provisioning.

If you chose SCIM as your provisioning mode, you need to establish the connection between your Identity Provider and Anthropic before enabling it.

1.  Navigate to your **Organization and access** settings in Claude (**[claude.ai/admin-settings/organization](https://claude.ai/admin-settings/organization)**) or your **Identity and access** settings in Console (**[platform.claude.com/settings/identity](https://platform.claude.com/settings/identity)**)

2.  In the **User provisioning** section, click “Setup SCIM” (or “Manage SCIM”)next to **SCIM directory sync**.

3.  Follow the WorkOS setup guide to configure SCIM in your Identity Provider. You'll need to copy values from WorkOS into your IdP's Anthropic application.

**‼️ When you reach the IdP Group step, pause to review Steps 3 and 4 of this guide, alongside the other guides.**

For IdP-specific JIT / SCIM setup instructions, see:

- **[Okta SAML](https://workos.com/docs/integrations/okta-saml)** / **[OKTA SCIM](https://workos.com/docs/integrations/okta-scim)**

- **[Entra ID SAML](https://workos.com/docs/integrations/entra-id-saml)** / **[Entra ID SCIM](https://workos.com/docs/integrations/entra-id-scim)**

- **[Google SAML](https://workos.com/docs/integrations/google-saml)** / **[Directory Sync](https://workos.com/docs/integrations/google-directory-sync)**

- **[OneLogin SAML](https://workos.com/docs/integrations/onelogin-saml)** / **[OneLogin SCIM](https://workos.com/docs/integrations/onelogin-scim)**

- **[JumpCloud SAML](https://workos.com/docs/integrations/jumpcloud-saml)** / **[JumpCloud SCIM](https://workos.com/docs/integrations/jumpcloud-scim)**

- See additional IdPs **[here](https://workos.com/docs/integrations)**

Once your IdP is connected, continue to Step 3.

------------------------------------------------------------------------

## Step 3: Configure provisioning mode and enable group mappings

1.  Find the **User provisioning** section of your settings.

2.  Select your chosen option:

    1.  **Invite only**: New members can only join if manually invited by an existing member. SSO access alone won't add them to your org.

    2.  **Just-in-time (JIT)**: Allow people with SSO access to join when they first log in. Each new member uses one of your available seats.

    3.  **SCIM directory sync**: Add or remove members automatically as your directory changes. Your org always stays current.

3.  If you selected “Just-in-time (JIT)” or “SCIM directory sync,” don’t click “Save changes” yet. First, make sure all users are assigned to your Anthropic application in your IdP. Any users who aren’t assigned when you save will be deprovisioned from the organization.

4.  Once you’ve confirmed all users are assigned in your IdP, choose one:

    1.  Click “Save changes” to complete the setup and trigger the initial provisioning. Where it’s available, the admin console shows a preview of what the sync will change before it applies, including how many users will be removed. Review it before you confirm, and cancel if the removal count is higher than you expect. Learn more about **[how SCIM sync works](how-scim-sync-works-for-enterprise-organizations.md)**.

    2.  Turn on **Enable group mappings** and move to Step 4. If you’re on an Enterprise plan and plan to map any IdP group to the “Custom” role with SCIM, follow the steps in the next section first.

### Use custom roles with SCIM group mappings (Enterprise plans)

Your IdP groups appear in Claude only after the first SCIM sync runs, so you can’t assign them to custom roles ahead of time. Until a synced group is assigned to a custom role, users in a group mapped to “Custom” have no permissions: features that require permissions are greyed out or hidden, and Claude Code sign-in fails. To avoid this, set up SCIM in two passes:

1.  Click “Save changes” with **Enable group mappings** off, and let the initial sync finish.

2.  Go to **[Organization settings \> Roles](https://claude.ai/admin-settings/roles)** and assign the synced groups to your custom roles. If the groups aren’t listed yet, click “Check for updates” in **[Organization settings \> Groups](https://claude.ai/admin-settings/groups)** and sync Groups only.

3.  Return to **User provisioning**, turn on **Enable group mappings**, and move to Step 4.

Learn more about **[setting up role-based permissions on Enterprise plans](https://support.claude.com/en/articles/13930458-set-up-role-based-permissions-on-enterprise-plans)**.

------------------------------------------------------------------------

## Step 4: Configure groups in your Identity Provider and map groups to roles and seat types

1.  Create groups in your IdP for each role you want to assign. Unless you're on the single-seat Enterprise plan, create groups for each seat type as well.

    1.  While there are no longer naming requirements for these groups, we recommend including something in the group name (e.g., `anthropic-claude-` or `anthropic-console-`) to make them easier to identify.

2.  Add users to the groups you created, ensuring at least one user (including yourself) is in a group that will be mapped to an Admin (Console) or Owner (Claude) role.

3.  Return to your **Organization and access** or **Identity and access** settings in Claude or Console, and find **User provisioning**.

4.  Toggle **Enable group mappings** on (if it’s not already):


5.  In the **Enable group mappings** section, click “Add” next to each role and select the corresponding group from your IdP in the dropdown.

    1.  When using group mappings, you *must* assign all users to a role-based group in order to ensure they’re provisioned an account. Assigning users to seat-tier based groups is optional.

    2.  You can map an IdP group to the “Custom” role. Users assigned this role have no default permissions; their access is determined entirely by the custom roles assigned to their groups in Claude. If you use JIT, add these users to groups manually in **[Organization settings \> Groups](https://claude.ai/admin-settings/groups)** before they log in, since JIT doesn't sync group memberships from your IdP.

6.  **For all plans except single-seat Enterprise:** In the **Assign seat tiers to IdP groups** section (optional), click "Add" next to each seat type and select the corresponding group from your IdP. Users who aren’t in any seat tier group aren’t removed. Existing users keep their current seat type. New users receive the highest seat type that still has an unassigned purchased seat (for example, Premium while unassigned Premium seats remain, then Standard), and are added with no seat if none is available.

    1.  **For single-seat Enterprise:** Seat type mapping does not apply. All provisioned users are automatically assigned an Enterprise seat, provided one is available in your organization.

7.  Verify all necessary groups are mapped to the appropriate roles and seat types.

8.  Click “Save changes.”

    1.  **Note:** Microsoft Entra only pushes SCIM changes every 40 minutes, so there may be a delay before changes appear. You can check which users are synced from your IdP by clicking "Manage SCIM" and viewing the Directory. Those users in the Directory will be provisioned to Claude / Console.

**Important:** All users who need access must be assigned to the appropriate groups before you save your group mappings configuration. These users should already be assigned to your Anthropic application in your IdP from when you enabled SSO.

### How the Primary Owner role works with JIT and SCIM

- Your organization's Primary Owner is exempt from SCIM reconciliation. If the Primary Owner account is not present in the IdP directory, or is not a member of any group mapped to a role, it will be skipped when SCIM syncs. With JIT and group mappings, the Primary Owner’s role isn’t changed when they log in, and they aren’t removed if they’re not in any mapped group. The Primary Owner’s membership and role are preserved.

- This exemption applies only to the single Primary Owner role. Owner and Admin roles are **not** exempt and **must** be in a group mapped to a role, or they will be removed when SCIM group mappings are enabled. With JIT and group mappings, they’re removed the next time they log in.

- The Primary Owner role cannot be assigned through group mappings. It can only be transferred manually from **[Organization settings \> Members](https://claude.ai/admin-settings/members)**. Set your intended Primary Owner before enabling SCIM or group mappings.

- The Primary Owner is not exempt from SSO sign-in enforcement. SSO enforcement is applied by email domain; if the Primary Owner's email is on an enforced domain, they must authenticate through SSO.

------------------------------------------------------------------------

## Troubleshooting

### Users assigned correctly and showing in the directory but aren’t being added to the Claude as members?

Verify you have enough seats purchased and available to add members to your org.

1.  Check the number of available seats shown in **[Organization settings \> Organization and access](https://claude.ai/admin-settings/organization)** and purchase additional seats if needed (see our guides for **[Team plans](https://support.claude.com/en/articles/12004354-purchasing-and-managing-seats)** and **[Enterprise plans](../17-Billing-Plans/purchasing-and-managing-seats-on-enterprise-plans.md)**).

2.  Once you have available seats, go back to the Organization and access page and click “Sync now,” next to **Directory sync (SCIM)**. This will trigger a sync to provision accounts for those users not yet added as members.

### Users aren't being provisioned with the correct role

1.  Verify the user is assigned to the correct group in your IdP.

2.  Verify the group is mapped to the correct role in your **Organization and access** settings.

3.  **For JIT:** The user needs to log out and log back in for role changes to take effect.

4.  **For SCIM:** Click "Sync" to prompt an immediate sync, or wait for the automatic sync cycle:


### Users mapped to the Custom role can't access anything after logging in

This happens when your provisioning mode is JIT and an IdP group is mapped to the Custom role. JIT assigns the role type but doesn't sync group memberships from your IdP, so these users aren't in any group with a custom role attached and have no permissions.

To fix this, do one of the following:

1.  Add the affected users to the appropriate groups manually in **[Organization settings \> Groups](https://claude.ai/admin-settings/groups)**. Learn more about **[managing groups on Enterprise plans](https://support.claude.com/en/articles/13799932)**.

2.  Switch your provisioning mode to SCIM directory sync, which syncs groups and their memberships from your IdP. Learn more about **[how SCIM sync works](https://support.claude.com/en/articles/14499648)**.

### I lost Admin/Owner access after enabling group mappings

This happens when the person configuring group mappings isn’t assigned to a group mapped to an Admin or Owner role, causing their permissions to be downgraded to User. Someone who isn’t in any mapped group is removed from the organization instead (with JIT, the next time they log in; with SCIM, at the next sync) and needs to be added again. This doesn’t affect the Primary Owner.

To fix this, do one of the following:

**Option 1: Have another Admin/Owner reinstate your role**

1.  Contact another Admin or Owner of your organization.

2.  Ask them to navigate to **[Organization settings \> Organization](https://claude.ai/admin-settings/organization)** (for Claude) or **[Settings \> Members](../04-API-Reference/Other/playground.md)** (for Console).

3.  Have them change your role back to Admin or Owner.

**Option 2: Fix via your Identity Provider**

1.  In your IdP, assign yourself to a group with the correct prefix that maps to an Admin or Owner role.

2.  **For JIT:** Log out and log back in to regain access.

3.  **For SCIM:** Ask another Admin or Owner to click "Sync" in the **Organization and access** settings, or wait for the automatic sync cycle.

---

## Turn on data retention for a Workspace in a zero data retention organization | Claude Help Center

Source: https://support.claude.com/en/articles/16824617-turn-on-data-retention-for-a-workspace-in-a-zero-data-retention-organization
Path: 13-Enterprise-Admin/turn-on-data-retention-for-a-workspace-in-a-zero-data-retention-organization.md

# Turn on data retention for a Workspace in a zero data retention organization


This article shows you how to turn on 30-day data retention for a Workspace in your Claude Console organization when your organization uses zero data retention (ZDR).

This is only available to organizations on the Claude API that use zero data retention. Organizations that already retain data for 30 days don’t have this setting.

## Why a Workspace needs data retention

**[Covered Models](../15-Claude-AI-Features/covered-models.md)**, such as Claude Fable 5.1, require 30-day data retention and aren't available under zero data retention unless Anthropic has expressly authorized it for your organization (for example, under Enterprise Frontier Safeguards). If not, you need to turn on data retention. Data retention is configured at the Workspace level, so you need to turn it on for each Workspace you’d like to use Covered Models in.

Requests to Covered Models from a Workspace with retention off return an error like this one:

"In order to access this model, your organization or Workspace must have data retention enabled."

## Before you begin

Before you start, make sure that:

- You're an Admin, Owner, or Primary Owner of the organization. Users with other roles who are members of the Workspace can see the setting but can't change it.

- You're working in a Workspace other than the default Workspace. Retention for the default Workspace is managed at the organization level, so create a new Workspace if you need one. Learn more about **[creating and managing Workspaces](https://support.claude.com/en/articles/9796807)**.

## Turn on data retention for a Workspace

The setting is on your organization's **Data retention** page, where a dropdown menu at the top of the page lets you pick the organization or a Workspace.

To turn on data retention for a Workspace:

1.  Log in to the **[Claude Console](https://platform.claude.com/login)**.

2.  Go to the **Data retention** page (platform.claude.com/settings/privacy).

3.  Select the Workspace from the dropdown menu at the top of the page.

4.  Select "Standard data retention (30-day retention)" and click "Save."

5.  Review and accept the terms in the confirmation dialog. By accepting, you acknowledge that Section F of the **Service Specific Terms** applies to this Workspace and supplements your organization's agreement with Anthropic. Data retention turns on for the Workspace only after you accept.

## See which Workspaces have retention on

To see which Workspaces have data retention on, navigate to the **Workspaces** page ([https://platform.claude.com/settings/workspaces](../04-API-Reference/Other/playground.md)) and click the menu button to the right of the Workspace you want to check. Select "Data retention" to see if the Workspace is using either **Zero data retention** or **30 day retention**.

## What changes after you turn it on

- The change applies to new requests right away. In rare cases it can take up to an hour.

- Inputs, Outputs, and other data from this Workspace are retained for 30 days by default and may be accessed by Anthropic for safety and security purposes.

- The change isn't retroactive. It applies only to requests sent after you turn on retention.

- Your organization's zero data retention no longer applies to this Workspace while retention is on. Other Workspaces aren't affected.

- Requests to Covered Models from this Workspace's API keys now succeed, as long as your organization already has access to the model. You don't need new API keys.

**Important:** This setting covers API requests and responses from the Workspace. It doesn't cover features that need to store data to work, such as the Batch API, the Files API, and Claude Managed Agents sessions. Learn more about **[which features are eligible for zero data retention](../04-API-Reference/Other/manage-claude-api-and-data-retention.md#feature-eligibility)**.

## Turn off data retention for a Workspace

You can turn retention off again from the **Data retention** page (platform.claude.com/settings/privacy). If the Workspace uses customer-managed encryption keys, you can't turn retention off. If a program on the Workspace requires retention, turning it off removes that program.

---

## Use enterprise search | Claude Help Center

Source: https://support.claude.com/en/articles/12489464-using-enterprise-search
Path: 13-Enterprise-Admin/using-enterprise-search.md

# Use enterprise search


Enterprise search capabilities are available for users on Team and Enterprise plans.

Enterprise search adds a dedicated project for searching across your organization's knowledge sources with optimized instructions and seamless connector integrations.

## What is enterprise search?

We’ve added a pre-configured “Ask Your Org” **[project](../15-Claude-AI-Features/what-are-projects.md)** that appears in your sidebar. This project is designed specifically for unified knowledge access across your company's tools and data sources. This dedicated workspace provides:

- **Guided setup:** Easy onboarding flow for connecting your work apps.

- **Organization-wide availability:** Available to all members of your organization after an Owner completes the initial setup process.

  - Owners can control which apps are connected to the project.

  - Users need to authenticate with the connected apps before they can start using them.

- **Optimized instructions:** Specialized system prompts maintained by Anthropic for effective searches.

- **Unified access:** Search across multiple data sources (Slack, Microsoft 365, and more) in one place.

------------------------------------------------------------------------

## Get started

### For owners

The enterprise search project is enabled by default for all Team and Enterprise plan organizations within Admin settings, but Owners will need to complete this initial setup process before other members can use it:

1.  Click “Ask Your Org” in the menu on the left of your screen.

2.  Click the “Set up for your org” button to continue or “Disable” to turn the project off for your team.

3.  If setting up the project, you’ll be prompted to connect some tools to the project.

    1.  You are required to choose a connector for both **Documents** and **Chat**; **Email** is recommended but optional.

    2.  Only enabled connectors will be available in the enterprise search project.

4.  Click “+ Add more” to set up any other tools your team needs.

    1.  Choose between “Select another MCP” to open the Connectors directory, or “+ Add custom connector”

5.  Click “Next” when you’re finished.

6.  On the last screen, you can edit the name of your project. The format will be “Ask \[Name field\],” so whatever you enter here will impact what shows on the left panel.

7.  Enter a description, then click “Finish set up.”

If you decide to stop using the project after setting it up, an Owner will need to manually disable this feature organization-wide to remove it from users’ accounts:

1.  Navigate to **[Organization settings \> Capabilities](https://claude.ai/admin-settings/capabilities)**.

2.  Locate "Ask Your Org"

3.  Click the “Disable” button to turn the feature off.

### For users

The enterprise search project will be starred in your sidebar by default when the feature is enabled for your organization.

**First-time setup:**

1.  Click on the "Ask {org name}" project in your sidebar.

2.  Follow the guided onboarding flow to connect to recommended services.

3.  Authenticate with the services you want to search (Slack, Google, Microsoft 365).

    1.  The more connectors you enable, the more comprehensive your search results will be.

4.  Start asking Claude questions about your organization’s knowledge.

## Customize your enterprise search project

**Connecting additional tools:**

After initial setup, you can enable additional connectors in your search project by clicking “Connect” in the **Instructions** section and allowing access to the tools and connectors shown in the modal.

**To unstar or hide the project:**

1.  Click the “...” next to the project in your sidebar

2.  Select "Unstar"

**To adjust connected tools:**

1.  Open the search project.

2.  Click "Search and tools" in the lower left.

3.  Enable or disable specific connectors.

4.  Changes apply to new conversations in the project.

------------------------------------------------------------------------

## How does enterprise search work?

When you ask a question within your organization’s dedicated project, Claude searches across all your connected data sources to generate comprehensive, well-cited responses.

**Example queries:**

- "What is our company's remote work policy?"

- "Summarize discussions about the Q4 product roadmap."

- "Find information about our customer onboarding process."

- "What were the key decisions from last week's leadership meetings?"

Claude will search your connected tools—such as SharePoint documents, Slack conversations, Gmail threads, and Google Drive files—and synthesize information into a unified response with source citations.

## Compare enterprise search to standard projects

[TABLE]

## Use cases

Enterprise search is particularly valuable for:

**Executive briefings:**

- "What happened yesterday while I was out?"

- "Summarize key updates across the business"

**Project research:**

- "What are the main reasons customers cite for why they pick our competitors?"

- "What are the current blockers on the Infrastructure project?"

**Policy and process questions:**

- "What is our vacation policy?"

- "How do I submit an expense report?"

**Onboarding:**

- "How does our authentication system work?"

- "Who should I talk to, to learn about X?"

**Performance reviews:**

- "Find discussions and documents related to \[employee\]'s Q3 projects"

- "Summarize team contributions to the Platform initiative"

------------------------------------------------------------------------

## Privacy and permissions

- **Permission-aware:** You only see search results from data you have permission to access in the original systems. For a custom connector that uses a shared credential, results reflect what that credential can access.

- **User-level authentication for connectors:** Each user authenticates with their own credentials to enable connectors, unless a custom connector is set up with a shared credential, such as an API key. Everyone who uses that connector reaches the service with the same credential.

- **No external indexing:** Search results are generated by making MCP calls. No data from connected services are indexed in our systems for serving queries.

- **Data security:** All data access follows your organization's existing security controls and policies.

## Best practices

**Be specific about sources:** Instead of "Find information about the product launch,” try "Search Slack and Google Drive for discussions about the Q4 product launch."

**Use date ranges:** "Summarize emails from last week about the budget review."

**Combine multiple sources:** "Compile information from SharePoint documents, Slack discussions, and meeting notes about our hiring process."

**Break complex queries into steps:** For thorough research, ask Claude to search one source at a time, then synthesize findings.

------------------------------------------------------------------------

## Troubleshooting

### The search project isn't appearing in my sidebar

- Verify you’re using a Team or Enterprise plan.

- Have an owner check that the feature is enabled for your organization.

- Try refreshing your browser or signing out and back in.

### I can't connect a recommended connector

- Check that an owner has enabled the connector at the organization level.

- Confirm that you have an active account with the service.

- Ensure your organization's policies don't block third-party integrations.

- Make sure you've completed the authentication flow properly.

- Contact your administrator or IT department if problems persist.

### My search results seem incomplete

- Verify you've connected the relevant services through the onboarding flow.

- Check that you've authenticated successfully with each connected service.

- Confirm you have permissions to access the content in the original systems.

- Try being more specific about which tools to search or what information you need.

- Narrow your search with date ranges or specific keywords.

- Break complex queries into smaller, focused sections.

### Connected tools aren't working

- Revisit **[Settings \> Connectors](https://claude.ai/settings/connectors)** to verify authentication status.

- Try disconnecting and reconnecting the problematic connector.

- Check if the connector was recently disabled at the organization level.

### Claude is responding slowly in my search project – what should I do?

**Possible causes:**

- Searching across many large data sources takes time

- Network connectivity issues

- High load on connected services

**Try:**

- Narrowing your search scope

- Searching one tool at a time

- Being more specific about what you're looking for

### What happens if a connector stops working?

If a connector fails:

1.  The dedicated project will search remaining connected tools.

2.  You'll see a notice about the failed connector.

3.  Try reconnecting the tool through **[Settings \> Connectors](https://claude.ai/settings/connectors)**.

4.  Contact an organization owner if the issue persists.


------------------------------------------------------------------------

## Frequently asked questions

### Can I add my own custom connectors to the search project?

Yes. You can add connectors available through **[Settings \> Connectors](https://claude.ai/settings/connectors)**, and you can also add custom connectors if permitted by your organization. The guided onboarding recommends a few connectors, but you're not limited to those.

For more information, refer to **[Get started with custom connectors using remote MCP](../14-Connectors/get-started-with-custom-connectors-using-remote-mcp.md)**.

### Can I use enterprise search on mobile or desktop apps?

The search project is fully functional on Claude Desktop, but not available on Claude Mobile (iOS/Android).

### Will using the search project count against my usage limits?

Yes. Search queries within the project count toward your plan's standard usage limits, just like other projects and conversations.

### How is this feature different from using Research?

**Research**:

- Designed for deep, multi-step research on specific topics

- Performs extensive web and internal data searches automatically

- Takes longer and may use more messages

- Best for complex research projects

**Enterprise search:**

- Optimized for quick knowledge retrieval

- Searches internal sources you've connected

- Faster responses for finding specific information

- Best for everyday knowledge access

For more guidance, see **[When should I use web search, extended thinking, and Research?](https://support.claude.com/en/articles/11095361)**

### What data can Claude access in my search project?

Claude can only access data within the project that:

1.  Comes from connectors you've individually added and authenticated to.

2.  You have permission to view within the original systems.

3.  Is potentially relevant based on your search queries.

### Does everyone in my organization see the same search results?

No. Search results are permission-aware. You only see data that you have permission to access in each connected system. The exception is a custom connector that uses a shared credential, such as an API key, where everyone sees what that credential can access.

### Is my search history visible to others?

No. Conversations within your search project are private to you unless you choose to manually share them, just like other conversations with Claude. However, on Enterprise plans, conversations follow your **[organization's data retention policies](custom-data-retention-controls-for-enterprise-plans.md)**.

### How long are search results retained?

Search results are retained with their associated chats. You can delete search data by deleting the associated conversation. Enterprise organizations may have custom data retention policies that apply to all conversations, including those within the search project. Check with your organization owner for details.

---

## Use Google Workspace connectors | Claude Help Center

Source: https://support.claude.com/en/articles/10166901-using-the-google-drive-integration
Path: 13-Enterprise-Admin/using-the-google-drive-integration.md

# Use Google Workspace connectors


Connect your Gmail, Google Calendar, and Google Drive to Claude so you can search and send emails, manage your calendar, work with documents, and save files, all without leaving the conversation.

Google Workspace connectors (Gmail, Google Calendar, and Google Drive) are available for all users on Claude and Claude Desktop.

Claude can edit Google Docs, Sheets, and Slides live in a pane beside the chat, on Claude on the web and Claude Desktop. You and Claude can edit the same file at the same time. See **[Current limitations](#h_cbd94d5da5)** for what isn't supported.

For Team and Enterprise plans, an Owner or Primary Owner must enable these connectors at the organization level before individual users can authenticate. For setup instructions, read **[Use connectors to extend Claude's capabilities](https://support.claude.com/en/articles/11176164-use-connectors-to-extend-claude-s-capabilities#h_17dd443beb)**.

## What you can do

### Gmail

- **Search and read emails** using natural language queries

- **Draft emails** with proper formatting and context

- **Send, reply to, and forward emails** from Gmail. By default, Claude asks for your approval before each of these actions. On Team and Enterprise plans, owners decide whether members can allow these actions to run without asking each time.

- **Access email metadata**, including attachment metadata (not attachment content)

- **Manage email organization** with labels and threads

- **List saved drafts** in your Gmail account

### Google Calendar

- **View events and calendars**, including shared calendars you have access to

- **Create, update, and delete events** with full customization

- **Find mutual availability** across attendees

- **Manage attendee lists** and respond to invitations (accept, decline, or tentative)

- **Set up recurring meetings**

### Google Drive

- **Search and retrieve Google Docs** from your Drive.

- **Share, move, and trash files** in Google Drive. By default, Claude asks for your approval before each of these actions. On Team and Enterprise plans, owners decide whether members can allow these actions to run without asking each time.

- **Look up file metadata** and preview directly without searching first.

- **Read** Sheets, Slides, PDFs, images, and MS Office files.

- **Upload any file type**, with optional auto-convert to Google formats.

- **Create folders**.

- **Add Google Docs directly to chats and projects** by pasting URLs or selecting from recent documents.

- **View file permissions** and **list recent changes** to your Drive.

- **Save Claude-generated files** directly to your Drive (requires **[code execution and file creation](../15-Claude-AI-Features/create-and-edit-files-with-claude.md#h_1c99382190)** to be enabled).

**Note:** When Claude reads a Google Drive file, it extracts text content only. Images embedded in documents are not processed.

### Google Docs, Sheets, and Slides

- **Edit files live in a pane beside the chat.** You and Claude can work in the same file at the same time.

- **Create new Google Docs, Sheets, and Slides files.**

- **Read and work with comments and suggestions** in Google Docs.

These are separate connectors from Google Drive. Google Drive still handles searching, uploading, and sharing files.

------------------------------------------------------------------------

## How to use the connectors

Ask Claude a question that requires access to your Gmail, Calendar, or Drive. Claude automatically detects which tools it needs and uses them to respond. By default, each action Claude takes on your behalf requires your explicit approval. On Team and Enterprise plans, owners decide whether members can allow certain actions to run without asking each time.

Claude's response includes citations indicating which emails, calendar events, or documents were used as sources, with links to the originals when available. You can ask follow-up questions to dig deeper into any source.

### Add Google Drive files to chats

1.  Click the plus sign in the chat interface.

2.  Select "Add from Google Drive."

3.  Search through your recently accessed documents or paste the document's URL.

4.  When you send your message, Claude accesses and processes the document to inform its response.

You can add multiple files to a single chat to give Claude comprehensive context. The files must fit within the conversation's context window.

### Add Google Drive files to projects

The Google Drive connector is only available when adding to **Files** in private projects. This option will be disabled for shared projects.

1.  Open your project and find the **Files** section.

2.  Click the "+" button to add files.

3.  Select "Drive."

4.  Search through your recently accessed documents or paste the document's URL.

5.  Your document is added to your project knowledge for Claude to access when chatting in that project.

Google Docs added to chats and projects sync directly from Google Drive, so you're always working with the latest version.

### Edit Google Docs, Sheets, and Slides live

1.  Turn on the Docs, Sheets, and Slides connectors. Click the plus sign in the chat, hover over "Connectors," and toggle them on. If the connector isn't turned on when you ask Claude to edit a Google file, Claude prompts you to connect it, then continues with your request.

2.  Paste a Google Docs, Sheets, or Slides link into the chat, or ask Claude to create one by name (for example, "make a Google Slides deck from these notes"). Asking for a generic "doc" or "deck" without naming Google creates a local file instead.

3.  The file opens in a pane next to the conversation, and you can keep working in it while Claude edits.

    1.  The pane works on Claude on the web in Chrome, and on Claude Desktop when the **[built-in browser](https://support.claude.com/en/articles/16607400)** is turned on. On Enterprise plans, the built-in browser is off by default. If the pane isn't available, select “Open in Google” on the file card to open the file in your regular browser.

4.  Use the pane header to copy the file's Google link or open it directly in Google.

Claude can only open and change files your Google account has access to. Your existing Google sharing permissions apply.

Each file Claude creates or edits shows as a card in the chat. Select the card to open the file in the pane, or select “Open in Google” to open it in Google. In the pane, you can copy the file's Google link or use the share dialog.

Sign in to Google in the pane: The pane needs a Google session to show your file. On Claude on the web, it uses the Google account already signed in to your browser. On Claude Desktop, Claude asks you to sign in to Google in the pane. If the account in the pane is different from the one you connected to Claude, Claude tells you and offers to switch.

### Manage individual connectors

Live editing uses separate Google Docs, Google Sheets, and Google Slides connectors alongside Gmail, Google Calendar, and Google Drive. You can enable or disable specific connectors from below the chat interface:

1.  Click the plus sign in the chat interface.

2.  Hover over "Connectors."

3.  Toggle individual connectors on or off.

------------------------------------------------------------------------

## Privacy and data handling

- You must authenticate directly with your Google account before using these connectors.

- On Team and Enterprise plans, an Owner or Primary Owner must enable connectors at the account level before individual users can authenticate.

- Claude can only access the Gmail, Calendar, and Drive data for the Google account you've connected.

- Claude only accesses your data when you explicitly ask a question or request an action requiring this information, and retrieves the minimum information needed.

- Claude mirrors your existing permissions—you cannot access information you don't already have access to in Google Workspace.

- Data retrieved while using connectors is stored on Anthropic servers, protected by Anthropic's security infrastructure (see our **[Trust Center](https://trust.anthropic.com/controls#organizational-security)** for details). This data is retained with its associated chat, so you can delete any retrieved data by deleting the chat.

- We do not train our models on your Gmail, Drive, or Calendar connector data, ensuring your private information remains private.

  - **Note**: If you are using our consumer products (e.g. Claude Free, Pro, and Max (when using Claude Code with those accounts) and you have chosen to allow us to use your chats and coding sessions for model training, then any content you copy/paste from your Gmail, Drive, Calendar, or Claude's responses which include specific information from these connectors may be used to improve our models. For more information, refer to **[Is my data used for model training?](https://privacy.claude.com/en/articles/10023580-is-my-data-used-for-model-training)**

- Data is encrypted at rest and in transit.

### A note on Gmail permissions

During authentication, Google's OAuth screen mentions email sending permissions. Claude can send, reply to, and forward emails, but only does so with your explicit approval by default. On Team and Enterprise plans, owners decide whether members can allow these actions to run without asking each time.

------------------------------------------------------------------------

## Current limitations

- Claude can't add charts to Google Slides, and Slides edits may not appear live in the pane.

- Live editing in the pane works on Claude on the web in Chrome and on Claude Desktop with the built-in browser turned on. In other cases, use “Open in Google” on the file card.

- Attachment content is not directly accessible through Gmail (metadata only).

- Some advanced Gmail filters may not be supported.

- Complex queries may require multiple API calls.

- Rate limits apply per Google's API quotas.

- Performance may vary for large mailboxes.

------------------------------------------------------------------------

## Troubleshooting

### Reconnecting your Google account

If you see the error "*Please try again. You may need to reconnect with your Google Drive account,"* follow these steps:

1.  Navigate to **[Customize \> Connectors](https://claude.ai/customize/connectors)**.

2.  Find your Google connector and click on it to view the details.

3.  Click the "Disconnect" button.

4.  Click "Disconnect" in the confirmation message.

5.  The next time you use a Google Workspace feature in your chat, you'll be redirected to Google to re-authenticate.

If you're still having trouble, disconnect from your Google account settings at [**myaccount.google.com/connections**](https://myaccount.google.com/connections). Search for the Claude connection, click into the settings, and choose "Delete all connections."

### Google Workspace admin configuration

If your organization uses Google Workspace and the connectors aren't working (you may see the message *"Access blocked: your institution's admin needs to review Claude for Google Drive"*), your Google Workspace admin may need to allow Claude as a trusted application:

1.  Go to **[admin.google.com](https://admin.google.com)**

2.  Navigate to **Security \> Access and data control \> API controls \> Manage third-party app access**.

3.  Click "Add app," then select "OAuth App Name."

4.  Search for "Claude" and set it as "Trusted."

5.  Wait approximately 15 minutes for Google's policy to propagate, then try connecting again.

Live editing for Docs, Sheets, and Slides uses the same Claude for Google Drive app. Admins approve it once, and no separate approval is needed for each file type.

------------------------------------------------------------------------

## Frequently asked questions

### What happens if I update a Google Doc after adding it to a chat?

The document continues to sync with the most up-to-date version in Google Drive.

### Can I add multiple files to a single chat?

Yes. You can add multiple Google Drive files to provide Claude with comprehensive context. The documents must fit within the conversation's context window.

### What happens if I lose access to a document?

You won't be able to view its contents in conversations where it was previously added. The document preview is removed, but the conversation history is maintained.

### Does Claude have access to images, comments, or suggestions in Google Docs?

Claude can see comments and suggestions in Google Docs, but it can't see images.

### How do I get Claude to create a Google file instead of a local file?

Ask for a Google doc, Google sheet, or Google Slides deck by name, or paste a link to an existing Google file. If you ask for a document without mentioning Google, Claude creates a file you can download instead.

### Can I edit the file while Claude is editing it?

Yes. You and Claude can work in the same file at the same time. Claude re-reads the current content before each change, but in a busy file, check the result.

### Why does Claude ask me to sign in to Google in the pane?

The connector lets Claude change your file, and the pane needs its own Google session to show it. Sign in with the same Google account you connected to Claude.

### Can Claude send emails on my behalf?

Yes. Claude can send, reply to, and forward emails from Gmail, and asks for your approval by default before each of these actions. On Team and Enterprise plans, owners decide whether members can allow these actions to run without asking each time.

### Can I opt out of using these connectors?

On Team and Enterprise plans, Owners and Primary Owners can disable Google Workspace connectors for their organization in **[Organization settings \> Connectors](https://claude.ai/admin-settings/connectors)**. Individual users can also disable specific tools from the chat interface.

Browse all available connectors in the **[Connectors Directory](https://claude.ai/directory)**.

---

## View your organization's support tickets | Claude Help Center

Source: https://support.claude.com/en/articles/15937951
Path: 13-Enterprise-Admin/view-your-organization-s-support-tickets-claude-help-center.md

# View your organization's support tickets

July 15, 2026


The Customer Portal gives you a single view of every support conversation your organization has open or closed with Anthropic, not just the tickets you filed yourself.

The Customer Portal is currently in beta. During the beta, Anthropic Support enrolls teams individually and can't grant portal access directly to individual users. Access is limited to Admins of Claude Console organizations and Owners/Primary Owners of Enterprise plan organizations.

## Who has access

Access to the Customer Portal is based on your role in each Anthropic product, and roles are evaluated independently. For example, a user who's an Admin of a Console organization but only has a User role in their Enterprise organization will only see tickets for the Console organization.

[TABLE]

If you don't have one of these roles, you'll see the message “No access to tickets portal.” Ask your organization's admin to assign you the appropriate role in Claude.

## Open the portal

The portal must be opened from within Claude or the Console:

1.  Log in to your Claude or Console account.

2.  Click your initials or name in the lower left corner and select “Get help” from the menu.

3.  Select the “Messages” panel in the messenger that appears.

4.  Click the portal link at the top of the panel (“Looking for your company’s conversations? Go to customer portal”).


**Note:** The portal requires session authentication from the messenger, so opening the portal from a direct link or bookmark may not work reliably.

## Navigate the portal

1.  In the top left dropdown, switch from “Tickets” to “Conversations.”

2.  In the dropdown to the right, select the organization whose tickets you want to view.

3.  Click any row to open the full conversation thread and see its status.


## Why is there an organization dropdown?

Support history is organized by Anthropic organization. If your company has both an Enterprise organization and a separate Console organization, each appears as a distinct entry here. Select the organization relevant to the conversations you're looking for.

If there are multiple organizations tied to your company, the organization names can look similar. If you're not sure which organization is which, contact your Anthropic customer success manager (CSM) for help identifying them.

## What appears in the portal

[TABLE]

## Troubleshooting

### The portal won't load or redirects to a login page.

Sign in at **[claude.ai](http://claude.ai)** or **[platform.claude.com](../04-API-Reference/Other/playground.md)** first, then open the portal through the support messenger. Authenticating directly from the portal isn't supported.

------------------------------------------------------------------------

## Frequently asked questions

### I see “No access to tickets portal.”

You're signed in, but you likely don't hold an Owner/Primary Owner (Enterprise) or Admin (Console) role in any organization. Ask your organization's admin to grant the role, or have them check the portal on your behalf.

### The organization dropdown shows organizations I don't recognize.

Most users belong to multiple organizations, typically your company's Enterprise organization and separate Console organization. Organization names are often simple defaults that don't distinguish the organization type.

### A ticket I know exists isn't showing.

It's likely associated with a different organization (try switching organizations in the dropdown), or it was opened through email or Slack and hasn't been linked to your organization yet. Send the ticket details to your CSM and we'll map it.

### Can I open a new ticket from the portal?

Not directly. Use the chat messenger in Claude or the Console, and tickets opened there appear in the portal automatically. Learn more about **[how to get support](../15-Claude-AI-Features/how-to-get-support.md)**.

---

## What happens to a user's data when they are removed from a Team or Enterprise organization? | Claude Help Center

Source: https://support.claude.com/en/articles/12053672-what-happens-to-a-user-s-data-when-they-are-removed-from-a-team-or-enterprise-organization
Path: 13-Enterprise-Admin/what-happens-to-a-user-s-data-when-they-are-removed-from-a-team-or-enterprise-organization.md

# What happens to a user's data when they are removed from a Team or Enterprise organization?


This article explains what happens to projects, chats, and skills when a member is removed from your Team or Enterprise plan organization.

## Delete individual accounts within an organization

If you are a non-Owner member of a Team or Enterprise plan, you will need to contact an organization Owner to have your account removed.

Removing a user from your organization doesn’t delete their account or data. If you’re an Owner or Primary Owner, you can ask Support to permanently delete a removed user’s account and data:

1.  If you need a copy of the user’s data, run an organization **[data export](https://support.claude.com/en/articles/13346720)** from the Primary Owner’s account first. The export still includes a removed user’s data.

2.  If your organization provisions users through SCIM, remove the user in your identity provider (IdP). Otherwise, they may be provisioned again.

3.  **[Contact Support](../15-Claude-AI-Features/how-to-get-support.md)** from your own email address. Include the user’s email address, and confirm in writing that their account and data should be deleted.

**Important:** Deletion is permanent and can’t be undone.

## Delete organization-level accounts

For help with deleting your organization’s Team or Enterprise plan, please **[reach out to our Support team](../15-Claude-AI-Features/how-to-get-support.md)**. Note that organization level account deletion requests must be made by your account's Primary Owner.

## Will a user’s projects still be accessible after removing them?

Once a user is removed from your Team or Enterprise organization, whether remaining members can access their projects or not depends on the project's visibility settings.

### Private projects

If the project’s visibility was set to private, other members of the organization will not be able to access it once the team member has been removed from the account. If a team member knows they're working on something in a private project they want to pass on, they will need to adjust the project’s permissions so either everyone in the organization can edit it, or invite specific users and grant them "Can edit" permissions.

### Shared projects

If a deleted team member shared their project with the entire organization, it will appear under the "Team" tab where remaining members can view or edit the project, depending on their permissions. If the deleted team member shared the project with specific users only, it will appear under the "Shared with me" tab for those specific users after the member is removed from the organization.

## Will a user’s chats still be accessible after removing them?

When a user is removed from your Team or Enterprise organization, remaining members will no longer be able to access their chats. This applies to chat snapshots that are shared with the organization. After the user who created the chat is removed, remaining members will see this message when they try to access any shared chat URLs: "Conversation not found. The requested conversation either doesn't exist or you don't have permission to access it."

Note that the removed user’s data will still be included in any **[data exports](../22-Safety-Policy/how-can-i-export-my-claude-data.md)** run by your organization’s Primary Owner. For Enterprise organizations specifically, the removed user’s data will still be subject to any configured **[custom data retention settings](custom-data-retention-controls-for-enterprise-plans.md)**.

## Will a user's skills still be accessible after removing them?

Removing a user from your Team or Enterprise organization doesn't delete the skills they uploaded to their own account. Skills the user uploaded and never shared stay on their account and remain recoverable. If the user is later added back to the organization with the same email address, those skills will reappear under **[Customize \> Skills](https://claude.ai/new#settings/customize-skills)**, where they can download them.

## What happens if a member is re-added to an organization?

If a team member is removed and later added back to the same organization using the same email address, previous chats, projects, and skills will be restored. The Primary Owner can also always export the member's project data and chats (note that customer data retention settings may impact this).

To learn more about Anthropic’s data retention periods see **[here](https://privacy.claude.com/en/articles/7996866-how-long-do-you-store-my-organization-s-data)**.

---

## Who owns and manages the data of my team? | Claude Help Center

Source: https://support.claude.com/en/articles/9265372-who-owns-and-manages-the-data-of-my-team
Path: 13-Enterprise-Admin/who-owns-and-manages-the-data-of-my-team.md

# Who owns and manages the data of my team?

March 16, 2026


This article provides important information about your Claude for Work account associated with your organization's Claude for Work plan (Team or Enterprise plans). It includes details about data access and control that were shared with you when your Claude for Work plan account was set up.

*This article is about our commercial products such as Claude for Work and the Anthropic API. For our consumer products such as Claude Free, Pro, Max and when accounts from those plans use Claude Code, see [here](https://privacy.claude.com/en/collections/10663362-consumers).*

## Account Management

Your organization's designated Primary Owner manages your Work account and all associated data. This includes the ability to request access to your user data through data exports, which may contain your conversations with Claude, uploaded files, and usage patterns. Primary Owners can also remove your access to the Claude for Work plan account if necessary.

Your organization determines which Claude services and features you can access in your Claude for Work plan account and they may choose to limit certain functionality.

## Governing Agreements

The use of your Claude for Work plan account is governed by the agreement between Anthropic and your organization. Please contact your Primary Owner for details of this agreement.

While Anthropic’s [Privacy Policy](https://www.anthropic.com/legal/privacy) describes how we collect, use, and share personal information when acting as a data controller in providing the Claude services to your organization, please note that the Anthropic [Consumer Terms of Service](https://www.anthropic.com/legal/consumer-terms) and [Privacy Policy](https://www.anthropic.com/legal/privacy) do not apply to the use of the Claude for Work plan where Anthropic acts as the data processor.

## Organizational Policies

Your organization may have its own internal policies regarding usage of Claude for Work plan. Please refer to your Primary Owner for guidance.

## Questions about Privacy

If you have any questions about data privacy in your Claude for Work plan account, we encourage you to contact your organization's account owner(s).
