XTLS/Xray-core · error

write packet: bad length: %d

Error message

write packet: bad length: %d

What it means

encodePacket rejects an outgoing packet whose encoded field data exceeds maxPacketDataLength (32 KiB, protocol.go:11). This is a hard cap on the Minecraft protocol frame body this transport will emit, guarding the framing layer from oversized packets. It fires before the packet ID is prepended and before framing.

Source

Thrown at transport/internet/finalmask/xmc/protocol.go:404

		return 0, err
	}
	if err = writeFull(w, frame); err != nil {
		return 0, fmt.Errorf("write packet data: %w", err)
	}
	return len(frame), nil
}

func encodePacket(packetID int, fields ...field) ([]byte, error) {
	var dataBuf bytes.Buffer

	for _, field := range fields {
		err := field.writeTo(&dataBuf)
		if err != nil {
			return nil, fmt.Errorf("write packet field: %w", err)
		}
	}
	if dataBuf.Len() > maxPacketDataLength {
		return nil, fmt.Errorf("write packet: bad length: %d", dataBuf.Len())
	}

	packetIDVarint := Varint(packetID)
	bodyLength := varintSize(packetIDVarint) + dataBuf.Len()
	if bodyLength > maxPacketBodyLength {
		return nil, fmt.Errorf("write packet: bad length: %d", bodyLength)
	}

	var frame bytes.Buffer
	frame.Grow(varintSize(Varint(bodyLength)) + bodyLength)
	frameLength := Varint(bodyLength)
	if err := frameLength.writeTo(&frame); err != nil {
		return nil, fmt.Errorf("write packet length: %w", err)
	}
	if err := packetIDVarint.writeTo(&frame); err != nil {
		return nil, fmt.Errorf("write packet ID: %w", err)
	}
	frame.Write(dataBuf.Bytes())

View on GitHub (pinned to 7d214f8b09)

Solutions

  1. Check the wrapped length in the message and identify which field (usually profile textures) exceeds 32 KiB
  2. Shorten or omit the textures property in the configured login profile (empty TexturesValue/TexturesSignature)
  3. If genuinely large payloads are required, raise maxPacketDataLength in a fork — but note vanilla clients may reject such frames
  4. Validate profile sizes at config load time instead of at connection time

Example fix

// before
profile := loginProfile{TexturesValue: hugeBase64} // > 32 KiB

// after
profile := loginProfile{} // omit textures; empty value stays within the frame cap
Defensive patterns

Strategy: validation

Validate before calling

const maxFieldBudget = 32 * 1024\nif len(profile.TexturesValue)+len(profile.TexturesSignature) > maxFieldBudget-1024 {\n\treturn fmt.Errorf("login profile textures too large: %d bytes", len(profile.TexturesValue)+len(profile.TexturesSignature))\n}

Type guard

func profileFitsPacket(p loginProfile) bool {\n\treturn len(p.TexturesValue)+len(p.TexturesSignature) < 30*1024\n}

Try / catch

if err := writePacket(w, 0x02, fields...); err != nil {\n\tvar maxErr *maxPacketError\n\tif errors.As(err, &maxErr) || strings.Contains(err.Error(), "bad length") {\n\t\t// shrink payload (drop textures) and retry once\n\t}\n}

Prevention

When it happens

Trigger: Calling writePacket with fields whose combined serialized size exceeds 32768 bytes, e.g. writing the login-finished success packet with a very large textures value/signature (profile.TexturesValue/TexturesSignature) or an oversized status JSON string.

Common situations: Injecting a login profile whose base64 textures property is larger than 32 KiB; hand-edited or machine-generated profiles from config. Vanilla servers keep textures well under this limit, so seeing it usually means misconfigured profile data.

Related errors


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