{"record":{"id":"7dd7ddd1434ffd89","repo":"siyuan-note/siyuan","slug":"stdout-pipe-w","errorCode":null,"errorMessage":"stdout pipe: %w","messagePattern":"stdout pipe: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"kernel/mcp/client/mcp.go","lineNumber":470,"sourceCode":"\n\tcmd := exec.Command(server.Command, server.Args...)\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()\n\t\treturn nil, cmd, fmt.Errorf(\"connect: %w\", err)\n\t}\n\n\treturn session, cmd, nil","sourceCodeStart":452,"sourceCodeEnd":488,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/mcp/client/mcp.go#L452-L488","documentation":"Returned when exec.Cmd.StdoutPipe() fails after the stdin pipe was created successfully. StdoutPipe allocates a second OS pipe for reading the child's output; failure is environmental and structurally identical to the stdin-pipe case. The wrapped OS error is preserved.","triggerScenarios":"connectStdio calls cmd.StdoutPipe() right after cmd.StdinPipe() and the OS cannot allocate another pipe handle. File-descriptor exhaustion, handle quota exhaustion on Windows, or a transient OS resource shortage.","commonSituations":"Same conditions as the stdin-pipe error: too many stdio MCP servers already running, RLIMIT_NOFILE or Windows handle quota exhausted, container with restrictive ulimits. Seeing this one specifically means the stdin pipe succeeded and only the second allocation failed.","solutions":["Raise the file-descriptor / handle limit for the kernel process (ulimit -n, LimitNOFILE, Docker --ulimit nofile=).","Lower the count of concurrent stdio MCP servers configured.","Inspect the wrapped error for the exact errno; if it is not a resource error, capture the OS message and report it, because StdoutPipe almost never fails otherwise.","Note: when this error fires, the stdin pipe was already created but the command has not started, so no child cleanup is required here."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"import \"syscall\"\nfunc fdBudget() int {\n    var r syscall.Rlimit\n    if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &r); err != nil { return -1 }\n    return int(r.Cur)\n}","typeGuard":null,"tryCatchPattern":"if errors.Is(err, syscall.EMFILE) || errors.Is(err, syscall.ENFILE) {\n    // backoff and retry; the stdin pipe succeeded so budget is tight\n}","preventionTips":["Raise RLIMIT_NOFILE / Windows handle quota for the kernel.","Keep concurrent stdio MCP server count modest."],"tags":["mcp","stdio","os-resource","file-descriptors"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}