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

  1. Raise the file-descriptor limit (ulimit -n or systemd LimitNOFILE) and retry
  2. Find and fix fd leaks — check for many lingering MCP child processes with lsof and kill stale ones
  3. Reduce the number of simultaneously connected stdio MCP servers
  4. 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

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)