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
- Fix the offending Env/InheritEnv entry: each name must be non-empty and contain no '=' or NUL
- Move the value out of the name — write {"Env": {"FOO": "bar"}} not {"FOO=bar": "..."}
- Remove duplicate entries from InheritEnv (Windows compares case-insensitively)
- 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
- Treat Env as a name-to-value map: names never contain '='
- Avoid duplicate InheritEnv entries; on Windows names compare case-insensitively
- Verify secret/variable interpolations resolve before saving the server config
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)