Remote Managed Settings
- Entity ID:
ent-20260410-363f35ee52f5 - Type:
service - Scope:
shared - Status:
active
Description
Enterprise managed settings synchronization service at src/services/remoteManagedSettings/. Fetches organization-managed configuration from the Anthropic API (/api/claude_code/settings), caches it locally as ~/.claude/remote-settings.json, and feeds it into the five-layer-architecture's settings merge pipeline as the policySettings layer -- the highest-priority settings source that cannot be overridden by user, project, or local settings.
The service enables centralized policy enforcement for Teams and Enterprise organizations: administrators define settings once on the server, and every Claude Code client in the organization picks them up automatically. The system is designed to fail open -- if the fetch fails and no cache exists, the client continues without remote settings rather than blocking.
Sync mechanism
Initialization and loading
initializeRemoteManagedSettingsLoadingPromise()is called early during startup (ininit.ts). It creates a promise that other subsystems can await viawaitForRemoteManagedSettingsToLoad(), ensuring they do not initialize with stale or missing policy data. A 30-second timeout prevents deadlocks if the load function is never called (e.g., Agent SDK tests).loadRemoteManagedSettings()runs during CLI initialization. It uses a cache-first strategy: ifremote-settings.jsonexists on disk, it applies those settings and unblocks waiters immediately (saving ~77ms of fetch latency on print-mode startup). The fetch still runs asynchronously; if new settings arrive, a hot-reload notification fires.- After loading, background polling starts to detect mid-session changes.
Fetch protocol
- Endpoint:
{BASE_API_URL}/api/claude_code/settings - Authentication: API key via
x-api-keyheader (Console users) or OAuth Bearer token withanthropic-betaheader (Claude.ai users). OAuth tokens are refreshed before each fetch. - HTTP caching: Uses
If-None-Match/ ETag-style caching with SHA-256 checksums. The checksum is computed client-side from the cached settings usingjson.dumps(sort_keys=True, separators=(",", ":"))semantics to match the server's Python implementation. A304 Not Modifiedresponse means the cache is still valid -- no data transfer needed. - Response handling:
200-- new settings; validated againstRemoteManagedSettingsResponseSchema(uuid + checksum + settings object), then fullSettingsSchemavalidation.204/404-- no settings exist for the user or the feature flag is off. Returns empty object{}and deletes any stale cache file.304-- cache still valid, no action needed.401/403-- auth errors, not retried.
Retry and resilience
- Up to 5 retries with exponential backoff (using the shared
getRetryDelayutility). - Auth errors (
skipRetry: true) are never retried. - On fetch failure, falls back to stale cached file if available (graceful degradation).
- On any error with no cache, fails open -- continues without remote settings.
Background polling
Polls every 1 hour (POLLING_INTERVAL_MS = 60 * 60 * 1000). On each poll:
1. Snapshots the current cached settings.
2. Fetches fresh settings via the same fetch-and-load pipeline.
3. Compares old vs. new via JSON serialization; if different, fires settingsChangeDetector.notifyChange('policySettings') to trigger hot-reload across the application (env vars, telemetry, permissions).
The polling timer is unref()'d so it does not keep the Node.js process alive, and a cleanup handler stops it on shutdown.
Auth state changes
refreshRemoteManagedSettings() is called on login/logout. It clears all caches (session, file, polling), re-fetches from the API, and fires a change notification.
Eligibility
Not all users query the remote settings endpoint. Eligibility is determined by isRemoteManagedSettingsEligible() in syncCache.ts:
| User type | Eligible? |
|---|---|
| Console users with API key | Yes |
| OAuth Enterprise / Team subscribers | Yes |
| OAuth with null subscriptionType (externally-injected tokens, CCD, CCR, Agent SDK, CI) | Yes -- API returns empty for ineligible orgs, cost is one round-trip |
| Third-party provider users (non-firstParty) | No |
| Custom base URL users (non-Anthropic endpoint) | No |
| Cowork / local-agent entrypoint | No -- Cowork has its own VM permission model |
The eligibility result is cached for the session lifetime to avoid repeated auth-chain evaluation.
Manageable settings
Remote settings use the same SettingsSchema as all other settings sources. Key categories an administrator can configure remotely:
| Category | Example fields |
|---|---|
| Permissions | permissions.allow, permissions.deny, permissions.defaultMode, permissions.disableBypassPermissionsMode |
| Model controls | model, availableModels (allowlist), modelOverrides (provider-specific IDs) |
| Environment variables | env (record of key-value pairs) |
| MCP server policy | allowedMcpServers, deniedMcpServers, allowManagedMcpServersOnly |
| Hooks | hooks, allowManagedHooksOnly, allowedHttpHookUrls |
| Plugin policy | strictPluginOnlyCustomization, strictKnownMarketplaces, blockedMarketplaces |
| Auth helpers | apiKeyHelper, awsCredentialExport, awsAuthRefresh, gcpAuthRefresh |
| Login | forceLoginMethod, forceLoginOrgUUID |
| Telemetry | otelHeadersHelper and OTEL env vars |
| Sandbox | sandbox settings |
| UX | companyAnnouncements, outputStyle, language, spinnerVerbs |
| SSH configs | sshConfigs for pre-configured remote environments |
| Permission rules | allowManagedPermissionRulesOnly to lock down non-admin permission rules |
Settings merge and conflict resolution
Settings are merged across five layers in ascending priority order:
pluginSettings -> userSettings -> projectSettings -> localSettings -> flagSettings -> policySettings
policySettings is the highest-priority layer. Because SETTING_SOURCES defines it last, it wins over all other sources when keys conflict. The merge uses lodash mergeWith with a custom settingsMergeCustomizer that concatenates arrays (rather than replacing them).
Within policySettings, a first-source-wins strategy selects a single origin:
- Remote (API-fetched, highest) --
remote-settings.json - MDM admin (HKLM registry on Windows / macOS plist) -- admin-only
- File-based (
managed-settings.json+managed-settings.d/*.jsondrop-ins) -- requires admin filesystem access - HKCU (Windows user-writable registry) -- lowest
Only the first non-empty source is used; they do not merge with each other. This means remote settings completely replace file-based managed settings when present.
policySettings and flagSettings are always loaded regardless of --setting-sources CLI flag. They cannot be disabled by the user.
Security check for dangerous settings
When new remote settings arrive with content, a security check runs before applying them (securityCheck.tsx). Settings are classified as "dangerous" if they contain:
- Shell-executing settings:
apiKeyHelper,awsAuthRefresh,awsCredentialExport,gcpAuthRefresh,otelHeadersHelper,statusLine-- these can execute arbitrary shell code. - Unsafe environment variables: Any
envvariable NOT in theSAFE_ENV_VARSallowlist. Dangerous env vars include those that redirect to attacker-controlled servers (ANTHROPIC_BASE_URL, proxy vars, OTEL endpoints), trust attacker-controlled servers (NODE_TLS_REJECT_UNAUTHORIZED,NODE_EXTRA_CA_CERTS), or switch to attacker-controlled projects (ANTHROPIC_API_KEY,AWS_BEARER_TOKEN_BEDROCK). - Hooks: Any non-empty
hooksconfiguration.
If dangerous settings are detected and have changed from the cached version, an interactive blocking dialog (ManagedSettingsSecurityDialog) is shown. The user can:
- Accept -- settings are applied and cached.
- Reject -- the process exits via gracefulShutdownSync(1). Cached settings are preserved for next session.
The dialog is skipped in non-interactive mode (consistent with the trust dialog). Analytics events are logged for dialog shown/accepted/rejected (tengu_managed_settings_security_dialog_*).
File layout
| File | Responsibility |
|---|---|
index.ts |
Public API, fetch logic, retry, background polling, save/clear |
types.ts |
Zod schema for API response (RemoteManagedSettingsResponseSchema), fetch result type |
syncCache.ts |
Eligibility check (isRemoteManagedSettingsEligible), auth-touching logic |
syncCacheState.ts |
Session cache state, disk I/O for remote-settings.json, eligibility tri-state |
securityCheck.tsx |
Security dialog for dangerous settings approval |
Key claims
- Remote managed settings are the highest-priority settings layer (
policySettings), overriding user, project, local, and flag settings on merge. - Within the policy layer, remote settings take precedence over MDM, file-based managed settings, and HKCU registry.
- The system fails open: fetch failures never block CLI startup; stale cache or no-settings are the fallback.
- Dangerous settings (shell executors, unsafe env vars, hooks) require interactive user approval via a blocking dialog before being applied. Rejection causes process exit.
- Background polling re-fetches every hour, triggering hot-reload if settings change mid-session.
- Checksum-based HTTP caching (SHA-256 with Python-compatible JSON normalization) minimizes network traffic.
Relations
- parent-of: five-layer-architecture -- remote managed settings feed into Layer 5 (Infrastructure/Auth) as the policy settings source
- depends-on:
settingsChangeDetector(utils/settings/changeDetector.ts) -- triggers hot-reload notifications across the application - depends-on:
SettingsSchema(utils/settings/types.ts) -- validates fetched settings against the shared schema - related-to: permission-pipeline -- remote settings can define
permissions.allow/deny/askrules andallowManagedPermissionRulesOnly - related-to: plugin-system --
strictPluginOnlyCustomizationand marketplace allowlists are enforced via policy settings - related-to: ManagedSettingsSecurityDialog component -- blocking UI for dangerous settings approval
Sources
src/services/remoteManagedSettings/index.ts-- main service implementation (fetch, retry, polling, save/clear)src/services/remoteManagedSettings/types.ts-- API response schemasrc/services/remoteManagedSettings/syncCache.ts-- eligibility logicsrc/services/remoteManagedSettings/syncCacheState.ts-- cache state managementsrc/services/remoteManagedSettings/securityCheck.tsx-- dangerous settings security dialogsrc/utils/settings/constants.ts--SETTING_SOURCESordering and precedencesrc/utils/settings/settings.ts-- settings merge pipeline (loadSettingsFromDisk)src/utils/settings/types.ts--SettingsSchemadefinitionsrc/utils/managedEnvConstants.ts--DANGEROUS_SHELL_SETTINGSandSAFE_ENV_VARSdefinitionssrc/components/ManagedSettingsSecurityDialog/utils.ts-- dangerous settings extraction and classification