[BUG] Desktop third-party mode (Vertex/Bedrock): SSH environment never appears — sshApisAvailable is hardcoded false in the bundled renderer

Status Open
Reported on v2.1.220
Maintainer reply None cached
Activity 0 comments · opened Aug 3, 2026

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report
  • [x] I am using the latest version (1.24012.9 is the current release per the update manifest)

What's Wrong?

In Claude Desktop third-party mode (Vertex AI / Bedrock — profile dir Claude-3p), the Code
tab's session environment picker offers Local only. The SSH group never renders — no host
entries, and critically no "Add SSH host…" item, so there is no way to add one through the UI
either. sshConfigs in ~/.claude/settings.json is ignored.

This is not a configuration problem. I traced it into the shipped renderer bundle.

The gateContents/Resources/ion-dist/assets/v1/c360a9e1c-DrYIyI47.js @119573:

sshSectionVisible: g && !nt

nt @111334 is 0 === _?.sshHostAllowlist?.value.length. With no managed-settings file the
optional chain is undefined, so 0 === undefined is false and !nt is true. The allowlist
is not involved.

g is sshApisAvailable, produced by this IIFE @107273:

{sshConfigs:h, sshConfigsLoaded:m, sshApisAvailable:g, refresh:x} = function(){
    const e = !1,                                   // hardcoded false
    [t,n] = (0,VS.useState)([]),                    // sshConfigs, never populated
    [s,a] = (0,VS.useState)(!0),
    i = (0,VS.useCallback)(() => iR().then(...), []);   // wraps re?.getSSHConfigs?.()
    return (0,VS.useEffect)(() => {}, [e,i]),       // effect body empty, dep array intact
      {sshConfigs:t, sshConfigsLoaded:s, sshApisAvailable:e, refresh:i}
}()

So sshSectionVisible is permanently false. The empty mount effect that still carries its
dependency array [e,i] looks like dead-code elimination against a known-falsy constant — the
loader was presumably if (e) i(). As a result iR() is never called from the picker and
sshConfigs never leaves its useState([]) initializer.

The items list is gated on the same value, so it cannot be non-empty either — @68380:

sshEnvItems: (0,VS.useMemo)(() => u ? d.map(...) : [], [u,d,n,v,g,ce])   // u === sshApisAvailable

And both layout branches of the picker gate on the bare prop, in
ion-dist/assets/v1/c5610fbe3-Bao3nWiP.js:

l && <Submenu><SubmenuTrigger icon="CommandLine">{le}</SubmenuTrigger>…</Submenu>   // @135039
l && <Fragment><Separator/><Group><GroupLabel>{le}</GroupLabel>{je}{we}</Group></Fragment>  // @136289

where le is the "SSH" label (i18n waUHa40VD6) and we is "Add SSH host…" (i18n
Or2vJpOmcq) — which is why the entire affordance disappears rather than showing an empty list.

The backend is complete and unconditionally wired, which is what makes this look like an
oversight rather than a deliberate limitation:

| Layer | State |
| --- | --- |
| Main-process IPC | ipc.handle("…LocalSessions_$_getSSHConfigs") registered unconditionally, guarded only by the generic origin check shared by all LocalSessions methods |
| Preload | exposes getSSHConfigs, setSSHConfigs, ensureSSHConnected, resolveSSHSettings, getTrustedSSHHosts, checkRemoteTargetTrust |
| Session machinery | preconnectRecentSSHConfigs(), remote-server controllers, remote $HOME resolution, remote settings cascade |
| Settings reader | vxn() reads sshConfigs from the user tier of ~/.claude/settings.json; a {id, name, sshHost} entry passes both the zod schema and the yxn guard |

The bridge also demonstrably works at runtime: a different component
(ion-dist/assets/v1/shared-17-YFu3JFq7.js @~306052) calls the same method live and un-gated, with
a real capability probe (const e = K?.getSSHConfigs; if (!e) return;), to rebuild the sshHost→id
map on session restore. So the picker's const e = !1 is an override, not a failed probe.

