AI代理运行命令为何与你的终端表现不同
文章分析代理Shell与日常终端的八类差异,包括交互模式、登录模式及输出是否连接终端。这些差异会影响启动文件、Node版本、别名和测试行为,文中提供可在两种环境中对比执行的验证命令。
AI 深度解读
你在终端执行了 nvm use 22,AI 编程助手跑测试时却仍然用旧版 Node;平时能用的 ll 找不到,npm test 还可能一直等到超时。原文把这类问题拆成了 8 个差异:Agent 启动的 Shell,在配置加载、环境继承和终端连接上,都可能与你手动操作的终端不同。
它最有用的结论是:别把终端里已经配好的环境,当成 Agent 必然具备的条件。先检查进程实际拿到了什么,再把测试入口做成可以重复执行的命令。这个思路对前端和 Node 项目尤其直接。
同一台机器,为什么会跑出两套环境
先分清三个问题:Shell 是否交互式、是否为登录 Shell、输出是否连接到终端。这些条件彼此有关,却不能混为一谈。
Agent 执行 bash -c '...' 或 zsh -c '...' 时,通常启动的是非交互式 Shell。它不需要显示提示符,也不会自动获得你平时输入命令时的全部配置。
原文建议先用这两行检查 Shell 选项和标准输出,两边各跑一次:
echo "$-"
[ -t 1 ] && echo "stdout is a tty" || echo "stdout is not a tty"
注意第一行里的 i:它表示交互模式。第二行单独检查文件描述符 1 是否连接终端,不能用它替代第一项判断。
配置文件的差异随后就出现了。
对 zsh,.zshrc 只在交互式 Shell 中加载,.zlogin 面向登录 Shell,.zshenv 通常每次启动都会加载,使用 -f 时例外。如果工具目录只在 .zshrc 里加入 PATH,普通 zsh -c 就不会执行那段配置。
但子进程还能继承父进程的 PATH。所以原文比较环境时使用 env -i HOME="$HOME" 清空已有环境,交互版本再带上 TERM="$TERM",避免继承结果掩盖配置差异。GUI 启动的 Agent,也未必拿得到终端里的那份 PATH。
对普通的非交互式 bash -c,原文指出它跳过 ~/.bashrc,通过环境变量 BASH_ENV 指定初始化文件。即便手动加载别名文件,Bash 默认仍不展开别名,需要 shopt -s expand_aliases;定义别名和使用别名还要分处前后不同的行,因为别名展开发生在解析阶段。
终端里命令能运行,只能证明当前进程的环境够用。

