---
date: 2026-05-10
title: agent-claim.sh 千万别 eval 时合并 stderr —— 中文消息会被当 shell 命令报错
tags: [agent-pool, shell, stderr, learnings]
---

# 现象

```bash
eval "$(bash scripts/dev/agent-pool/agent-claim.sh chore/foo develop 2>&1)"
# /bin/bash: line 10: agent-claim:: command not found
# /bin/bash: line 11: 目录:: command not found
# /bin/bash: line 12: 分支:: command not found
# /bin/bash: line 13: 释放:: command not found
```

但 `$FFOA_AGENT_SLOT` 实际上**已经被赋值成功**——slot 也确实被 claim 了。

# 根因

`agent-claim.sh` 的 IO 契约（脚本顶部第 3 行注释明确写着）：
- **stdout**：可 eval 的 `export FFOA_AGENT_SLOT=...` 行
- **stderr**：人类可读消息（`✓ agent-claim: 成功，目录: ..., 分支: ..., 释放: ...`）

我在 eval 里加了 `2>&1` 把 stderr 合进 stdout，于是中文消息也进了 eval 输入。bash 试着把"目录:"当命令找——失败但不阻塞——`export` 行依然执行成功，所以变量赋值生效，但 stderr 一片报错。

# 正确写法

CLAUDE.md「agent pool」段给的范本就是对的：

```bash
eval "$(bash scripts/dev/agent-pool/agent-claim.sh feature/xxx)"
```

**不要** `2>&1`。如果你想看人类消息，就别合并；想忽略，用 `2>/dev/null`：

```bash
eval "$(bash scripts/dev/agent-pool/agent-claim.sh feature/xxx 2>/dev/null)"
```

# 教训

- 看到任何脚本 stdout/stderr 分离的 contract（注释里写了"stdout=可 eval / stderr=人类可读"），**eval 时绝不能 `2>&1`**。
- bash `eval` 对失败行不 fail-fast——会继续执行后面的，所以"看到一堆 command not found 但变量居然被赋值上了"是符合预期的，不是玄学。
- 同类 pattern 的脚本：任何 `--script-output` / `--exports` mode 的工具（`ssh-agent`, `direnv`, agent-claim 等）都遵循这个 contract。
