vitessio/vitess · error

cannot decode Position

Error message

cannot decode Position

What it means

ProtoToReplicationStatus converts a replicationdatapb.Status proto into a ReplicationStatus struct, decoding the Position field via DecodePosition. Because protos arrive over RPC from other Vitess components, it assumes they are valid and panics (wrapped with vterrors) if the Position string cannot be decoded. This indicates a corrupt or foreign-produced proto message.

Source

Thrown at go/mysql/replication/replication_status.go:137

		SqlState:                               int32(s.SQLState),
		LastSqlError:                           s.LastSQLError,
		SslAllowed:                             s.SSLAllowed,
		HasReplicationFilters:                  s.HasReplicationFilters,
		AutoPosition:                           s.AutoPosition,
		UsingGtid:                              s.UsingGTID,
		SemiSyncPrimaryEnabled:                 s.SemiSyncPrimaryEnabled,
		SemiSyncReplicaEnabled:                 s.SemiSyncReplicaEnabled,
		SemiSyncPrimaryStatus:                  s.SemiSyncPrimaryStatus,
		SemiSyncReplicaStatus:                  s.SemiSyncReplicaStatus,
	}
	return replstatuspb
}

// ProtoToReplicationStatus translates a proto Status, or panics.
func ProtoToReplicationStatus(s *replicationdatapb.Status) ReplicationStatus {
	pos, err := DecodePosition(s.Position)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode Position"))
	}
	relayPos, err := DecodePosition(s.RelayLogPosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode RelayLogPosition"))
	}
	filePos, err := DecodePosition(s.FilePosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode FilePosition"))
	}
	fileRelayPos, err := DecodePosition(s.RelayLogSourceBinlogEquivalentPosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode RelayLogSourceBinlogEquivalentPosition"))
	}
	relayFilePos, err := DecodePosition(s.RelayLogFilePosition)
	if err != nil {
		panic(vterrors.Wrapf(err, "cannot decode RelayLogFilePosition"))
	}
	var sid SID

View on GitHub (pinned to 01a25a7d17)

Solutions

  1. Check that the sending tablet's Status.Position is populated and valid before/after RPC (log s.Position when it fails)
  2. Guard the conversion by validating the position string, or use the non-panicking DecodePosition path in new code
  3. Look for version skew or a zero-value Status proto being passed in; fix the producer to send a real position

Example fix

// before
status := replication.ProtoToReplicationStatus(s)
// after
if _, err := replication.DecodePosition(s.Position); err != nil {
	return vterrors.Wrapf(err, "tablet returned invalid Position %q", s.Position)
}
status := replication.ProtoToReplicationStatus(s)
Defensive patterns

Strategy: validation

Validate before calling

if s == nil || s.Position == "" {
	return errors.New("Status proto has empty Position")
}
if _, err := replication.DecodePosition(s.Position); err != nil {
	return vterrors.Wrapf(err, "invalid Position %q", s.Position)
}

Try / catch

func safeProtoToStatus(s *replicationdatapb.Status) (st replication.ReplicationStatus, err error) {
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("ProtoToReplicationStatus panicked: %v", r)
		}
	}()
	return replication.ProtoToReplicationStatus(s), nil
}

Prevention

When it happens

Trigger: Calling ProtoToReplicationStatus with a Status whose Position field is empty, truncated, or not a valid GTID set string — e.g. a zero-valued proto, or data written by a component using a different flavor encoding.

Common situations: failover/orchestrator code (findErrantGTIDs, FindPositionsOfAllCandidates, ReplicaWasRunning) receiving Status protos from tablets whose Position was never initialized or read from a misconfigured source; version-skew where a peer writes positions differently.

Related errors


AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01). Data as JSON: /api/errors/323dd5e730b976ce. Report an issue: GitHub.