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.packetView on GitHub (pinned to 7d214f8b09)
Solutions
- Unwrap the error chain (errors.Unwrap or %v logging) and fix the underlying stage error
- Run a tiny probe: call a one-byte Read immediately after connect to force the handshake early where it is easy to log
- 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
- Trigger the handshake explicitly after connect instead of relying on first Read
- Always log wrapped errors with %v/%+v to preserve the cause chain
- Use errors.As(err, &net.Error) to separate timeouts from auth/config failures
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- failed to read HTTP response
- write encryption response: %w
- read login finished: %w
- authentication rejected
- authentication rejected: %s
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/6819b9182e48744a.
Report an issue: GitHub.