诡异!Tmux 新 session 里凭空多出一个 http_proxy 环境变量
前言
今天遇到一个非常诡异的问题:新创建的 tmux session 里,echo $http_proxy 竟然显示了一个有效的代理地址。但我明明记得自己在 .zshrc 里只定义了两个 alias:proxy_on 和 proxy_off,它们都是手动触发的,不会自动执行。
诡异的是,我的主 shell 里 http_proxy明明是空的,但一进 tmux 就有了。这个问题排查了将近一个小时才找到根因,特此记录。
问题现象
主 shell(Terminal.app / iTerm2):
$ echo $http_proxy
# 空!什么都没有
新建 tmux session:
$ tmux new -s test
$ echo $http_proxy
http_proxy=http://x.x.x.x:1081/
注意,这里出现了一个内网 IP 地址段的代理,而且端口 1081 和我平时用的 1031 不一样。这说明这个值不是我自己配置的。

问题分析
第一步:检查配置文件
我先检查了所有常见的配置文件:
~/.zshrc— 只有proxy_on/proxy_offalias,没有任何 export~/.zshenv— 不存在~/.zprofile— 只有一行brew shellenv~/.tmux.conf— 没有 set-environment proxy 的配置/etc/zshrc— 系统级配置,没有 proxy 相关内容
结果:配置文件里没有任何地方会自动设置 http_proxy

第二步:检查外部工具注入
我发现 .codex/service-env/ai.codex-desktop.env 文件内容为:
http_proxy=http://x.x.x.x:1081
https_proxy=http://x.x.x.x:1081
这个文件是 Codex 桌面应用通过 launchd 注入的环境变量。但这个文件不应该被 tmux/zsh 自动加载。
第三步:关键发现 — tmux 继承父进程环境
最诡异的事情发生了:
# 父 shell
$ echo $http_proxy
# 空
$ tmux new-session -d -s test
$ tmux send-keys -t test "echo $http_proxy" Enter
$ tmux capture-pane -t test -p
http_proxy=http://x.x.x.x:1081/ # tmux 里居然有值!
而 tmux show-environment 不显示这个变量。这说明什么?
tmux 默认会继承父进程(创建它的 shell)的环境变量。但父 shell 里明明是空的。真相只有一个——父 shell 的环境变量在某个时刻被设置了,但在我执行 echo 检查之前就已经被清理了。
实际排查后发现:launchd 会将环境变量注入到所有用户进程树中,这些变量在 Terminal.app 启动时就已经存在了,只是在主 shell 里通过 unset 或者特定逻辑清理掉了。但 tmux 作为 launchd 的子进程,在创建新 session 时会重新继承这些变量。

第四步:tmux 的环境变量机制
tmux 有两个层面的环境变量管理:
- Server 级别 (
tmux show-environment -s) — 所有 session 共享 - Session/Window 级别 — 每次
new-session时继承当时父进程的环境
当你执行 tmux new -s session-name 时,tmux 会 fork 一个进程,那个进程继承当时父 shell 的完整环境,包括 launchd 注入的 proxy 变量。
问题根因
根因:launchd 在系统启动时设置的环境变量,被 tmux 在创建新 session 时继承。
- Codex/OpenClaw 等桌面应用通过 launchd 设置了
http_proxy和https_proxy - macOS 的 launchd 将这些变量注入到用户会话的整个进程树
- Terminal/iTerm2 启动时加载
.zshrc,可能在某个环节没有正确处理这些变量 - 当用户执行
tmux new时,tmux 进程从父 shell 继承完整的environ,包括这些 proxy 变量
但更准确地说:问题不在 tmux,而在于 launchd 注入的变量在主 shell 里没有被正确清理。

解决方案
最简单有效的方案:在 .zshrc 最开头添加一行 unset,在 zsh 启动时主动清理这些不应该存在的 proxy 变量。
# ~/.zshrc 最开头添加
# Clear proxy variables to prevent automatic proxy from external tools
unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY

为什么这能解决问题?
因为 .zshrc 会在每次启动 zsh shell 时被执行。不管是:
- 打开一个新的 Terminal tab
- 创建新的 tmux session(tmux 默认执行 login shell)
- SSH 登录到这台机器
都会先执行 .zshrc,而 unset 会在任何其他配置之前执行,确保 proxy 变量被清理掉。
另一个替代方案: 检查是哪个 launchd 进程设置了这些变量,然后删除对应的 plist 文件。但这样做可能会影响相关应用的正常运行。
验证修复
修改后,重新创建 tmux session:
$ tmux new -s verify
$ echo $http_proxy
# 空!修复成功
总结
| 项目 | 内容 |
|---|---|
| 问题现象 | tmux 新 session 里出现意外的 http_proxy 值 |
| 根本原因 | launchd 注入的环境变量被 tmux 继承 |
| 解决方案 | 在 .zshrc 开头 unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY |
| 预防措施 | 定期检查 `env |
Q&A
Q: 为什么不直接在 .zshenv 里 unset?
A: .zshenv 在每次执行 zsh 时都会加载(包括子 shell),而 .zshrc 只在 login shell 时加载。但如果 proxy 变量是在 login shell 之后才被设置的(例如 tmux 内部),.zshrc 的 unset 仍然有效。
Q: 会不会影响需要代理的工作?
A: 不会。如果你确实需要代理,可以手动执行 proxy_on alias 来设置。那些 alias 在 .zshrc 里仍然可用。
Q: 怎么查看是谁设置了这个变量?
A: 可以用 launchctl export user | grep proxy 查看 launchd 注入的变量,或者用 ps -e -o pid,ppid,comm | grep <应用名> 来追溯进程树。
如果你也遇到了类似的问题,或者有更好的解决方案,欢迎留言交流!