vitessio/vitess · error
got a real event before FORMAT_DESCRIPTION_EVENT: %#v
Error message
got a real event before FORMAT_DESCRIPTION_EVENT: %#v
What it means
The binlog protocol guarantees that the first meaningful event in a dump is the FORMAT_DESCRIPTION_EVENT (only a fake ROTATE_EVENT may precede it). If parseEvents sees any other real event before it has a format, it cannot safely decode anything and aborts with a dump of the offending event.
Source
Thrown at go/vt/binlog/binlog_streamer.go:337
// binlog settings change) that changes the format.
if ev.IsFormatDescription() {
format, err = ev.Format()
if err != nil {
return pos, fmt.Errorf("can't parse FORMAT_DESCRIPTION_EVENT: %v, event data: %#v", err, ev)
}
continue
}
// We can't parse anything until we get a FORMAT_DESCRIPTION_EVENT that
// tells us the size of the event header.
if format.IsZero() {
// The only thing that should come before the FORMAT_DESCRIPTION_EVENT
// is a fake ROTATE_EVENT, which the primary sends to tell us the name
// of the current log file.
if ev.IsRotate() {
continue
}
return pos, fmt.Errorf("got a real event before FORMAT_DESCRIPTION_EVENT: %#v", ev)
}
// Strip the checksum, if any. We don't actually verify the checksum, so discard it.
ev, _, err = ev.StripChecksum(format)
if err != nil {
return pos, fmt.Errorf("can't strip checksum from binlog event: %v, event data: %#v", err, ev)
}
switch {
case ev.IsPseudo():
gtid, _, _, _, err = ev.GTID(format)
if err != nil {
return pos, fmt.Errorf("can't get GTID from binlog event: %v, event data: %#v", err, ev)
}
oldpos := pos
pos = replication.AppendGTID(pos, gtid)
// If the event is received outside of a transaction, it must
// be sent. Otherwise, it will get lost and the targets will go outView on GitHub (pinned to 01a25a7d17)
Solutions
- Verify which MySQL flavor/version is streaming and that Vitess supports it
- Check for proxy/MX layers altering the event order; stream directly from MySQL
- Restart the binlog dump from a clean position so the format-description event is delivered first
- If a custom server build produces this, capture the dumped event and compare against a real MySQL dump
Defensive patterns
Strategy: try-catch
Try / catch
if err := streamer.Stream(ctx); err != nil {
if strings.Contains(err.Error(), "before FORMAT_DESCRIPTION_EVENT") {
// restart dump from a clean position; check for stream-intercepting middleware
}
} Prevention
- Stream directly from MySQL, not through proxies that alter the event stream
- Ensure streams start from valid binlog positions
- Use vanilla supported MySQL flavors
When it happens
Trigger: During Stream/parseEvents, before any FORMAT_DESCRIPTION_EVENT is processed, an event arrives that is neither a rotate event nor a format description — e.g. the server sent a PREVIOUS_GTIDS/query event immediately, or events got reordered/corrupted.
Common situations: Pointing the streamer at a non-standard binlog stream; corruption or interception in the dump stream; unusual server flavors that send different leading events; mid-file restart delivering events out of order.
Related errors
- internal error: unexpected rows without fields
- unexpected: query ended without no results and no error
- GetPreviousGTIDs: previous GTIDs not found
- Log file number is zero, cannot detect previous file
- SHOW BINARY LOGS returned no rows
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/2ae06a70ce7b1f16.
Report an issue: GitHub.