3p / enterprise / inference-gateway deployment on Linux
"3p" = third-party / managed deployment: Claude Desktop pointed at a non-Anthropic inference
backend (Bedrock, Vertex, Azure, or a self-hosted gateway like LiteLLM) and/or an MDM-managed config.
On Linux it is driven by /etc/claude-desktop/managed-settings.json.
Minified names below are version-specific (they churn every release - see /linux); anchor on stable
strings. Verified against v1.14271.0.
managed-settings.json (/etc/claude-desktop/managed-settings.json)
The official Linux build reads /etc/claude-desktop/managed-settings.json natively - it ships a Linux
managed-config reader, so we no longer patch the win32-registry/macOS-plist reader the way the old MSIX
pipeline did. (Historical note: on the MSIX, fix_enterprise_config_linux.nim injected a Linux JSON reader
for the old enterprise.json path; that patch and path are obsolete now that the official build does it.)
Schema is documented in memory project_enterprise_json_schema - the load-bearing facts:
- Top-level
managedMcpServers is an array (name+transport+url/command), NOT an object-keyed
mcpServers map (that is silently dropped by the schema parser - no warning).
- Inference-gateway keys:
inferenceProvider ("gateway"/"bedrock"/"vertex"/…), inferenceGatewayBaseUrl,
inferenceGatewayApiKey, inferenceGatewayAuthScheme, inferenceModels (array; first is default),
disableDeploymentModeChooser:true.
- Any of
inferenceProvider / bootstrapUrl present + (disableDeploymentModeChooser OR persisted
deploymentMode!=="1p") flips the app into 3p mode.
What "3p mode" changes (all upstream behavior, not our patches)
| Effect |
Why |
Our stance |
userData -> ~/.config/Claude-3p/ |
Upstream setPath("userData", base+"-3p") at bootstrap, guarded by !process.env.CLAUDE_USER_DATA_DIR. Logs [custom-3p] .... |
Intended - isolates 1p login from 3p. Keep it. (To collapse: export CLAUDE_USER_DATA_DIR in the launcher - it short-circuits the suffix. Not done by default.) |
| Chat tab hidden |
chatTabEnabled:A=>A.chatTabEnabled===!0 (strict; default off in 3p). Cowork/Code stay (!==!1). |
Intended. To restore: add "chatTabEnabled": true to managed-settings.json (config-only, no patch). |
3P setup SPA at app://localhost/setup-desktop-3p |
The ion-dist config UI. |
fix_ion_dist_linux + fix_marketplace_linux. Config-key schema in baseline/ION.md. |
[custom-3p] in main.log is upstream's own log namespace - do not mistake it for a patch tag.
Cowork under 3p: works the same as 1p, on the official native backend
Cowork runs on the official native Cowork VM backend bundled in the Linux .deb (cowork-linux-helper
- QEMU/OVMF; requires
/dev/kvm) - the same backend 1p uses. There is no separate daemon (the old
claude-cowork-service is deprecated). The 3p OAuth bounce (issue #142 / session_stale_relogin) only
fires for a genuinely broken token; a logged-in account with credits + valid Anthropic models does not
bounce. (Provider model names matter: inferenceModels must be Anthropic models or the config-health gate
reports config_model_rejected. A gpt-* entry, or no credits -> billing_error, makes a turn fail and
looks like a bounce - it is not.)
The old "Download a one-time package" banner (getDownloadStatus() -> NotDownloaded because the local
VM-image check never matched on the MSIX build) is no longer ours to patch: the official Linux build ships
the native Cowork VM backend, so the historical fix_cowork_download_status_linux.nim patch is obsolete.
Does the Claude-3p split break anything? (hardcoded-path audit, verified)
No load-bearing path breaks. State isolates between Claude (1p) and Claude-3p (3p) by design;
nothing crashes or cross-leaks. Rules:
- Markers/sockets (quick-entry, SSO-callback) live in
$XDG_RUNTIME_DIR (+CLAUDE_PROFILE),
not .config -> unaffected by the userData switch.
- All Electron userData (sessions, logs,
claude_desktop_config.json, custom-themes JSON, screenshot
restore-token) goes through app.getPath("userData"), which auto-relocates to Claude-3p. So it
follows the split (e.g. a custom-theme set in 1p won't carry into 3p - cosmetic only).
- CLI config uses
CLAUDE_CONFIG_DIR (launcher); autostart .desktop files live in the XDG
~/.config/autostart/ spec dir and are CLAUDE_PROFILE-aware - both correctly not tied to the
-3p userData.
- The rule for new patches: write user state via
app.getPath("userData") (auto-isolates), never a
literal os.homedir()+"/.config/Claude". The Python screenshot fallback ~/.config/Claude/... in
js/cu_linux_executor.js is dead code (the JS always passes the real getPath value via
CLAUDE_PORTAL_TOKEN_PATH) - harmless, but don't copy that pattern.
Verify it loaded / works (logs at ~/.config/Claude-3p/logs/)
Note the dir: 3p logs to ~/.config/Claude-3p/, NOT ~/.config/Claude/. Reading the wrong dir gives
stale/1p evidence.
L=~/.config/Claude-3p/logs/main.log
rg -a 'Enterprise config loaded|custom-3p\] (3P mode active|Credentials loaded|ConfigHealth)' "$L" | tail
# healthy gateway: [custom-3p] ConfigHealth recomputed { state: 'healthy', provider: 'gateway' }
rg -a 'LocalAgentModeSessions.start|Turn succeeded|cycle_health|session_stale_relogin|config_model_rejected|billing_error' "$L" | tail
# Cowork spawn proof (official native VM backend):
rg -a 'Using Claude VM spawn|Spawn succeeded|vmStarted|Turn succeeded' ~/.config/Claude-3p/logs/cowork_vm_node.log | tail
Related
baseline/ION.md - 3P setup SPA bundle, patched patterns, config-key schema (inferenceProvider,
deploymentOrganizationUuid, …) and the ~/.config/Claude-3p/claude_desktop_config.json path.
- memory
project_enterprise_json_schema - the exact managed-settings.json shape + how to confirm it loaded.
- memory
issue142_cowork_3p_oauth_regression - the OAuth-bounce mechanism (only bites a broken token).
1---2name: 3p3description: Reference for the 3p / enterprise / inference-gateway deployment of Claude Desktop on Linux - how /etc/claude-desktop/managed-settings.json is read, what "3p mode" changes (separate ~/.config/Claude-3p userData, hidden Chat tab, the false VM-download banner), how Cowork still runs under it, which hardcoded paths are safe vs split under the -3p switch, and how to verify it all from logs. Use when working on managed-settings.json, inferenceProvider/gateway/Bedrock/Vertex configs, the 3p setup SPA (ion-dist), the Claude-3p directory, or debugging managed-deployment Cowork.4---56# 3p / enterprise / inference-gateway deployment on Linux78"3p" = **third-party / managed deployment**: Claude Desktop pointed at a non-Anthropic inference9backend (Bedrock, Vertex, Azure, or a self-hosted **gateway** like LiteLLM) and/or an MDM-managed config.10On Linux it is driven by `/etc/claude-desktop/managed-settings.json`.11Minified names below are version-specific (they churn every release - see `/linux`); anchor on stable12strings. Verified against v1.14271.0.1314## managed-settings.json (`/etc/claude-desktop/managed-settings.json`)1516The **official Linux build reads `/etc/claude-desktop/managed-settings.json` natively** - it ships a Linux17managed-config reader, so we no longer patch the win32-registry/macOS-plist reader the way the old MSIX18pipeline did. (Historical note: on the MSIX, `fix_enterprise_config_linux.nim` injected a Linux JSON reader19for the old `enterprise.json` path; that patch and path are obsolete now that the official build does it.)20Schema is documented in memory `project_enterprise_json_schema` - the load-bearing facts:21- Top-level **`managedMcpServers`** is an **array** (`name`+`transport`+`url`/`command`), NOT an object-keyed22 `mcpServers` map (that is silently dropped by the schema parser - no warning).23- Inference-gateway keys: `inferenceProvider` (`"gateway"`/`"bedrock"`/`"vertex"`/…), `inferenceGatewayBaseUrl`,24 `inferenceGatewayApiKey`, `inferenceGatewayAuthScheme`, `inferenceModels` (array; first is default),25 `disableDeploymentModeChooser:true`.26- **Any** of `inferenceProvider` / `bootstrapUrl` present + (`disableDeploymentModeChooser` OR persisted27 `deploymentMode!=="1p"`) flips the app into 3p mode.2829## What "3p mode" changes (all upstream behavior, not our patches)3031| Effect | Why | Our stance |32|---|---|---|33| **userData -> `~/.config/Claude-3p/`** | Upstream `setPath("userData", base+"-3p")` at bootstrap, guarded by `!process.env.CLAUDE_USER_DATA_DIR`. Logs `[custom-3p] ...`. | **Intended** - isolates 1p login from 3p. Keep it. (To collapse: export `CLAUDE_USER_DATA_DIR` in the launcher - it short-circuits the suffix. Not done by default.) |34| **Chat tab hidden** | `chatTabEnabled:A=>A.chatTabEnabled===!0` (strict; default off in 3p). Cowork/Code stay (`!==!1`). | **Intended.** To restore: add `"chatTabEnabled": true` to managed-settings.json (config-only, no patch). |35| **3P setup SPA** at `app://localhost/setup-desktop-3p` | The ion-dist config UI. | `fix_ion_dist_linux` + `fix_marketplace_linux`. Config-key schema in `baseline/ION.md`. |3637`[custom-3p]` in `main.log` is **upstream's own** log namespace - do not mistake it for a patch tag.3839## Cowork under 3p: works the same as 1p, on the official native backend4041Cowork runs on the **official native Cowork VM backend** bundled in the Linux `.deb` (cowork-linux-helper42+ QEMU/OVMF; requires `/dev/kvm`) - the same backend 1p uses. There is no separate daemon (the old43`claude-cowork-service` is deprecated). The 3p OAuth bounce (issue #142 / `session_stale_relogin`) only44fires for a genuinely broken token; a logged-in account with credits + valid Anthropic models does **not**45bounce. (Provider model names matter: `inferenceModels` must be Anthropic models or the config-health gate46reports `config_model_rejected`. A `gpt-*` entry, or no credits -> `billing_error`, makes a *turn* fail and47looks like a bounce - it is not.)4849The old "Download a one-time package" banner (`getDownloadStatus()` -> `NotDownloaded` because the local50VM-image check never matched on the MSIX build) is no longer ours to patch: the official Linux build ships51the native Cowork VM backend, so the historical `fix_cowork_download_status_linux.nim` patch is obsolete.5253## Does the `Claude-3p` split break anything? (hardcoded-path audit, verified)5455**No load-bearing path breaks.** State *isolates* between `Claude` (1p) and `Claude-3p` (3p) by design;56nothing crashes or cross-leaks. Rules:57- **Markers/sockets** (quick-entry, SSO-callback) live in `$XDG_RUNTIME_DIR` (+`CLAUDE_PROFILE`),58 not `.config` -> **unaffected** by the userData switch.59- **All Electron userData** (sessions, logs, `claude_desktop_config.json`, custom-themes JSON, screenshot60 restore-token) goes through `app.getPath("userData")`, which **auto-relocates** to `Claude-3p`. So it61 *follows* the split (e.g. a custom-theme set in 1p won't carry into 3p - cosmetic only).62- **CLI config** uses `CLAUDE_CONFIG_DIR` (launcher); **autostart** `.desktop` files live in the XDG63 `~/.config/autostart/` spec dir and are `CLAUDE_PROFILE`-aware - both correctly **not** tied to the64 `-3p` userData.65- **The rule for new patches:** write user state via `app.getPath("userData")` (auto-isolates), never a66 literal `os.homedir()+"/.config/Claude"`. The Python screenshot fallback `~/.config/Claude/...` in67 `js/cu_linux_executor.js` is dead code (the JS always passes the real `getPath` value via68 `CLAUDE_PORTAL_TOKEN_PATH`) - harmless, but don't copy that pattern.6970## Verify it loaded / works (logs at `~/.config/Claude-3p/logs/`)7172**Note the dir: 3p logs to `~/.config/Claude-3p/`, NOT `~/.config/Claude/`.** Reading the wrong dir gives73stale/1p evidence.74```bash75L=~/.config/Claude-3p/logs/main.log76rg -a 'Enterprise config loaded|custom-3p\] (3P mode active|Credentials loaded|ConfigHealth)' "$L" | tail77# healthy gateway: [custom-3p] ConfigHealth recomputed { state: 'healthy', provider: 'gateway' }78rg -a 'LocalAgentModeSessions.start|Turn succeeded|cycle_health|session_stale_relogin|config_model_rejected|billing_error' "$L" | tail79# Cowork spawn proof (official native VM backend):80rg -a 'Using Claude VM spawn|Spawn succeeded|vmStarted|Turn succeeded' ~/.config/Claude-3p/logs/cowork_vm_node.log | tail81```8283## Related84- `baseline/ION.md` - 3P setup SPA bundle, patched patterns, config-key schema (`inferenceProvider`,85 `deploymentOrganizationUuid`, …) and the `~/.config/Claude-3p/claude_desktop_config.json` path.86- memory `project_enterprise_json_schema` - the exact managed-settings.json shape + how to confirm it loaded.87- memory `issue142_cowork_3p_oauth_regression` - the OAuth-bounce mechanism (only bites a broken token).