[bug] env.yaml 缺少顶层 variables: 时被静默当成空,导致所有工具的 MCP 注入全部被跳过,且 doctor 仍报通过
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 4.8k
- Forks
- 342
- Avg merge
- 13h 48m
- Merged PRs (30d)
- 211
Description
English summary —
EnvYamlSchemaisz.object({ variables: z.array(...).default([]) }).
Aenv/env.yamlwritten in the natural shorthand form (JIRA_PASSWORD: "x") has no
top-levelvariables:key, so zod strips it and the.default([])silently yields an
empty array.EnvHandler.pullItem()then hitsif (envConfig.variables.length === 0) return;
and returns with no output of any kind. Result: the variable table is empty for the
whole run, every MCP entry referencing${VAR}is skipped across all tools with
unresolved variable(s), andteamai doctorstill printsAll checks passed!.
Nothing in the output points atenv.yaml's shape being the cause.
Description
env/env.yaml 用最自然的写法时:
JIRA_PASSWORD: "<secret>"
teamai pull 会完全静默地把变量表当成空的,连带把所有工具的 MCP 注入全部跳过,但输出里没有任何一行指向 env.yaml 的结构问题。
现象链:
$ teamai pull
[mcp] claude/jira: skipped — unresolved variable(s): JIRA_PASSWORD
[mcp] cursor/jira: skipped — unresolved variable(s): JIRA_PASSWORD
[mcp] codebuddy/jira: skipped — unresolved variable(s): JIRA_PASSWORD
...(每个启用的工具各一行)
$ teamai mcp list
secrets: (none)
installed: (none)
$ teamai doctor
✔ All checks passed!
于是用户看到的是「MCP 装不上」,自然去查 MCP 定义、查工具配置、查网络——而真正的原因在 env/env.yaml 的顶层键名上,且这条信息在整个输出里一次都没出现过。
改成 variables: 数组后,同样的定义立刻全部注入成功:
variables:
- key: JIRA_PASSWORD
value: "<secret>"
$ teamai mcp list
secrets: JIRA_PASSWORD (all set)
installed: claude, cursor, codebuddy
也就是说:一个 YAML 键名的差异,决定全部 MCP 注入的成败,而 CLI 对这两种写法给出的反馈完全一样(都是零)。
Root cause
EnvYamlSchema 把 variables 设成了带默认值的数组,而 zod 默认会剥掉未声明的顶层键,所以简写形式不仅不报错,还会"成功地"解析出一个空数组:
EnvYamlSchema = z.object({
variables: z.array(EnvVariableSchema).default([])
});
env handler 拿到空数组后直接 return,没有任何 log:
async pullItem(item, teamConfig, localConfig) {
const content = await readFileSafe(item.sourcePath);
if (!content) return;
let envConfig;
try {
const raw = YAML7.parse(content);
envConfig = EnvYamlSchema.parse(raw);
} catch (e) {
log.warn(`Invalid env.yaml format: ${e.message}`); // 只有 YAML 语法错才走到这里
return;
}
if (envConfig.variables.length === 0) return; // ← 简写形式落在这,静默返回
...
}
注意 catch 里的 Invalid env.yaml format 只在 YAML 语法错误时触发。简写形式是合法 YAML,只是结构不符,所以连这句警告都不会出现。
同样地,countEnvVars() 也把异常吞掉并返回 0:
async countEnvVars(sourcePath) {
const content = await readFileSafe(sourcePath);
if (!content) return 0;
try {
const raw = YAML7.parse(content);
const envConfig = EnvYamlSchema.parse(raw);
return envConfig.variables.length;
} catch {
return 0; // 静默
}
}
下游 MCP 侧的失败信息也只有一句,指向的是变量名而不是源头:
const { def: resolved, missing } = resolvePlaceholders(raw, vars);
if (missing.length > 0) {
changes.push({
tool: target.tool,
server: raw.name,
action: "skipped",
reason: `unresolved variable(s): ${missing.join(", ")}`
});
continue;
}
而 doctor 里没有任何一项检查会覆盖 env.yaml 的可解析性/非空性,所以三项检查全绿,和实际状态完全脱节。
Reproduction
-
Windows / macOS 任一平台,
teamai0.24.0,团队仓库里放一个用简写写法的env/env.yaml:JIRA_PASSWORD: "whatever" -
团队仓库里有一个引用
${JIRA_PASSWORD}的 MCP 定义(例如mcp/mcp.yaml中的 jira 服务,env.JIRA_PASSWORD: "${JIRA_PASSWORD}")。 -
teamai pull→ 每个启用的工具各打一行skipped — unresolved variable(s): JIRA_PASSWORD,没有任何一行提到 env.yaml。 -
teamai mcp list→secrets: (none)/installed: (none)。 -
teamai doctor→✔ All checks passed!(假通过)。 -
只把 env.yaml 改成
variables:数组,其它一律不动,重跑teamai pull→ 全部注入成功。
Environment
- OS: Windows 11 25H2 (10.0.26200)
- Node.js:
v22.22.2 - teamai:
0.24.0 - Provider: GitHub
- AI tool(s): WorkBuddy / CodeBuddy(但跳过行为对所有工具生效)
Suggested fix
按性价比排序,任一条都能把静默失败变成可诊断的失败:
- 空数组要出声。 把
if (envConfig.variables.length === 0) return;改成带警告的返回,例如:
log.warn("env/env.yaml resolved to 0 variables. Expected a top-levelvariables:list of {key, value}.")。
这一句就能把绝大多数此类问题在 5 秒内定位。 - 识别并拒绝简写形式。 把
EnvYamlSchema收紧为.strict(),或在解析前检查顶层是否含有非variables的键;也可主动兼容KEY: value的平铺写法(很多用户的第一直觉就是那样写),二选一都比静默留空好。 countEnvVars()不要吞异常。 空结果与解析失败是两件事,现在都返回0,调用方无从区分。- 给
doctor加一项检查。 断言「env.yaml 存在 ⇒ 解析出的变量数 > 0 且已写入env.sh」,否则报错并给出修复提示。目前三项检查全绿而 MCP 一个都没装上的组合,是最容易被误判成"没问题"的状态。
Logs
简写形式下的完整输出(无任何 env.yaml 相关提示)
$ cat env/env.yaml
JIRA_PASSWORD: "***"
$ teamai pull
...
[mcp] claude/jira: skipped — unresolved variable(s): JIRA_PASSWORD
[mcp] cursor/jira: skipped — unresolved variable(s): JIRA_PASSWORD
[mcp] codebuddy/jira: skipped — unresolved variable(s): JIRA_PASSWORD
$ teamai mcp list
secrets: (none)
installed: (none)
$ teamai doctor
✔ All checks passed!
改成 variables: 数组后(其它未动)
$ cat env/env.yaml
variables:
- key: JIRA_PASSWORD
value: "***"
$ teamai mcp list
secrets: JIRA_PASSWORD (all set)
installed: claude, cursor, codebuddy
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating EnvYamlSchema, EnvHandler.pullItem(), countEnvVars(), and the doctor checks described in the report, then reproduce the shorthand env/env.yaml case with teamai pull, mcp list, and doctor. Trace how the empty parsed variables reach MCP resolution and reporting; done means the invalid shape is surfaced with an actionable env.yaml diagnostic and doctor no longer reports success for this state.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript, yaml
- Domain
- cli
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100