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
- 主要语言
- Python
- 星标
- 145k
- 派生
- 23.1k
- PR 合并指标
- PR 指标待抓取
描述
### 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.
贡献指南
这个仓库没有索引到贡献指南
调研方向
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.
由索引模型根据 Issue 内容生成。
评估
- 技术栈
- macos, node.js
- 领域
- desktop, security
- Issue 类型
- 缺陷
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 活跃度
- 活跃
- 描述清晰度
- 基本清楚
- 新手友好度
- 45/100