vitessio/vitess · error
marshal primary health state: %w
Error message
marshal primary health state: %w
What it means
writePrimaryHealthState marshals the in-memory PrimaryHealthState to prototext before persisting it with REPLACE INTO primary_health. prototext.Marshal failure is unexpected for valid states but is wrapped and propagated so recordPrimaryHealthCheckAt (and its callers) surface it instead of silently skipping persistence.
Source
Thrown at go/vt/vtorc/inst/primary_health.go:277
return nil, err
}
return state, nil
}
// writePrimaryHealthState persists the current health window for a tablet alias.
// It is a no-op for empty aliases or nil state, and it deletes the row if the
// state is already evictable.
func writePrimaryHealthState(tabletAlias string, state *primaryHealthState) error {
if tabletAlias == "" || state == nil {
return nil
}
if shouldEvictPrimaryHealthWindow(state) {
return deletePrimaryHealthState(tabletAlias)
}
pb := toProtoPrimaryHealthState(state)
data, err := prototext.Marshal(pb)
if err != nil {
return fmt.Errorf("marshal primary health state: %w", err)
}
query := `REPLACE INTO primary_health (alias, health_state, last_updated) VALUES (?, ?, DATETIME('now'))`
_, err = db.ExecVTOrc(query, tabletAlias, string(data))
return err
}
// deletePrimaryHealthState removes the persisted health window for a tablet alias.
// It is safe to call repeatedly or with an empty alias.
func deletePrimaryHealthState(tabletAlias string) error {
if tabletAlias == "" {
return nil
}
_, err := db.ExecVTOrc("delete from primary_health where alias = ?", tabletAlias)
return err
}
// toProtoPrimaryHealthState converts the in-memory health window to its protobuf form.
// Timestamps are stored as Unix nanoseconds so ordering remains stable across reloads.View on GitHub (pinned to 01a25a7d17)
Solutions
- Inspect the wrapped cause (%w) — prototext.Marshal errors usually indicate a programming/proto-definition bug
- Verify the vtorc binary and the vitess proto definitions come from the same version (no partial builds)
- Check what state recordPrimaryHealthCheckAt produced; log it before marshal to identify the bad field
- As a workaround, delete the health row so a fresh state window is built
Example fix
// before: error surfaces only at runtime
pb := toProtoPrimaryHealthState(state)
data, err := prototext.Marshal(pb)
// after: guard against invalid state before marshal
if state == nil {
return fmt.Errorf("marshal primary health state: nil state for %s", tabletAlias)
} Defensive patterns
Strategy: try-catch
Try / catch
err := recordPrimaryHealthCheckAt(ctx, alias, now)
if err != nil && strings.Contains(err.Error(), "marshal primary health state") {
// indicates a proto/code version mismatch; report and rebuild state
log.Error("primary health marshal failed", slog.String("alias", alias), slog.Any("error", err))
_ = deletePrimaryHealthState(alias)
} Prevention
- Build vtorc from a single consistent Vitess version (no partial proto updates)
- Run make proto/codegen after any proto change so encode/decode stay in sync
- Log the state before marshal in debug builds to catch invalid states early
- Treat this error as a bug: stock Vitess states should always marshal cleanly
When it happens
Trigger: writePrimaryHealthState (called from recordPrimaryHealthCheckAt) marshaling a state that fails prototext encoding — practically only when the state/proto contains something invalid or a code change introduces an unmarshalable field.
Common situations: Custom builds or version-skew where toProtoPrimaryHealthState produces a message the linked proto cannot encode; rarely hit in stock Vitess.
Related errors
- unmarshal primary health state: %w
- recovery.IsRecoveryDisabled(): %v
- failed to read tablet %q from vtorc db: %w
- failed to read instance: %w
- InsertRecoveryDetection: no detection_id returned for alias=
AI-assisted analysis of vitessio/vitess@01a25a7d17 (2026-09-01).
Data as JSON: /api/errors/a8f499965b35c946.
Report an issue: GitHub.