XTLS/Xray-core · error

handshake: %w

Error message

handshake: %w

What it means

The lazy handshake (called from Read before touching c.reader) failed and the error is re-wrapped as 'handshake: %w'. This is not a distinct failure — it prefixes whichever handshake-stage error occurred (any of errors 860-875, deadline errors, or the early write/read handshake packet errors). The %w chain preserves the root cause for errors.Is/As inspection.

Source

Thrown at transport/internet/finalmask/xmc/client.go:243

	c.lifecycleMu.Lock()
	if c.closed {
		c.lifecycleMu.Unlock()
		packet.Stop()
		return net.ErrClosed
	}
	c.packet = packet
	c.reader = packet
	c.writer = packet
	c.state = clientStateProxy
	c.lifecycleMu.Unlock()

	return nil
}

func (c *clientConn) Read(b []byte) (int, error) {
	err := c.handshake()
	if err != nil {
		return 0, fmt.Errorf("handshake: %w", err)
	}

	return c.reader.Read(b)
}

func (c *clientConn) Write(b []byte) (int, error) {
	err := c.handshake()
	if err != nil {
		return 0, fmt.Errorf("handshake: %w", err)
	}

	return c.writer.Write(b)
}

func (c *clientConn) Close() error {
	c.lifecycleMu.Lock()
	c.closed = true
	packet := c.packet

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Unwrap the error chain (errors.Unwrap or %v logging) and fix the underlying stage error
  2. Run a tiny probe: call a one-byte Read immediately after connect to force the handshake early where it is easy to log
  3. Address the root cause using the matching entry for the inner error (860-875)

Example fix

// before
n, err := conn.Read(buf)
if err != nil { log.Fatal(err) } // 'handshake: authentication rejected: ...'

// after
n, err := conn.Read(buf)
if err != nil {
    log.Printf("read failed: %+v", err) // prints full wrapped chain
    var netErr net.Error
    if errors.As(err, &netErr) && netErr.Timeout() { /* handle deadline */ }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// force and inspect the handshake eagerly right after connect
probe := make([]byte, 1)
if _, err := cc.Read(probe); err != nil {
    return fmt.Errorf("xmc handshake failed at connect time: %w", err)
}

Try / catch

n, err := conn.Read(buf)
if err != nil {
    if strings.HasPrefix(err.Error(), "handshake:") {
        inner := errors.Unwrap(err)
        log.Printf("handshake stage: %v", inner) // actionable cause
    }
    return err
}

Prevention

When it happens

Trigger: The very first Read(b) on a conn returned by WrapConnClient, when the deferred xmc handshake errors for any reason: bad key, password mismatch, protocol skew, or network failure.

Common situations: Any first-use failure of the tunnel surfaces through Read/Write with this prefix; developers grep for 'handshake:' without realizing the actionable text is the suffix.

Understand the failure class

Related errors


AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15). Data as JSON: /api/errors/6819b9182e48744a. Report an issue: GitHub.