fatedier/frp · error
unexpected frame type %d, want %d
Error message
unexpected frame type %d, want %d
What it means
Returned by Conn.ReadJSONFrame when the frame just read has a Type different from the expected frameType argument. ReadJSONFrame is a convenience that reads one frame and asserts its type, so an out-of-order or unexpected message produces this error rather than a silent misparse.
Source
Thrown at pkg/proto/wire/wire.go:115
header := make([]byte, 8)
binary.BigEndian.PutUint16(header[0:2], f.Type)
binary.BigEndian.PutUint16(header[2:4], f.Flags)
binary.BigEndian.PutUint32(header[4:8], uint32(len(f.Payload)))
if _, err := c.rw.Write(header); err != nil {
return err
}
_, err := c.rw.Write(f.Payload)
return err
}
func (c *Conn) ReadJSONFrame(frameType uint16, out any) error {
f, err := c.ReadFrame()
if err != nil {
return err
}
if f.Type != frameType {
return fmt.Errorf("unexpected frame type %d, want %d", f.Type, frameType)
}
return c.UnmarshalFrame(f, out)
}
func (c *Conn) UnmarshalFrame(f *Frame, out any) error {
return json.Unmarshal(f.Payload, out)
}
func NewJSONFrame(frameType uint16, in any) (*Frame, error) {
payload, err := json.Marshal(in)
if err != nil {
return nil, err
}
return &Frame{
Type: frameType,
Payload: payload,
}, nil
}View on GitHub (pinned to 6c8a8d0a97)
Solutions
- Read frames generically with ReadFrame and dispatch on f.Type, handling error frames, instead of hard-expecting one type.
- Ensure exactly one goroutine owns the read side of the connection.
- After any prior read/write error, treat the connection as dead — do not keep expecting the next typed frame.
Example fix
// before
var hello wire.ServerHello
conn.ReadJSONFrame(wire.FrameTypeServerHello, &hello) // fails if server sent error frame
// after
f, err := conn.ReadFrame()
if err != nil { return err }
switch f.Type {
case wire.FrameTypeServerHello:
return conn.UnmarshalFrame(f, &hello)
case wire.FrameTypeError:
return parseAndReturnError(f)
} Defensive patterns
Strategy: try-catch
Try / catch
f, err := conn.ReadFrame()
if err != nil { return err }
if f.Type != expectedType {
if f.Type == frameTypeError {
return decodeRemoteError(f) // server sent an error frame instead
}
return fmt.Errorf("unexpected frame type %d, want %d", f.Type, expectedType)
} Prevention
- Prefer ReadFrame + explicit dispatch over ReadJSONFrame in nontrivial state machines.
- Keep one reader goroutine per connection to preserve frame ordering.
When it happens
Trigger: Calling ReadJSONFrame(FrameTypeServerHello, ...) but the server sent an error frame first; protocol state machine getting out of order (two readers, or reading after an error); peer sending a frame type from a newer protocol.
Common situations: Server replies with an error/close frame where the client expects the next handshake frame; concurrent goroutines reading from the same Conn; version skew introducing new frame types.
Related errors
- unsupported frame flags: %d
- frame payload length %d exceeds limit %d
- invalid transport.protocol, optional values are %v
- %s cannot be empty
- %s is too long: length %d exceeds maximum %d
AI-assisted analysis of fatedier/frp@6c8a8d0a97 (2026-08-15).
Data as JSON: /api/errors/d24bee19653ce7bc.
Report an issue: GitHub.