终端环境与 Agent 会话环境说明
这份文档的目标,是把下面三件事彻底讲清楚:
- 你在终端里敲一条命令时,系统到底用了哪一层环境。
- 你在 Codex 这类桌面 Agent 里执行命令时,用到的环境和终端有什么相同、有什么不同。
- Cursor / Claude Code / Codex 这类“桌面端 Agent”在原理上到底是不是一回事。
本文基于两类信息:
- 本机实测结果
- 官方公开资料
其中“本机实测”发生在 2026-04-09,工作目录是 ~/Documents/nook-ios。
1. 一句话先讲明白
你平时“命令能不能跑通”,本质上取决于 5 层:
- 父进程传给你的环境变量
- Shell 启动时加载了哪些配置文件
- 当前工作目录是什么
PATH、alias、function、shell builtin、可执行文件的查找顺序- 当前宿主程序有没有额外的权限、沙箱、网络限制
终端、Codex、Cursor、Claude Code 的差别,大多都落在这 5 层上。
2. 一条命令到底怎么被执行
当你输入:
node -v
系统并不是“直接找到 node 就执行”。中间通常会经历这条链路:
- 一个宿主程序发起执行
- 例如 Terminal、iTerm、Codex Desktop、Cursor、Claude Code CLI
- 它创建一个 shell 进程
- 例如
/bin/zsh
- 例如
- shell 先加载启动文件
- 例如
~/.zshenv、~/.zprofile、~/.zshrc
- 例如
- shell 根据自己的查找规则解析
node- alias
- shell function
- builtin
PATH里的可执行文件
- 命令在当前目录、当前权限、当前网络/文件系统约束下运行
所以“同一台机器里,同一个命令,在不同地方结果不一样”是正常的。最常见原因不是命令本身,而是:
PATH不一样~/.zshrc没加载~/.zprofile没加载- 父进程环境不同
- 工作目录不同
- 工具运行在远程容器 / VM 而不是本机
3. zsh 的启动文件顺序
zsh 常见的加载顺序可以这样理解:
3.1 所有 zsh 都会读
/etc/zshenv~/.zshenv
这层最“底”。不管是不是 login shell,不管是不是 interactive shell,通常都会经过这里。
3.2 login shell 会额外读
/etc/zprofile~/.zprofile
这层适合放“登录时要准备好的环境变量”。
3.3 interactive shell 会额外读
/etc/zshrc~/.zshrc
这层适合放:
- alias
- prompt
- 补全
- 快捷函数
- 交互体验增强
3.4 login shell 还可能再读
/etc/zlogin~/.zlogin
这层很少有人用。
3.5 login shell 退出时可能读
/etc/zlogout~/.zlogout
你这台机器上没有 ~/.zlogin、~/.zlogout、/etc/zlogin、/etc/zlogout。
4. 你这台机器上的 zsh 配置是怎么工作的
下面这些是本机已经验证到的事实。
4.1 ~/.zshenv 的作用
你的 ~/.zshenv 做了一件非常关键的事:
if [ -z "${ZPROFILE_SOURCED:-}" ]; then
export ZPROFILE_SOURCED=1
if [ -f "$HOME/.zprofile" ]; then
source "$HOME/.zprofile"
fi
fi
它的含义是:
- 任何 zsh 一启动,先跑
~/.zshenv - 如果还没有打过
ZPROFILE_SOURCED=1这个标记 - 就主动
source ~/.zprofile
这个设计的直接效果是:
- 即使是非交互 shell,也能吃到你原本写在
~/.zprofile里的环境变量 - 所以像 Codex / Claude Code / 各种 agent harness 这种“不会真的给你开一个交互终端”的工具,也更容易继承到
nvm、brew、pyenv、Flutter、Android SDK 这些环境
这是你现在环境能工作得比较顺的核心原因之一。
4.2 ~/.zprofile 里放了什么
你的 ~/.zprofile 主要负责准备“开发环境变量”,包括:
- Homebrew
- rbenv
- nvm
- pyenv
- Flutter / FVM
- Android SDK
JAVA_HOMEDEVELOPER_DIR- 一些项目路径
~/.local/bin
也就是说,你机器上的语言工具链环境,主要是靠 ~/.zprofile 建起来的。
4.3 ~/.zshrc 里放了什么
你的 ~/.zshrc 很轻,只做了交互层的事情:
alias setup-flutter='bash ~/.cursor/flutter_templates/setup_flutter_project.sh'
这意味着:
setup-flutter只会在 interactive shell 里可用- 在 Codex 这种
zsh -lc的非交互 shell 里,这个 alias 默认不会出现
这和你实际观测到的现象是一致的。
4.4 /etc/zprofile 在你机器上的作用
系统级的 /etc/zprofile 调用了:
/usr/libexec/path_helper -s
这一步会把系统默认路径拼进来。它是 macOS 上非常常见的一层 PATH 初始化。
5. 你平时在终端里敲命令,用的是哪套环境
这里先说“普通规律”,再说“你机器上的推断”。
5.1 普通规律
你在 Terminal / iTerm 新开一个窗口或 tab 时,通常拿到的是 interactive shell。
至于它是不是 login shell,要看终端配置:
- macOS Terminal 通常默认偏向 login shell
- iTerm2 可以配置成 login 或 non-login
所以你在真实终端里,很常见的情况是:
~/.zshenv会加载~/.zprofile也可能会加载~/.zshrc会加载
于是你既能拿到环境变量,也能拿到 alias / prompt / 补全。
5.2 对你机器的意义
因为你的 ~/.zshenv 会主动 source ~/.zprofile,所以即使某个终端 profile 不是 login shell,你依然大概率能拿到:
- brew
- nvm
- pyenv
- Flutter
- Android SDK
- Java
而如果那个终端是 interactive shell,你还会额外得到:
setup-flutteralias- 任何以后写进
~/.zshrc的交互增强
所以从“最终体感”看,你的本地终端环境非常像一套“无论 login 与否都尽量完整”的 zsh 环境。
6. 你当前这个 Codex 会话里,用到的环境是什么
这一节只讲本机已经验证过的事实。
6.1 当前命令是怎么执行的
我在这个会话里执行命令时,实际走的是:
/bin/zsh -lc '<command>'
这表示:
- shell 是
zsh - 带
-l,所以是 login shell - 用
-c直接执行一段命令 - 不是一个真正给你人手打字用的 interactive shell
6.2 当前宿主进程是谁
当前 shell 的父进程是:
/Applications/Codex.app/Contents/Resources/codex app-server --analytics-default-enabled
也就是说,命令不是直接由 Terminal 发起的,而是由 Codex 桌面应用里的 app-server 拉起的。
6.3 当前会话已经验证到的环境事实
本机会话里已经确认到:
SHELL=/bin/zshTERM=dumbPWD=/Users/huchu/Documents/nook-iosnode来自~/.nvm/versions/node/v20.20.2/bin/nodenode -v是v20.20.2nvm在当前 shell 中是可用的 shell function
6.4 Codex 父进程本身就带着不少开发环境
我还检查了 Codex 的父进程环境,发现它自己已经带着这些变量:
PATHNVM_DIRNVM_BINJAVA_HOMEFLUTTER_ROOTANDROID_HOMEDEVELOPER_DIRLANGLC_ALLZPROFILE_SOURCED=1
这说明至少在你现在这台机器上,Codex app-server 不是一个“完全空白”的 GUI 进程。它已经带着一套开发环境在运行。
6.5 为什么当前 Codex 会话能用到你的 node
原因有两层,同时成立:
- Codex 父进程自身已经带着一部分开发环境
- 它再启动
/bin/zsh -lc时,zsh 还会走你的启动文件
所以当前 shell 既继承了父进程环境,又跑了 zsh 初始化逻辑。
6.6 为什么你的 PATH 现在有重复
这是你当前配置里最值得注意的点。
根因是:
~/.zshenv会先主动 source~/.zprofile- 当前 shell 又是
zsh -lc - login shell 阶段还会再次处理
~/.zprofile
而你的 ~/.zprofile 里有很多 export PATH=...,这些操作本身不是幂等的,所以路径会被重复追加。
于是你现在看到的 PATH 里出现了很多重复段,比如:
~/.local/bin- Flutter 相关路径
- Android SDK 相关路径
- nvm / pyenv / rbenv 相关路径
这不会立刻让命令失效,但会带来几个副作用:
PATH越来越长- 排障时更难看清“到底是哪一个 node 先被命中”
- 某些初始化脚本会被重复执行
7. 为什么 Codex 里的 shell 和你手开的终端“不完全一样”
虽然它们都在你本机上,但不是同一种 shell 场景。
7.1 终端更像“人类交互 shell”
特点通常是:
- interactive
- 有 prompt
- 有 alias
- 有补全
- 有颜色
- 有历史记录体验
TERM更像xterm-256color、screen-256color这类值
7.2 Codex 当前会话更像“工具驱动 shell”
特点通常是:
- 由宿主应用拉起
- 通过
-c执行命令字符串 - 重点是可编排、可自动化、可抓取输出
- 未必加载
~/.zshrc - 未必有完整交互终端语义
TERM可能是dumb
所以你以后遇到这种情况时,不要惊讶:
- 终端里能用的 alias,Agent 里不能用
- 终端里 prompt 很漂亮,Agent 里没有
- 终端里某些交互命令正常,Agent 里行为不一样
这不是“坏了”,而是宿主场景不同。
8. 桌面 Agent 的通用原理
这里说的是“抽象层面”。不同产品细节不同,但核心骨架非常像。
8.1 通用架构
大多数桌面 Agent 都可以用这条链路理解:
- GUI / IDE 宿主程序
- Agent harness
- 工具调用层
- shell / terminal
- 文件读写
- 搜索
- 浏览器 / 网络
- git
- 模型推理层
- 权限 / 沙箱 / 审批 / 远程执行层
也就是说,真正决定“它像不像你的终端”的不是模型本身,而是工具层和执行层。
8.2 一定要分清两种模式
所有这类产品,几乎都可以拆成两大类:
- 本地前台 Agent
- 远程 / 后台 Agent
这两类看起来都像“AI 在帮你干活”,但环境来源完全不同。
本地前台 Agent
特点:
- 命令在你的机器上执行
- 读写的是你当前磁盘上的文件
- 能不能找到
node、python、xcodebuild,取决于你本机环境 - 很像“一个会自动敲命令的终端伙伴”
远程 / 后台 Agent
特点:
- 命令在远程 VM / container / worker 上执行
- repo 常常是重新 clone 过去的
- 环境是远端镜像、远端脚本、远端 worker 决定的
- 默认不一定等于你本机的
PATH、nvm、Xcode、Android SDK
这也是为什么很多人会说:
- “本地 agent 能跑,cloud agent 跑不了”
- “桌面端能找到 node,后台 agent 找不到”
不是模型能力变了,而是执行环境换了。
9. Codex、Cursor、Claude Code 到底是不是一回事
结论先说:
它们在“产品抽象”上很像,但在“执行落点”上不一定一样。
9.1 Codex Desktop
结合本机会话和 OpenAI 官方资料,可以这样理解:
- 本地线程里,Codex 可以直接在本机工作区里读写、执行命令
- 桌面应用负责线程、任务、diff、并行 agent、worktree 等编排
- OpenAI 官方也明确把 Codex app 定义成多 agent 的 command center,并支持 worktrees / cloud environments
对你来说,最重要的是:
- 本地线程不等于“裸终端”
- 但它确实可以非常贴近你本机开发环境
9.2 Cursor
从 Cursor 官方公开资料能确认两件事:
- Cursor 有 cloud agents
- Cloud agents 运行在隔离虚拟机里;如果是 self-hosted 模式,则由 worker 接收来自 Cursor 云端的工具调用并在你的基础设施上执行
这意味着:
- Cursor 不是只有一种执行模式
- “编辑器里这个 agent”和“云上 background agent”不能混为一谈
基于官方资料和同类产品的共同结构,可以做一个合理推断:
- Cursor 的本地前台 agent 更像“挂在编辑器里的本地工具执行器”
- Cursor 的 cloud/background agent 则明显是“远程执行环境”
这里的“本地前台 agent”表述是基于产品结构的推断,不是本文直接实测出的结论。
9.3 Claude Code
Claude Code 官方公开形态更偏 CLI / agent harness,而不是一个像 Codex app 那样完整强调“桌面 command center”的产品外壳。
从 Anthropic 官方文档可以确认:
- Claude Code 有层级化设置
~/.claude/settings.json.claude/settings.json.claude/settings.local.json
- Bash 工具会“在你的环境里执行 shell 命令”
- 会话和工具状态可以本地保存并恢复
所以如果你是在本机终端或某个桌面壳里跑 Claude Code,本质上它仍然高度依赖宿主机器的 shell 环境。
9.4 最实用的理解方式
不要把它们简单理解成“所有桌面端都一样”。
更准确的理解是:
- 它们共享同一类设计思路
- 模型 + 工具 + 权限 + 工作区 + 自动化
- 但具体执行在哪
- 你本机
- IDE 内嵌终端
- 桌面 app 子进程
- 远程 worker
- 云端隔离 VM 才是真正决定环境差异的关键
10. 你这台机器上,最值得记住的几个结论
10.1 你的 node 当前来自 nvm
当前实测:
node -> /Users/huchu/.nvm/versions/node/v20.20.2/bin/node
node -v -> v20.20.2
10.2 你把“登录环境”推广到了所有 zsh
因为 ~/.zshenv 主动 source ~/.zprofile,所以你的非交互 zsh 也能拿到完整开发环境。
这对 Agent 非常友好。
10.3 你的 alias 仍然只属于交互 shell
setup-flutter 在 ~/.zshrc 里,所以:
- 终端交互场景能用
- Codex 当前这种
zsh -lc场景默认不能用
10.4 你现在有 PATH 重复问题
问题不在于“能不能用”,而在于“长期可维护性”。
建议以后考虑做两件事中的一个:
- 把真正的环境变量初始化抽到单独文件,比如
~/.shell_env~/.zshenv和~/.zprofile都去 source 它- 但只在一个地方真正做 PATH 去重
- 使用 zsh 的去重能力
- 例如
typeset -U path PATH
- 例如
如果你愿意,我可以下一步直接帮你把这套配置整理成“不重复 PATH、又兼容 Agent”的版本。
11. 以后自己排查环境问题,用这些命令最快
11.1 看当前 shell 是谁拉起来的
echo "$SHELL"
ps -p $$ -o ppid=
ps -p $$ -o command=
11.2 看父进程是谁
ppid=$(ps -o ppid= -p $$ | tr -d ' ')
ps -p "$ppid" -o command=
11.3 看当前环境变量
env | sort
11.4 看某个命令到底命中了谁
type node
which node
whence -va node
11.5 看 nvm / pyenv / brew 是否真的在当前 shell 生效
type nvm
type pyenv
type brew
echo "$PATH"
11.6 对比不同 shell 模式
zsh -lc 'type node; node -v'
zsh -ic 'type node; node -v'
zsh -lc 'alias'
zsh -ic 'alias'
这组对比非常适合回答一句话:
“这个问题,是 login / interactive 差异造成的吗?”
12. 官方资料与参考链接
12.1 OpenAI / Codex
- Codex app 介绍页:
- Codex 产品页:
OpenAI 在这些页面里明确提到:
- Codex app 是多 agent 的 command center
- 支持 worktrees
- 也支持 cloud environments
12.2 Cursor
- Cursor self-hosted cloud agents:
Cursor 官方在这篇文章里明确写到:
- cloud agents 运行在隔离虚拟机里
- self-hosted 模式下,worker 接收来自 Cursor 云端的工具调用并执行
12.3 Anthropic / Claude Code
- Claude Code settings:
- Claude Code tutorials / session resume:
Anthropic 官方在这些文档里明确写到:
- Claude Code 有用户级、项目级、本地项目级配置
- Bash 工具是在你的环境里执行命令
- 会话历史和工具状态可以在本地恢复
13. 最后的心智模型
以后你可以用下面这句话快速判断一个 Agent 到底会不会“像你的终端”:
先问它的命令是在哪台机器、由哪个父进程、用哪种 shell 模式、带着哪份 PATH、在什么权限下执行的。
这 5 个问题一旦想清楚,大多数“为什么这里能跑、那里不能跑”的问题都会变得非常直接。