kubernetes/kops · error

error creating sftp client (at stdin pipe): %w

Error message

error creating sftp client (at stdin pipe): %w

What it means

SSHPath.newClient in util/pkg/vfs/sshfs.go wraps errors from s.StdinPipe() with this message on the sudo=true path. After opening an SSH session, pipes for stdin/stdout are created to talk to the remote sftp-server process; a failure here means the session could not provide the stdin pipe, indicating the session channel is in a bad or already-closed state.

Source

Thrown at util/pkg/vfs/sshfs.go:72

}

func (p *SSHPath) newClient(ctx context.Context) (*sftp.Client, error) {
	if !p.sudo {
		sftpClient, err := sftp.NewClient(p.client)
		if err != nil {
			return nil, fmt.Errorf("error creating sftp client: %w", err)
		}

		return sftpClient, nil
	}
	s, err := p.client.NewSession()
	if err != nil {
		return nil, fmt.Errorf("error creating sftp client (in new-session): %w", err)
	}

	stdin, err := s.StdinPipe()
	if err != nil {
		return nil, fmt.Errorf("error creating sftp client (at stdin pipe): %w", err)
	}
	stdout, err := s.StdoutPipe()
	if err != nil {
		return nil, fmt.Errorf("error creating sftp client (at stdout pipe): %w", err)
	}

	err = s.Start("sudo /usr/lib/openssh/sftp-server")
	if err != nil {
		return nil, fmt.Errorf("error creating sftp client (executing 'sudo /usr/lib/openssh/sftp-server'): %w", err)
	}

	c, err := sftp.NewClientPipe(stdout, stdin)
	if err != nil {
		return nil, fmt.Errorf("error starting sftp (executing 'sudo /usr/lib/openssh/sftp-server'): %w", err)
	}
	return c, nil
}

View on GitHub (pinned to 4c8573c808)

Solutions

  1. Re-establish the ssh.Client and retry the operation — the connection was likely closed mid-setup
  2. Avoid sharing one ssh.Client across concurrent goroutines without synchronization
  3. Enable SSH keepalives to keep the connection alive between VFS operations
  4. Check sshd server logs for why the channel was torn down (e.g. session limits)

Example fix

// before
client, err := ssh.Dial("tcp", host, cfg) // reused long after dial
// after
// keep connection alive and re-dial on failure:
client, err := ssh.Dial("tcp", host, cfg)
if err != nil {
	return nil, fmt.Errorf("re-establishing ssh connection: %w", err)
}
// set ClientConfig.HostKeyCallback/Timeout and a KeepAlive via client.SendRequest loop
Defensive patterns

Strategy: retry

Validate before calling

// verify the connection is alive right before VFS operations
_, err := client.SendRequest("keepalive@openssh.com", true, nil)
if err != nil {
	client, err = ssh.Dial("tcp", host, cfg) // re-dial stale connection
	if err != nil { log.Fatalf("ssh connection dead: %v", err) }
}

Type guard

func isStdinPipeError(err error) bool {
	return err != nil && strings.Contains(err.Error(), "sftp client (at stdin pipe)")
}

Try / catch

err := p.ReadDir(ctx)
if err != nil && strings.Contains(err.Error(), "error creating sftp client (at stdin pipe)") {
	// connection died mid-setup: rebuild and retry once
	client, derr := ssh.Dial("tcp", host, cfg)
	if derr == nil {
		p = vfs.NewSSHPath(client, host, path, true)
		return p.ReadDir(ctx)
	}
}
return err

Prevention

When it happens

Trigger: SSHPath operations with sudo=true where s.StdinPipe() returns an error after NewSession() succeeded — typically when the underlying ssh connection was closed between opening the session and requesting the pipe, or the session was rejected/reaped immediately by the server.

Common situations: Race with connection teardown (server closed the connection right after session setup); broken ssh.Client sharing across goroutines leading to closed channels; server abruptly closing the channel due to policy limits.

Related errors


AI-assisted analysis of kubernetes/kops@4c8573c808 (2026-09-05). Data as JSON: /api/errors/dfab81dce61995c6. Report an issue: GitHub.