siyuan-note/siyuan · error

environment

Error message

environment: %w

What it means

Before spawning a stdio MCP server, buildStdioEnvironment assembles the subprocess environment from InheritEnv and Env entries, validating names and resolving {{secrets.*}}/{{vars.*}} interpolation. If validation or assembly fails (empty/invalid/duplicate names, bad key characters), connectStdio wraps the failure with this "environment:" prefix. The process is never started.

Solutions

  1. Fix the offending Env/InheritEnv entry: each name must be non-empty and contain no '=' or NUL
  2. Move the value out of the name — write {"Env": {"FOO": "bar"}} not {"FOO=bar": "..."}
  3. Remove duplicate entries from InheritEnv (Windows compares case-insensitively)
  4. Verify any {{secrets.NAME}}/{{vars.NAME}} references resolve to defined secrets/variables

Example fix

// before
"env": {"API_KEY=x": "abc"}
// after
"env": {"API_KEY": "abc"}
Defensive patterns

Strategy: validation

Validate before calling

for name := range server.Env {
    if name == "" || strings.ContainsAny(name, "=\x00") {
        return fmt.Errorf("invalid env name %q for server %s", name, server.Name)
    }
}
for _, name := range server.InheritEnv {
    if strings.ContainsAny(name, "=\x00") {
        return fmt.Errorf("invalid inheritEnv name %q", name)
    }
}

Type guard

null

Try / catch

if _, err := buildStdioEnvironment(server, os.LookupEnv, resolve, runtime.GOOS); err != nil {
    return fmt.Errorf("check Env/InheritEnv for server %q: %w", server.Name, err)
}

Prevention

When it happens

Trigger: connectStdio calls buildStdioEnvironment, which calls validateMCPServerEnvironment; an InheritEnv or Env entry has an empty name, a name containing '=' or NUL, a duplicate inherited name (case-insensitive on Windows), or an invalid value for the target OS.

Common situations: Config with an Env key written as "FOO=bar" instead of key "FOO", an empty key from malformed JSON, the same variable listed twice in InheritEnv, or secrets referencing undefined secret names.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/8baeda074f3c92da. Report an issue: GitHub.

Appendix: source

Thrown at kernel/mcp/client/mcp.go:463

	}
}

func connectStdio(ctx context.Context, client *mcp.Client, server conf.MCPServer) (*mcp.ClientSession, *exec.Cmd, error) {
	if server.Command == "" {
		return nil, nil, fmt.Errorf("command is required for stdio server")
	}

	cmd := exec.Command(server.Command, server.Args...)
	// stdio 环境变量插值不受密钥 AllowedHosts 约束:目标是本地子进程而非网络主机,管理员在 Env 中
	// 引用 {{secrets.NAME}} 本身就是对该服务器的显式授权,与直接写入明文属于同一信任级别。
	cmdEnv, err := buildStdioEnvironment(server, os.LookupEnv, func(value string) string {
		if model.Conf == nil {
			return value
		}
		return conf.ResolveSecretsVars(model.Conf.Secrets, model.Conf.Variables, value)
	}, runtime.GOOS)
	if err != nil {
		return nil, nil, fmt.Errorf("environment: %w", err)
	}
	cmd.Env = cmdEnv
	stdin, err := cmd.StdinPipe()
	if err != nil {
		return nil, nil, fmt.Errorf("stdin pipe: %w", err)
	}
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, nil, fmt.Errorf("stdout pipe: %w", err)
	}
	cmd.Stderr = io.Discard

	if err := cmd.Start(); err != nil {
		return nil, nil, fmt.Errorf("start command: %w", err)
	}

	connectCtx, connectCancel := context.WithTimeout(ctx, serverTimeout(server))
	defer connectCancel()

View on GitHub (pinned to 9f775e8a12)