jackc/pgx · error

bad cancel request code

Error message

bad cancel request code

What it means

Returned by CancelRequest.Decode when the first 4 bytes of the body do not equal the cancel request magic number 80877102. That magic (0x04D2162E) is how PostgreSQL distinguishes a cancel request from an SSL/GSS request or a normal startup message on the same socket. A mismatch means the bytes are not a cancel request at all.

Source

Thrown at pgproto3/cancel_request.go:32

type CancelRequest struct {
	ProcessID uint32
	SecretKey []byte
}

// Frontend identifies this message as sendable by a PostgreSQL frontend.
func (*CancelRequest) Frontend() {}

func (dst *CancelRequest) Decode(src []byte) error {
	if len(src) < 12 {
		return errors.New("cancel request too short")
	}
	if len(src) > 264 {
		return errors.New("cancel request too long")
	}

	requestCode := binary.BigEndian.Uint32(src)
	if requestCode != cancelRequestCode {
		return errors.New("bad cancel request code")
	}

	dst.ProcessID = binary.BigEndian.Uint32(src[4:])
	dst.SecretKey = make([]byte, len(src)-8)
	copy(dst.SecretKey, src[8:])

	return nil
}

// Encode encodes src into dst. dst will include the 4 byte message length.
func (src *CancelRequest) Encode(dst []byte) ([]byte, error) {
	if len(src.SecretKey) > 256 {
		return nil, errors.New("secret key too long")
	}
	msgLen := int32(12 + len(src.SecretKey))
	dst = pgio.AppendInt32(dst, msgLen)
	dst = pgio.AppendInt32(dst, cancelRequestCode)
	dst = pgio.AppendUint32(dst, src.ProcessID)

View on GitHub (pinned to ec1a0befd2)

Solutions

  1. Before calling CancelRequest.Decode, switch on the first uint32: 80877102 → cancel, 80877103 → SSL request, 80877104 → GSS request, else → startup/startup-packet path.
  2. Verify you are reading bytes in network (big-endian) order; the code constant is defined relative to big-endian wire bytes.
  3. Confirm the sender is actually issuing a cancel (libpq's PQcancel / pg_cancel_backend), not a startup or SSL negotiation.
  4. If proxying, ensure connection state is tracked so a cancel is only routed to the cancel decoder after the magic has been identified.

Example fix

// before
var cr pgproto3.CancelRequest
err := cr.Decode(body) // body was actually an SSL request

// after
code := binary.BigEndian.Uint32(body)
switch code {
case 80877102: // cancelRequestCode
    var cr pgproto3.CancelRequest
    err := cr.Decode(body)
case 80877103: // SSL request
case 80877104: // GSS request
default: // startup message
}
Defensive patterns

Strategy: validation

Validate before calling

func dispatchStartupCode(body []byte) error {
	if len(body) < 4 {
		return errors.New("frame too short for startup code")
	}
	switch binary.BigEndian.Uint32(body) {
	case 80877102: // cancel
		var cr pgproto3.CancelRequest
		return cr.Decode(body)
	case 80877103: // SSL
	case 80877104: // GSS
	default: // startup
	}
	return nil
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: `(*CancelRequest).Decode(src)` where `binary.BigEndian.Uint32(src) != 80877102`. Happens when a buffer containing a startup message, SSL request (80877103), or GSS request (80877104) is mistakenly routed to the cancel-request decoder.

Common situations: A proxy or test server reads the first int32 from a fresh connection and dispatches to CancelRequest.Decode instead of inspecting the code first. Also occurs with endianness mistakes (e.g. reading little-endian on a little-endian host) or when the connection bytes were corrupted in transit.

Related errors


AI-assisted analysis of jackc/pgx@ec1a0befd2 (2026-08-04). Data as JSON: /data/errors/dbd995754d206782.json. Report an issue: GitHub.