Skip to content

[bug] env.yaml 缺少顶层 variables: 时被静默当成空,导致所有工具的 MCP 注入全部被跳过,且 doctor 仍报通过 #662

Description

@CarlosWonMore

English summaryEnvYamlSchema is z.object({ variables: z.array(...).default([]) }).
A env/env.yaml written in the natural shorthand form (JIRA_PASSWORD: "x") has no
top-level variables: key, so zod strips it and the .default([]) silently yields an
empty array. EnvHandler.pullItem() then hits if (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), and teamai doctor still prints All checks passed!.
Nothing in the output points at env.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

EnvYamlSchemavariables 设成了带默认值的数组,而 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

  1. Windows / macOS 任一平台,teamai 0.24.0,团队仓库里放一个用简写写法的 env/env.yaml

    JIRA_PASSWORD: "whatever"
  2. 团队仓库里有一个引用 ${JIRA_PASSWORD} 的 MCP 定义(例如 mcp/mcp.yaml 中的 jira 服务,env.JIRA_PASSWORD: "${JIRA_PASSWORD}")。

  3. teamai pull → 每个启用的工具各打一行 skipped — unresolved variable(s): JIRA_PASSWORD没有任何一行提到 env.yaml

  4. teamai mcp listsecrets: (none) / installed: (none)

  5. teamai doctor✔ All checks passed!(假通过)。

  6. 只把 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

按性价比排序,任一条都能把静默失败变成可诊断的失败:

  1. 空数组要出声。if (envConfig.variables.length === 0) return; 改成带警告的返回,例如:
    log.warn("env/env.yaml resolved to 0 variables. Expected a top-level variables: list of {key, value}.")
    这一句就能把绝大多数此类问题在 5 秒内定位。
  2. 识别并拒绝简写形式。EnvYamlSchema 收紧为 .strict(),或在解析前检查顶层是否含有非 variables 的键;也可主动兼容 KEY: value 的平铺写法(很多用户的第一直觉就是那样写),二选一都比静默留空好。
  3. countEnvVars() 不要吞异常。 空结果与解析失败是两件事,现在都返回 0,调用方无从区分。
  4. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions