现象 链接到标题

打开新的终端窗口,执行 ssh <内网服务器> 时反复提示:

Enter passphrase for key '~/.ssh/id_rsa':

明明之前在钥匙串中保存了密码,不该每次都输。

排查 链接到标题

SSH_AUTH_SOCK 指向僵尸 socket 链接到标题

$ ssh-add -l
Error connecting to agent: Connection refused

$ echo $SSH_AUTH_SOCK
/var/folders/.../agent.45029   ← 指向一个已经死掉的 agent 进程

SSH_AUTH_SOCK 环境变量保存着一个已经不存在的 agent 进程的 socket 路径。

多个 ssh-agent 进程同时运行 链接到标题

$ ps aux | grep ssh-agent
marshal  45030  ...  ssh-agent -s
marshal  45031  ...  ssh-agent -s
marshal  45312  ...  /usr/bin/ssh-agent -l

一个用户账号下同时跑了 3 个 ssh-agent,不正常。

根因:zshrc 中的 pkill 逻辑 链接到标题

检查 ~/.zshrc_custom.sh

if [ ! -S "$SSH_AUTH_SOCK" ] || ! ssh-add -l >/dev/null 2>&1; then
  unset SSH_AUTH_SOCK SSH_AGENT_PID
  pkill -9 -f ssh-agent         # ← 杀掉所有 ssh-agent 进程
  sleep 0.5
  eval "$(ssh-agent -s)"        # ← 新建一个 agent
  ssh-add --apple-load-keychain # ← 从钥匙串加载 key
fi

这段逻辑在每个新终端启动时执行。问题在于:

  1. pkill -9 -f ssh-agent 无差别杀掉所有 agent,包括其他终端正在使用的
  2. 多个终端打开时互相残杀对方创建的 agent
  3. agent 被杀后,socket 文件变成僵尸,SSH_AUTH_SOCK 指向无效路径
  4. 新创建的 agent 如果钥匙串处于锁定状态,--apple-load-keychain 静默失败(stderr 被重定向到 /dev/null
  5. 最终 ssh-add -l 失败,SSH 降级为交互式输入 passphrase

macOS 的 SSH agent 架构 链接到标题

macOS 自带由 launchd 管理的系统 SSH agent:

$ launchctl list com.openssh.ssh-agent
{
    "Label" = "com.openssh.ssh-agent";
    "Program" = "/usr/bin/ssh-agent";
    "ProgramArguments" = ("/usr/bin/ssh-agent", "-l");
    "PID" = 7750;
    ...
}

特点:

  • 进程名:/usr/bin/ssh-agent -l(注意 -l 参数)
  • socket 路径:/private/tmp/com.apple.launchd.xxx/Listeners
  • 跨登录 session 持久化,重启电脑后自动恢复
  • 支持 ssh-add --apple-load-keychain 从钥匙串加载 key

不需要手动 eval "$(ssh-agent -s)" 启动 agent。

解决方案 链接到标题

去掉 pkill,不再杀进程,优先使用系统 agent。

# ~/.zshrc_custom.sh

# SSH agent — 优先用 macOS launchd 系统 agent,跨终端持久化,避免多个 agent 互相残杀
if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
  export SSH_AUTH_SOCK=$(launchctl getenv SSH_AUTH_SOCK 2>/dev/null)
fi

if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
  export SSH_AUTH_SOCK=$(lsof -U 2>/dev/null | awk '/ssh-agent/ && /Listeners/ {print $NF; exit}' 2>/dev/null)
fi

if [ -z "$SSH_AUTH_SOCK" ] || [ ! -S "$SSH_AUTH_SOCK" ]; then
  export SSH_AUTH_SOCK="$HOME/.ssh/agent.sock"
  rm -f "$SSH_AUTH_SOCK"
  ssh-agent -a "$SSH_AUTH_SOCK" -s >/dev/null 2>&1
fi

ssh-add -l >/dev/null 2>&1 || ssh-add --apple-load-keychain

三层 fallback 逻辑:

flowchart TD A["新终端启动"] --> B{"SSH_AUTH_SOCK
已设置且有效?"} B -->|是| C["ssh-add -l"] B -->|否| D["launchctl getenv
获取 launchd socket"] D --> E{"获取到有效
socket?"} E -->|是| C E -->|否| F["lsof -U
扫描 ssh-agent Listeners"] F --> G{"找到
agent?"} G -->|是| C G -->|否| H["自建 ~/.ssh/agent.sock"] H --> C C -->|key 已加载| I["完成"] C -->|key 未加载| J["ssh-add --apple-load-keychain"] J --> I

关键改动:

  • 去掉 pkill,不再互相残杀
  • 优先用 launchd 系统 agent,所有终端共享同一个 socket
  • ssh-add --apple-load-keychain 只在需要时执行一次

验证 链接到标题

# 1. 清理僵尸 socket
$ find /private/var/folders/... -name "ssh-*" -type d -exec rm -rf {} +

# 2. 新终端启动后
$ ssh-add -l
4096 SHA256:... ~/.ssh/id_rsa (RSA)  ← key 已加载

# 3. SSH 测试
$ ssh -o BatchMode=yes <内网服务器> echo ok
ok

多个终端同时打开不冲突,重启后 agent 由 launchd 自动恢复。

总结 链接到标题

macOS 下不需要手动管理 ssh-agent 进程。利用系统自带的 launchd agent,配合 ssh-add --apple-load-keychain,即可实现跨终端持久化、免重复输入 passphrase。

核心原则:不要杀 agent,不要新建 agent,用系统的。