siyuan-note/siyuan · error
stdin pipe
Error message
stdin pipe: %w
What it means
After building the environment, connectStdio creates an os.Pipe for the child's stdin via cmd.StdinPipe(). This error wraps the os.Pipe failure with the "stdin pipe:" prefix. Pipe creation fails only under severe OS resource exhaustion, since it precedes process start.
Solutions
- Raise the file-descriptor limit (ulimit -n or systemd LimitNOFILE) and retry
- Find and fix fd leaks — check for many lingering MCP child processes with lsof and kill stale ones
- Reduce the number of simultaneously connected stdio MCP servers
- Restart SiYuan to release leaked descriptors, then reconnect servers gradually
Example fix
# before ulimit -n # 256, too low # after ulimit -n 4096 # or set LimitNOFILE=4096 in the service unit
Defensive patterns
Strategy: retry
Validate before calling
var r syscall.Rlimit syscall.Getrlimit(syscall.RLIMIT_NOFILE, &r) // if r.Cur is small, raise it before spawning many stdio servers
Type guard
null
Try / catch
if err := connectStdio(ctx, client, server); err != nil {
if strings.Contains(err.Error(), "stdin pipe") {
time.Sleep(backoff) // fds may free up; or raise RLIMIT_NOFILE and retry
return retryLater
}
return err
} Prevention
- Run with a generous file-descriptor limit (>= 4096)
- Ensure failed connections kill and reap child processes so pipes are released
- Avoid unbounded reconnect loops that spawn new pipe pairs each attempt
When it happens
Trigger: connectStdio calls cmd.StdinPipe() and the underlying os.Pipe syscall fails (Errno set), e.g. when the process has exhausted its file-descriptor limit.
Common situations: ulimit -n (or the Windows HANDLE equivalent) exhausted by leaked file descriptors, a runaway number of concurrently spawned MCP server subprocesses each holding pipes, or a container with a very low RLIMIT_NOFILE.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/f8f8960c1a6d1021.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/mcp/client/mcp.go:468
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()
transport := &mcp.IOTransport{Reader: stdout, Writer: stdin}
session, err := client.Connect(connectCtx, transport, nil)
if err != nil {
cmd.Process.Kill()
cmd.Wait()View on GitHub (pinned to 9f775e8a12)