siyuan-note/siyuan · error
stdout pipe
Error message
stdout pipe: %w
What it means
connectStdio calls cmd.StdoutPipe() to obtain the read end of the child's stdout, which the MCP IOTransport reads JSON-RPC messages from. A failure creating the underlying pipe is wrapped with the "stdout pipe:" prefix. Like the stdin-pipe error, this indicates OS-level resource exhaustion rather than a configuration problem.
Solutions
- Raise the process file-descriptor limit (ulimit -n, LimitNOFILE) and retry the connection
- Inspect for descriptor leaks: list MCP child processes (ps/lsof) and terminate stale ones
- Lower the number of concurrently configured stdio MCP servers
- Restart the SiYuan kernel to reclaim leaked pipe descriptors
Example fix
// before: unbounded reconnect loop spawning pipes each retry
// after: reuse or close previous pipes/session before reconnecting
if prev != nil { prev.Close(); prev.Cmd.Process.Kill(); prev.Cmd.Wait() } Defensive patterns
Strategy: retry
Validate before calling
var r syscall.Rlimit syscall.Getrlimit(syscall.RLIMIT_NOFILE, &r) // ensure r.Cur comfortably exceeds 3 fds per configured stdio server
Type guard
null
Try / catch
if err := connectStdio(ctx, client, server); err != nil {
if strings.Contains(err.Error(), "stdout pipe") {
// free descriptors (close stale sessions, kill children), then retry with backoff
return retryAfterCleanup
}
return err
} Prevention
- Raise RLIMIT_NOFILE in systemd units, containers, and CI sandboxes
- Audit for descriptor leaks after connection errors with lsof
- Limit the number of concurrently connected stdio MCP servers
When it happens
Trigger: connectStdio calls cmd.StdoutPipe() and the underlying os.Pipe syscall fails, typically EMFILE/ENFILE from hitting the file-descriptor limit.
Common situations: Too many concurrent MCP child processes each holding stdin/stdout pipes, leaked descriptors from earlier crashed connections, or a low hard fd limit in containers/CI sandboxes.
Related errors
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/7dd7ddd1434ffd89.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/mcp/client/mcp.go:472
// 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()
return nil, cmd, fmt.Errorf("connect: %w", err)
}
return session, cmd, nilView on GitHub (pinned to 9f775e8a12)