Agent 记住了环境,为什么下一条命令还是失效
原文援引 Claude Code 文档称,它会在会话开始时加载对应的 Shell 配置,捕获别名、函数和选项,再应用到自己的 Bash 命令。这能弥补部分初始化差异,但有两个边界。
第一,快照有时间点。会话中途修改 .zshrc,或者安装工具时调整 PATH,已经启动的会话未必能看到。作者建议添加 alias hello='echo hi',分别让旧会话和新会话执行 type hello,比较结果。
Codex 也有默认开启的 features.shell_snapshot。原文明确说,所引用的页面没有说明快照何时刷新,因此这里需要实际验证,不能直接套用 Claude Code 的刷新规则。
第二,快照不等于连续使用同一个进程。按原文引用的说明,Claude Code 的每条 Bash 命令运行在独立进程里。上一条执行 export API_URL=https://api.example.com,下一条再执行 echo "${API_URL:-unset}",变量不会因此保留下来。工作目录可以延续,更容易让人误以为环境变量也会延续。
nvm use 22 也是同一个问题。nvm 是加载进 Shell 的函数,不是独立可执行文件;它修改的是当前 Shell 的 PATH。一次调用切换成功,并不保证下一次 npm test 使用相同版本。原文给出 nvm alias default 22,但它仍以新 Shell 正确加载 nvm 为前提。
Codex 还有另一层过滤:shell_environment_policy.inherit 可设为 all、core 或 none。即便变量存在于启动 Codex 的终端里,后续命令能否继承,也受这个配置影响。
更容易漏掉的是子进程。Agent 自己恢复了别名和函数,不代表 npm script、Git hook、Shell 脚本也获得相同初始化。GNU make 默认使用 /bin/sh,也不会从环境变量继承 SHELL;需要在 Makefile 中明确设置 SHELL := /bin/bash。
测试入口怎样才能少依赖终端状态
原文剩下的差异,集中在自动激活和终端行为上。
direnv 通常在提示符出现前检查 .envrc;mise 的 PATH 激活依赖提示符和目录切换。Agent 的命令启动后直接执行再退出,没有显示提示符的过程,相关钩子就可能不运行。
解决这类问题,原文给出了显式加载环境的入口:
direnv exec . npm test
mise exec -- npm test
注意 exec:它把环境准备和测试绑定在同一次调用中。这里是两种工具的替代方案,按项目实际使用的工具选一个;mise 也提供 mise run。
输出连接方式同样会改变程序行为。原文称,Claude Code 将命令输出流式写入工作文件,标准输出并非终端。Git 通常因此不启用分页器,颜色和进度条也可能关闭。更麻烦的是交互提问:程序等着输入,Agent 却没有提交答案,最终可能触发原文所述默认两分钟超时。应根据工具支持情况传入 --yes、-y 或 --no-input。
作业控制也不能照搬。非交互式 Bash 通常没有 i、m 标志,不能指望通过 fg、bg、Ctrl-Z 管理任务。开发服务器应使用 Agent 提供的后台机制,例如 Claude Code 的 run_in_background。
作者的修复建议可以归为两层:个人基础路径放进安静、快速的 ~/.zshenv,Bash 则用 BASH_ENV 指向小型初始化文件;项目变量通过显式包装命令加载,再把稳定入口写进 Makefile 和 CLAUDE.md 或 AGENTS.md。Claude Code 还提供 CLAUDE_ENV_FILE,原文建议启动前将其指向 Shell 脚本,或用 SessionStart hook 动态填充。
我的判断
这篇最准确的地方,是把故障拆回了进程边界:配置何时加载、变量从哪里继承、修改能活多久、程序有没有终端。相比反复让 Agent 重试,这些问题更容易验证,也更容易修复。
但原文提供的主要是文档规则和复现命令,没有给出覆盖不同系统、Shell 和 Agent 版本的对照数据。「8 个差异导致大多数问题」应视为作者的排障经验。
我更倾向于优先采用项目级显式入口。把基础 PATH 放进 .zshenv 有用,却无法覆盖 Bash、/bin/sh 和所有子进程;nvm alias default 22 会改变个人默认环境,也不能单独承担项目版本约束。直接给所有命令加交互模式,同样会引入个人配置和提示符插件,不适合作为统一修复。
作者还推广了自己开发的 Hermes IDE,并坦承产品关系。它提供真实终端承载 Agent 界面,但不会改变 Agent 内部启动工具进程的规则。界面有终端,不代表每条工具命令都有终端,这个边界交代得清楚。至于它的跨平台支持和 BSL 1.1 许可,与上述环境问题是否解决是两回事。
能拿来做什么
- 前端项目出现 Node 版本或命令路径不一致时,让终端与 Agent 都运行开头的检查,再比较实际 Node 版本和命令位置,先确认环境差异。
- 已经使用 direnv 或 mise 的项目,把
direnv exec . npm test或mise exec -- npm test写进项目说明,并统一成团队使用的测试入口。 - 单次测试需要变量时,使用
API_URL=... npm test这类同次调用;需要跨命令复用时,采用明确加载的配置文件或 Agent 支持的环境机制。 - 测试超时或开发服务器管理失败时,检查是否卡在交互输入,以及是否误用了
fg、bg;按工具能力配置非交互参数和后台执行入口。