anthropics / anthropics/claude-code
[BUG] Desktop SSH connections fail when ~/.ssh is an iCloud Drive symlink — spawned ssh/ssh-keygen can't read known_hosts or config
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
### Preflight Checklist
- [x] I have searched [existing issues](https://github.com/anthropics/claude-code/issues?q=is%3Aissue%20state%3Aopen%20label%3Abug) and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
### What's Wrong?
The desktop app's "Add SSH connection" always fails with `Host denied (verification failed)` and:
> "" is not in ~/.ssh/known_hosts yet, or its host key has changed. If this is your first connection to it, run ssh to this host once in a terminal and accept the host key, then test again. If you have connected before and the machine was not reinstalled, do not connect.
Command-line `ssh` to the same host from the same Mac succeeds, and the host key **is** in `~/.ssh/known_hosts` and current (verified by comparing fingerprints against `/etc/ssh/ssh_host_*_key.pub` read over an already-trusted session). Following the dialog's advice therefore changes nothing.
Entering an alias from `~/.ssh/config` in the SSH Host field fails earlier, at `getaddrinfo ENOTFOUND`, even though the field's helper text offers "a host from ~/.ssh/config".
## Root cause
On this machine `~/.ssh` is a symlink into iCloud Drive (`~/Library/Mobile Documents/com~apple~CloudDocs/...`), a TCC-protected location, so one keystore can be shared between two Macs.
The app shells out to `ssh -G` and `ssh-keygen -F`. Those children get the correct `HOME` and resolve to `/usr/bin/ssh` and `/usr/bin/ssh-keygen`, but they do not inherit Claude.app's `kTCCServiceFileProviderDomain` grant, so `~/.ssh` is effectively invisible to them. The app's own Node `fs` reads are unaffected, which is what makes this hard to diagnose.
## Evidence (from `~/Library/Logs/Claude/ssh.log`)
1. `identityFiles: 5` — real `ssh -G ` returns **1** identity file for this host. 5 is exactly what `ssh -F none -G ` returns, i.e. the user config was never read.
2. An alias defined in `~/.ssh/config` is logged as `Host is not in known_hosts` — the raw string was used as the hostname, so `ssh -G` output was discarded.
3. The rejection is always the "lookup returned nothing" branch (`host is not in known_hosts`), never the "presented key does not match" branch. No key comparison ever happens.
4. `keys: 1[ssh-rsa]` — an identity file read **in-process** from that same iCloud-backed directory (the agent holds 3 keys, so this is the file from the dialog, not the agent).
Host key type is not involved: adding every key type the server offers, in any order, changes nothing, because the file is never read.
Related but distinct: #72735 reports the same error string caused by a genuine host key rotation.
### What Should Happen?
Two things:
1. **The connection should succeed.** The host key is present and current in `~/.ssh/known_hosts`, and command-line `ssh` accepts it. Reading `known_hosts` and `ssh_config` in-process — the way identity files are already read — would remove the dependency on TCC grants that spawned OpenSSH binaries do not inherit.
2. **The error should distinguish "the known_hosts lookup could not run" from "the presented key does not match."** The current message asserts the host is unknown or re-keyed and warns "do not connect", when in fact nothing was compared. Only a genuine mismatch warrants the MITM warning; as written, it sends users off to re-record a host key that is already correct, which cannot help.
### Error Messages/Logs
```shell
~/Library/Logs/Claude/ssh.log (host/user redacted):
[info] [SSH2Connection] Resolved @ -> @:22 (identityFiles: 5)
[warn] [SSH2Connection] Host is not in known_hosts
[info] [SSH2Connection] Connecting to @:22 (agent: env:/var/run/com.apple.launchd.XXXXX/Listeners, keys: 1[ssh-rsa], proxy: false, keyboard: true)
[warn] [SSH2Connection] Rejecting host key for - host is not in known_hosts
[error] [SSH2Connection] Connection error: Host denied (verification failed)
[error] [RemoteServerController] Connection failed (166ms, trigger: send_message, attempt 1): Host denied (verification failed)
[warn] [RemoteServerController] Auto-reconnect aborted: terminal error class "hostkey" (Host denied (verification failed))
With a ~/.ssh/config alias in the SSH Host field, the alias is never resolved:
[warn] [SSH2Connection] Host is not in known_hosts
UI, when an alias is entered:
Couldn't find the host "". Check the spelling in SSH Host, or connect to your VPN if it is an internal name.
getaddrinfo ENOTFOUND
Same shell, same user, same HOME - the lookups the app relies on all succeed:
$ ssh-keygen -F
# Host found: line 51
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5...
$ ssh -G | grep -c '^identityfile'
1
$ ssh -F none -G | grep -c '^identityfile'
5 # <- the number the app logs
$ ssh
# alias resolves fine via ~/.ssh/config
```
### Steps to Reproduce
On macOS, with `~/.ssh` relocated into iCloud Drive (a common setup for sharing one keystore between two Macs):
1. Move `~/.ssh` into iCloud Drive and symlink it back:
```
mv ~/.ssh "$HOME/Library/Mobile Documents/com~apple~CloudDocs/ssh-store"
ln -s "$HOME/Library/Mobile Documents/com~apple~CloudDocs/ssh-store" ~/.ssh
```
2. Confirm OpenSSH is happy from a terminal - both of these must succeed:
```
ssh @ # connects
ssh-keygen -F # prints the recorded host key
```
3. In the desktop app, add an SSH connection: SSH Host `@`, port empty, Identity File set to a key in `~/.ssh`.
4. Click Test connection.
Observed: `Connection failed: Host key not trusted` / `Host denied (verification failed)`, with the dialog claiming the host is not in `~/.ssh/known_hosts` or has been re-keyed.
5. Open `~/Library/Logs/Claude/ssh.log`. It shows `Host is not in known_hosts` and `identityFiles: 5` - the latter being the count you get only when `~/.ssh/config` is not read (`ssh -F none -G`).
Also reproducible in step 3 by entering a `~/.ssh/config` alias instead of `@`: it fails at `getaddrinfo ENOTFOUND` because the alias is never resolved through the config.
A machine with a normal, local `~/.ssh` does not reproduce this.
### Claude Model
None
### Is this a regression?
No, this never worked
### Last Working Version
_No response_
### Claude Code Version
2.1.237 (Claude Code), in Claude desktop 1.34493.1
### Platform
Anthropic API
### Operating System
macOS
### Terminal/Shell
Terminal.app (macOS)
### Additional Information
**Environment**
- Claude desktop 1.34493.1 / Claude Code 2.1.237
- macOS 26.5.2, Apple Silicon
- OpenSSH from `/usr/bin` (both `ssh` and `ssh-keygen` resolve there on the login-shell PATH)
**Ruled out while diagnosing**
- Missing or stale host key entries. All host key types the server offers were added, verified against `/etc/ssh/ssh_host_*_key.pub` over an already-trusted session, and reordered; no effect. The file is never read.
- Malformed `known_hosts`. 63 lines, no CRs, no markers, no blank or short lines, only `ssh-ed25519` / `ecdsa-sha2-nistp256` / `ssh-rsa`.
- Wrong `HOME` in spawned children. `ps -Eww` on a process the app spawned shows `HOME=/Users/`.
- `ssh-keygen` not on PATH, or a slow/failing login-shell PATH probe. It resolves to `/usr/bin/ssh-keygen`, and `zsh -lc` returns in ~25 ms.
- TCC being denied to the app itself. Both `com.anthropic.claude-code` and `com.anthropic.claudefordesktop` hold `kTCCServiceFileProviderDomain` (allowed), and the app reads an identity file from the same directory in-process. Only the spawned OpenSSH binaries are affected.
**Workarounds**
None that keep the setup intact. Moving `config` and `known_hosts` back to real local files outside iCloud Drive would fix it, at the cost of the shared keystore. Granting Full Disk Access to `/usr/bin/ssh` and `/usr/bin/ssh-keygen` may also work, but that is a broad grant to system binaries and I did not test it.
Note: this reproduces on both Macs sharing the keystore, so the app cannot SSH out from either.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the desktop SSH connection path that spawns ssh -G and ssh-keygen -F, using ~/Library/Logs/Claude/ssh.log and the identityFiles: 5 and alias-resolution failures as evidence. Reproduce with a symlinked iCloud ~/.ssh and a local ~/.ssh; done means config and known_hosts are read reliably, aliases resolve, and lookup failures are distinguished from host-key mismatches.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, node.js
- Domain
- desktop, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100