第 4 课:Bash / PowerShell 与 Agent Runtime——为什么同一条命令在不同环境里表现完全不同
今天开始补真正支撑 Agent 的操作系统基础。目标不是学一堆 Shell 语法,而是看懂 Agent 到底怎样启动进程、传环境变量、接收 stdout/stderr、处理超时和当前目录。
这部分非常重要,因为大量“Agent 失败”其实不是模型问题,而是 进程和执行环境问题。
先记住 Agent 调用 Shell 的真实结构:
LLM
↓
tool_call
↓
Agent Runtime
↓
创建子进程
↓
shell / executable
↓
stdin
↓
process
↓
stdout / stderr / exit code
↓
Runtime
↓
重新喂给模型
Codex 的公开 issue 就有一个很典型的问题:命令返回非 0 时,如果 stdout/stderr 没有正确回传,模型就看不到 lint、编译或测试的具体错误,自然无法继续修。citeturn992668search0
所以企业 Agent 工程里,“命令失败”还不够,必须把失败信息完整带回来。
1. cwd:Agent 到底在哪个目录执行
cwd 是:
Current Working Directory
也就是当前工作目录。
比如:
Get-Location
或者 Bash:
pwd
如果 Agent 想执行:
dotnet test
但 cwd 错了:
正确:
C:\Projects\sample-app
实际:
C:\
结果可能就是:
Could not find a project
这并不是:
dotnet坏了
模型不会C#
而只是:
cwd错误
OpenCode 2026 年就有公开 issue:Desktop 版本工具被错误地从 / 根目录执行,而不是项目目录,导致相对路径工具全部失效。citeturn992668search17
所以以后排查 Agent 命令问题,第一个问题之一就是:
当前 cwd 是什么?
2. 为什么 cd 经常让 Agent 初学者困惑
假设 Agent 第一次调用:
cd src
然后下一次调用:
pwd
你可能以为应该还是:
/project/src
但不一定。
因为很多 Agent 的 Shell Tool 每次都会:
创建新的 subprocess
因此:
Tool Call #1
shell process A
cd src
进程结束
Tool Call #2
shell process B
pwd
进程 B 根本不知道 A 执行过 cd。
Kimi CLI 2026 年的一个公开 issue 就明确涉及这个问题:Shell tool 每次启动新的 subprocess,因此单独执行 cd 只影响那个子进程。citeturn992668search22
所以:
cd src
再单独运行:
dotnet test
可能失败。
更可靠的是:
cd src && dotnet test
或者 Runtime 本身设置:
cwd=/project/src
这就是为什么 Agent Runtime 必须有:
working_directory
概念。
3. stdin / stdout / stderr
一个进程有三个最重要的标准流:
stdin → 输入
stdout → 正常输出
stderr → 错误/诊断输出
比如:
git status
输出一般进入:
stdout
而错误:
git abc
可能进入:
stderr
Agent Runtime 通常做类似事情:
result = subprocess.run(
command,
capture_output=True,
text=True
)
然后得到:
result.stdout
result.stderr
result.returncode
C# 类比就是:
Process process = new Process();
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
所以如果你以前做过 .NET Process,其实已经接触过 Agent Shell Tool 的底层基础。
4. stdout/stderr 为什么会造成 Agent 卡死
这个问题非常工程化。
OpenCode 2026 年出现过 Bash Tool 卡死问题:
bash -c "sleep 10 &"
看上去:
父shell已经退出
但后台子进程还继承了:
stdout
stderr
对应的 pipe 文件描述符。
Node.js 运行时可能继续等待这些流真正关闭,于是:
Agent一直认为命令没结束
哪怕 shell 本身早就退出了。citeturn992668search13turn992668search1
这是很典型的:
process lifetime
≠
stdio lifetime
问题。
企业 Agent 部署工程师必须能看懂这种现象。
5. 什么是 pipe
Shell 中:
commandA | commandB
意思是:
commandA stdout
↓
pipe
↓
commandB stdin
例如:
git status --short | grep ".cs"
可以理解成:
git status --short
↓ stdout
pipe
↓ stdin
grep ".cs"
Windows PowerShell 也有:
Get-ChildItem | Where-Object Name -Like "*.cs"
但注意:
Bash 的 pipe 主要传文本流;PowerShell 的 pipeline 经常传对象。
这是二者非常大的思维区别。
例如 PowerShell:
Get-Process | Where-Object CPU -gt 100
中间传的不只是字符串,而可能是:
Process Object
所以 PowerShell 不应该简单理解成“Windows 版 Bash”。
6. 重定向
Bash:
echo hello > out.txt
意思是:
stdout
↓
out.txt
追加:
echo hello >> out.txt
stderr:
command 2> error.log
合并 stdout + stderr:
command > output.log 2>&1
这在 Agent 场景特别常见。
例如:
dotnet test > test.log 2>&1
然后 Agent 可以:
cat test.log
分析。
不过现代 Agent Runtime 更推荐直接捕获进程输出,不一定必须经由文件。
7. PowerShell 对应写法
PowerShell 也支持:
dotnet test > test.log
以及:
dotnet test 2> error.log
查看:
Get-Content test.log
但 Agent 经常直接让 Runtime 捕获:
stdout
stderr
而不落磁盘。
这里要培养一个习惯:
当你看到 Agent 卡在“Executing command...”时,要考虑它究竟是在等进程退出,还是在等 stdout/stderr pipe 关闭。
8. stdin 为什么也会把 Agent 卡住
假设 Agent 执行:
git push
Git 发现需要用户名密码,于是:
Username:
它等待:
stdin
但是 Agent Runtime 并没有人工终端。
结果就是:
一直卡着
Kimi CLI 最近的 changelog 就专门修过这一类问题:
- Shell tool 会关闭 stdin;
- 设置
GIT_TERMINAL_PROMPT=0; - 需要凭据的 Git 命令不再无限等待,而是尽快失败并把错误返回。citeturn992668search2
这就是很优秀的 Agent Runtime 设计。
从:
等待人工输入
变成:
fail fast
↓
stderr
↓
LLM判断
9. 环境变量
你会经常看到:
OPENAI_API_KEY
ANTHROPIC_API_KEY
PATH
HTTP_PROXY
HTTPS_PROXY
CUDA_VISIBLE_DEVICES
这些都属于:
environment variables
Bash:
export MY_VAR=hello
echo $MY_VAR
PowerShell:
$env:MY_VAR="hello"
$env:MY_VAR
Agent Runtime 启动子进程时,会把环境变量传进去。
因此:
终端里命令能运行
Agent里却不行
一个很重要的原因就是:
environment不同
例如你自己的 PowerShell:
PATH里有:
C:\Program Files\dotnet
但 Docker 或 Agent sandbox 里:
PATH里没有
于是:
dotnet: command not found
10. PATH 到底是什么
假设 Agent 执行:
git
系统并不会凭空知道 git.exe 在哪里。
它会按照 PATH 逐个寻找。
Windows 可能类似:
C:\Windows\System32
C:\Program Files\Git\cmd
C:\Program Files\dotnet
...
Linux/macOS:
/usr/local/bin
/usr/bin
/bin
...
查看:
Bash:
echo $PATH
PowerShell:
$env:PATH
寻找命令:
Bash:
which git
PowerShell:
Get-Command git
如果 Agent 报:
git not found
工程排查顺序应该是:
git是否安装
↓
PATH是否包含
↓
Agent env与用户shell是否一致
↓
sandbox/container是否有git
11. Codex 的一个非常真实 Windows Bug
2026 年 Codex 在 Windows 上有一个很有代表性的 issue:
内部为了终止进程树,会调用:
taskkill /T /F /PID ...
但 taskkill 的 stdout 被意外泄漏到 Codex 自己的 stdout。
更麻烦的是,不同语言 Windows 输出可能不同。
例如中文系统输出中文。
下游工具本来期待:
纯 JSONL
结果中间突然混入操作系统文本,于是 parser 直接崩。citeturn992668search12
这里暴露了一个重要企业问题:
stdout 是接口
如果某个程序承诺:
stdout = JSONL协议
那么调试文字就应该放:
stderr
否则会污染协议。
这是 CLI 工程和 Agent 平台非常重要的规范。
12. 为什么 codex exec 在 CI 环境可能表现不同
另一个 2026 年 Codex issue 显示:
有TTY
→ 正常
没有controlling TTY
→ 可能异常退出
尤其是在:
CI
background process
daemon
subprocess
环境中更加明显。citeturn992668search8
这涉及:
TTY
PTY
non-interactive mode
今天只需要先记:
有终端
≠
无终端
很多 CLI 工具会检测:
isatty()
然后改变行为。
所以:
我在终端运行正常
GitHub Actions里面失败
非常常见。
后面 CI/CD 课程会专门讲。
13. timeout 有两层
假设:
timeout 10 myprogram
外面 Agent Runtime 自己又设置:
tool timeout = 5秒
结果:
外层5秒先杀掉
模型看到:
Tool timed out
而不是程序原本想返回的:
exit code 124
Codex 之前就有公开 issue 讨论这种“tool timeout 抢先终止 inner timeout”的情况。citeturn992668search4
所以以后看到 timeout:
不要只问:
超时了吗?
而要问:
是哪一层 timeout?
可能是:
LLM API timeout
Agent tool timeout
shell timeout
application timeout
HTTP timeout
database timeout
Kubernetes deadline
企业排错经常就是不断确定:
是哪一层
14. Kimi / OpenCode / Codex 对比
今天可以先做这样一个工程化比较:
| 问题 | Codex | OpenCode | Kimi Code |
|---|---|---|---|
| Shell执行 | Rust runtime/tool | Bash tool | Shell tool |
| cwd问题 | 有独立runtime/workspace概念 | Desktop曾有cwd bug | Shell调用同样依赖工作目录 |
| stdout/stderr | 必须正确回传模型 | 后台进程继承pipe可能卡住 | 近期修复stdin交互阻塞 |
| timeout | tool层超时曾出现语义问题 | background process可能影响结束判断 | 支持避免交互命令无限等待 |
| Windows细节 | taskkill/stdout等实际bug很多 | Desktop与CLI环境差异 | 持续修Windows subprocess问题 |
这些不是“哪个工具差”。
恰恰说明:
Coding Agent 本质上是一个复杂的本地执行系统。
15. 企业 Agent 的 Shell Executor 应该长什么样
以后自己写 Mini Agent 时,我们不会简单:
os.system(command)
而应该逐渐变成:
class ShellResult:
stdout: str
stderr: str
exit_code: int
timed_out: bool
duration_ms: int
async def execute(
command,
cwd,
env,
timeout
) -> ShellResult:
...
甚至还要增加:
max_output_bytes
allowed_commands
sandbox
network_policy
stdin_policy
kill_process_tree
最终:
LLM
↓
Shell Tool
↓
Policy
↓
Sandbox
↓
Process Manager
↓
stdout/stderr
↓
Trace
这就是企业 Agent Runtime。
今日 10 分钟实验
Windows PowerShell:
Get-Location
Get-Command git
$env:PATH
然后:
git --version
$LASTEXITCODE
再试一个错误命令:
git abcxyz
$LASTEXITCODE
观察:
stdout
stderr
exit code
再:
Set-Location ..
Get-Location
注意 cwd 变化。
然后测试子进程工作目录:
powershell -Command "Set-Location ..; Get-Location"
Get-Location
你会发现:
子PowerShell改变目录
不会永久改变:
父PowerShell目录
这就是 Agent 每次启动新 subprocess 时 cd 失效问题的最小实验。
macOS/Linux:
pwd
which git
echo $PATH
然后:
bash -c 'cd ..; pwd'
pwd
同样观察:
child process cwd
≠
parent process cwd
今日企业故障案例
现象:
本地:
npm test
成功
Agent:
npm test
command not found
不要重新 Prompt。
正确故障树:
Agent执行失败
│
├─ cwd正确?
│
├─ npm安装了吗?
│
├─ PATH是否相同?
│
├─ Agent使用哪个shell?
│
├─ sandbox内能看到node吗?
│
├─ Docker镜像里有没有npm?
│
└─ CI环境是否不同?
如果最终发现:
宿主机Node安装在:
/opt/homebrew/bin/node
Agent container:
PATH没有/opt/homebrew/bin
那么这就是:
runtime environment问题
而不是:
LLM问题
岗位能力映射
今天开始进入真正的 Agent Runtime / Deployment Engineer 技能:
process、cwd、env、PATH、stdin/stdout/stderr、pipe、redirect、timeout、TTY、process tree。
后面 Docker、Kubernetes、CI/CD 其实都会不断重复今天这些概念。例如 Docker 的:
WORKDIR
ENV
ENTRYPOINT
STDOUT日志
signal
PID 1
本质都和今天紧密相关。
今日检查题
- 为什么 Agent 单独执行一次
cd src,下一次 Tool Call 不一定还在src? - 为什么后台进程即使父 Shell 已经退出,Agent Tool 仍可能“卡住”?
PATH对 Agent 有什么作用?- 为什么企业 CLI 应该把协议数据放 stdout、诊断信息放 stderr?
- “命令超时”为什么不能直接说明应用本身超时?
答案核心:Shell Tool 很可能每次是新子进程;后台进程可能继续持有 stdout/stderr pipe;PATH 决定可执行程序搜索路径;stdout 可能是机器接口,stderr 应承载诊断;超时可能发生在 LLM、Agent Tool、Shell 或应用的不同层。
下一课进入 Linux 进程、PID、信号、前后台任务和进程树,直接解释 Agent 为什么需要 kill process tree,以及 Docker/Kubernetes 为什么特别关心 PID 1。