{"record":{"id":"f8f8960c1a6d1021","repo":"siyuan-note/siyuan","slug":"stdin-pipe-w","errorCode":null,"errorMessage":"stdin pipe: %w","messagePattern":"stdin pipe: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/client/mcp.go","lineNumber":468,"sourceCode":"\t\treturn nil, nil, fmt.Errorf(\"command is required for stdio server\")\n\t}\n\n\tcmd := exec.Command(server.Command, server.Args...)\n\t// stdio 环境变量插值不受密钥 AllowedHosts 约束：目标是本地子进程而非网络主机，管理员在 Env 中\n\t// 引用 {{secrets.NAME}} 本身就是对该服务器的显式授权，与直接写入明文属于同一信任级别。\n\tcmdEnv, err := buildStdioEnvironment(server, os.LookupEnv, func(value string) string {\n\t\tif model.Conf == nil {\n\t\t\treturn value\n\t\t}\n\t\treturn conf.ResolveSecretsVars(model.Conf.Secrets, model.Conf.Variables, value)\n\t}, runtime.GOOS)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"environment: %w\", err)\n\t}\n\tcmd.Env = cmdEnv\n\tstdin, err := cmd.StdinPipe()\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"stdin pipe: %w\", err)\n\t}\n\tstdout, err := cmd.StdoutPipe()\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"stdout pipe: %w\", err)\n\t}\n\tcmd.Stderr = io.Discard\n\n\tif err := cmd.Start(); err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"start command: %w\", err)\n\t}\n\n\tconnectCtx, connectCancel := context.WithTimeout(ctx, serverTimeout(server))\n\tdefer connectCancel()\n\ttransport := &mcp.IOTransport{Reader: stdout, Writer: stdin}\n\tsession, err := client.Connect(connectCtx, transport, nil)\n\tif err != nil {\n\t\tcmd.Process.Kill()\n\t\tcmd.Wait()","sourceCodeStart":450,"sourceCodeEnd":486,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/mcp/client/mcp.go#L450-L486","documentation":"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.","triggerScenarios":"connectStdio calls cmd.StdinPipe() and the underlying os.Pipe syscall fails (Errno set), e.g. when the process has exhausted its file-descriptor limit.","commonSituations":"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.","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"],"exampleFix":"# before\nulimit -n  # 256, too low\n# after\nulimit -n 4096  # or set LimitNOFILE=4096 in the service unit","handlingStrategy":"retry","validationCode":"var r syscall.Rlimit\nsyscall.Getrlimit(syscall.RLIMIT_NOFILE, &r)\n// if r.Cur is small, raise it before spawning many stdio servers","typeGuard":"null","tryCatchPattern":"if err := connectStdio(ctx, client, server); err != nil {\n    if strings.Contains(err.Error(), \"stdin pipe\") {\n        time.Sleep(backoff) // fds may free up; or raise RLIMIT_NOFILE and retry\n        return retryLater\n    }\n    return err\n}","preventionTips":["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"],"tags":["mcp","stdio","os-resources","file-descriptors"],"backgroundTag":"resource-exhaustion","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}