jackc/pgx · error

too many column format codes

Error message

too many column format codes

What it means

Returned by CopyBothResponse.Encode when ColumnFormatCodes has more than 65535 entries. The wire format encodes the column count as a uint16, so anything beyond MaxUint16 cannot be represented. This guard prevents emitting a silently-truncated count that the receiver would misinterpret. CopyBothResponse is the 'W' message used in logical/physical streaming replication.

Source

Thrown at pgproto3/copy_both_response.go:52

		return &invalidMessageFormatErr{messageType: "CopyBothResponse"}
	}

	columnFormatCodes := make([]uint16, columnCount)
	for i := range columnCount {
		columnFormatCodes[i] = binary.BigEndian.Uint16(buf.Next(2))
	}

	*dst = CopyBothResponse{OverallFormat: overallFormat, ColumnFormatCodes: columnFormatCodes}

	return nil
}

// Encode encodes src into dst. dst will include the 1 byte message type identifier and the 4 byte message length.
func (src *CopyBothResponse) Encode(dst []byte) ([]byte, error) {
	dst, sp := beginMessage(dst, 'W')
	dst = append(dst, src.OverallFormat)
	if len(src.ColumnFormatCodes) > math.MaxUint16 {
		return nil, errors.New("too many column format codes")
	}
	dst = pgio.AppendUint16(dst, uint16(len(src.ColumnFormatCodes)))
	for _, fc := range src.ColumnFormatCodes {
		dst = pgio.AppendUint16(dst, fc)
	}

	return finishMessage(dst, sp)
}

// MarshalJSON implements encoding/json.Marshaler.
func (src CopyBothResponse) MarshalJSON() ([]byte, error) {
	return json.Marshal(struct {
		Type              string
		ColumnFormatCodes []uint16
	}{
		Type:              "CopyBothResponse",
		ColumnFormatCodes: src.ColumnFormatCodes,
	})

View on GitHub (pinned to ec1a0befd2)

Solutions

  1. Cap ColumnFormatCodes at 65535 entries; if you have more columns you cannot represent them in one CopyBothResponse.
  2. Reuse a single format code for all columns when they share a format — the protocol allows one entry to apply to all.
  3. Audit the code that builds ColumnFormatCodes for an unbounded loop or accidental append inside a row loop.
  4. If proxying a server, forward the server's encoded bytes verbatim instead of re-encoding.

Example fix

// before
resp := &pgproto3.CopyBothResponse{
    OverallFormat:     0,
    ColumnFormatCodes: perColumnCodes, // len > 65535
}
_, err := resp.Encode(nil)

// after
resp := &pgproto3.CopyBothResponse{
    OverallFormat:     0,
    ColumnFormatCodes: []uint16{0}, // single code applies to all columns
}
_, err := resp.Encode(nil)
Defensive patterns

Strategy: validation

Validate before calling

func validateCopyBothResponseEncode(r *pgproto3.CopyBothResponse) error {
	if len(r.ColumnFormatCodes) > math.MaxUint16 {
		return fmt.Errorf("too many column format codes: %d (max %d)", len(r.ColumnFormatCodes), math.MaxUint16)
	}
	return nil
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Calling `(*CopyBothResponse).Encode(dst)` with `len(ColumnFormatCodes) > 65535`. Realistic only in a test harness, a replication proxy synthesising a response, or a bug that builds a per-column slice for a table with >64k columns.

Common situations: Almost never hit by application code — CopyBothResponse is a backend→frontend message normally produced by the server, not by the client. Surfaces when writing a PostgreSQL-compatible proxy/server or a fuzzer that constructs oversized messages. A misconfigured replication proxy that loops while appending format codes could trigger it.

Related errors


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