For contrast, the WSL sibling two lines away is a genuine probe:

y = oe("ccd_wsl_backend") && !!re?.listWslDistros && !!re?.checkRemoteTargetTrust

Why this appears to be third-party-specific. The two deployment modes load the renderer from
different places:

// 3P — index.chunk-CnWKsyE_.js @2877045
getMainWindowUrl(){ return Rm }                             // Rm = "app://localhost"
// 1P — @2898808, this.type = "1p"
getMainWindowUrl(){ return this.deps.anthropicOriginUrl() }

and the app:// handler is installed over the bundled directory
(_Dn(path.join(XPt(), "ion-dist"), …) @5218197, where XPt() returns process.resourcesPath
when packaged). So 1P streams a server-side renderer that can be updated independently, while 3P is
pinned to the copy inside the installer — and in 1.24012.9 that copy has the flag off. I can only
observe the bundled build, so I can't confirm the server-side one differs; that inference is from
the split itself plus the fact that the feature is documented without qualification.

What Should Happen?

Per the Desktop docs, the environment
dropdown should list an SSH section containing any sshConfigs entries plus an
"+ Add SSH connection" item, in third-party deployments as well as first-party. The docs
describe sshConfigs as a normal user setting with no deployment-mode caveat.

Steps to Reproduce

  1. Install Claude Desktop 1.24012.9 on macOS and configure it in third-party mode against

Vertex AI (profile dir becomes ~/Library/Application Support/Claude-3p).

  1. Add a valid SSH host to ~/.ssh/config and confirm ssh <alias> connects from a plain shell.
  2. Add to ~/.claude/settings.json:

``json
"sshConfigs": [
{ "id": "example", "name": "Example Host", "sshHost": "<alias>" }
]
``

  1. Fully quit and relaunch Claude Desktop.
  2. Code tab → New session → open the environment dropdown.

Observed: Local only. No SSH group, no "Add SSH host…", no error and no log line.
Expected: an SSH group listing "Example Host", plus the add-host item.

No managed-settings file is present (/Library/Application Support/ClaudeCode/ does not exist), so
sshHostAllowlist is not the cause.

Error Messages/Logs

(none — this fails silently)

There is no diagnostic at any log level. vxn(), Yyt() and bxn() contain no logging
statements, so raising DESKTOP_LOG_LEVEL does not surface the decision either.

Is this a regression?

No, this never worked.

Claude Code Version

Desktop 1.24012.9 (confirmed current — the update manifest at
/api/desktop/darwin/universal/squirrel/update returns currentRelease: 1.24012.9).
Bundled/host CLI: 2.1.220.

Platform

Google Vertex AI

Operating System

macOS (26.6, arm64)

Additional Information

Suggested fix: enable sshApisAvailable in the renderer build that ships inside the installer,
so third-party deployments get the same SSH environment support as first-party. Given the entire
main-process implementation is already present and registered unconditionally, this looks like a
one-flag build-configuration difference rather than a missing feature.

Related: #77750 requests pre-configurable WSL environments for Desktop, which is the Windows
analogue of the same "pre-configure remote environments via settings" need. Note also that the
managed-config schema defines a disableWslSessions key but has no SSH counterpart among its
managed keys — consistent with SSH availability being a build property rather than a configurable
one.

Doc mismatch, minor and separate: the docs document an optional startDirectory field on
sshConfigs entries. That string does not appear anywhere in the 1.24012.9 bundle; the internal
field is remoteCwd. The zod schema does accept startDirectory, so this may just be naming skew
between the docs and this build.

Workaround for anyone hitting this: run Claude Code directly on the remote host over SSH
(terminal, or a VS Code Remote-SSH window). Remote inference is unaffected — only the Desktop
environment picker is.

View original on GitHub ↗