siyuan-note/siyuan · critical

stdout pipe: %w

Error message

stdout pipe: %w

What it means

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.

Source

Thrown at kernel/mcp/client/mcp.go:470

	cmd := exec.Command(server.Command, server.Args...)
	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, nil

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Raise the file-descriptor / handle limit for the kernel process (ulimit -n, LimitNOFILE, Docker --ulimit nofile=).
  2. Lower the count of concurrent stdio MCP servers configured.
  3. 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.
  4. Note: when this error fires, the stdin pipe was already created but the command has not started, so no child cleanup is required here.
Defensive patterns

Strategy: retry

Validate before calling

import "syscall"
func fdBudget() int {
    var r syscall.Rlimit
    if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &r); err != nil { return -1 }
    return int(r.Cur)
}

Try / catch

if errors.Is(err, syscall.EMFILE) || errors.Is(err, syscall.ENFILE) {
    // backoff and retry; the stdin pipe succeeded so budget is tight
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/7dd7ddd1434ffd89. Report an issue: GitHub.