bytebase/bytebase · error

failed to unmarshal saved query binding

Error message

failed to unmarshal saved query binding

What it means

After the root array decodes, each element is unmarshaled into storepb.SavedQueryBinding with the shared protojson unmarshaler. Failure produces 'failed to unmarshal saved query binding', meaning one array element is not a valid canonical protojson representation of a SavedQueryBinding (unknown fields with strict unmarshaler options, wrong field types, invalid enum strings).

Source

Thrown at backend/store/saved_query.go:302

	if err != nil {
		return "", errors.Wrapf(err, "failed to marshal saved query bindings")
	}
	return string(out), nil
}

func unmarshalSavedQueryBindings(b []byte) ([]*storepb.SavedQueryBinding, error) {
	if len(b) == 0 {
		return nil, nil
	}
	var elements []json.RawMessage
	if err := json.Unmarshal(b, &elements); err != nil {
		return nil, errors.Wrapf(err, "failed to unmarshal saved query bindings")
	}
	bindings := make([]*storepb.SavedQueryBinding, 0, len(elements))
	for _, element := range elements {
		var binding storepb.SavedQueryBinding
		if err := common.ProtojsonUnmarshaler.Unmarshal(element, &binding); err != nil {
			return nil, errors.Wrapf(err, "failed to unmarshal saved query binding")
		}
		bindings = append(bindings, &binding)
	}
	return bindings, nil
}

// SavedQueryPolicyEtag derives the etag from the stored bindings themselves,
// so no column has to be kept in step with them. Two policies with the same
// grants produce the same etag, which is what compare-and-swap needs: a write
// is rejected only when the grants actually moved.
func SavedQueryPolicyEtag(bindings []*storepb.SavedQueryBinding) (string, error) {
	marshalled, err := marshalSavedQueryBindings(bindings)
	if err != nil {
		return "", err
	}
	sum := sha256.Sum256([]byte(marshalled))
	return hex.EncodeToString(sum[:]), nil
}

View on GitHub (pinned to 1870550677)

Solutions

  1. Find the offending element and compare its keys/types against the SavedQueryBinding proto
  2. Re-serialize the element with protojson.Marshal and update the row
  3. If DiscardUnknown is the issue, either fix the data or confirm unmarshaler options match the writers
  4. Check proto history for renamed fields in SavedQueryBinding and migrate old rows

Example fix

// before
{"member": "users/a@x.com"} // wrong field name -> unknown field error
// after
{"members": ["users/a@x.com"]}
Defensive patterns

Strategy: try-catch

Validate before calling

for _, el := range elements {
	var b storepb.SavedQueryBinding
	if err := common.ProtojsonUnmarshaler.Unmarshal(el, &b); err != nil {
		return fmt.Errorf("element not a valid SavedQueryBinding: %w", err)
	}
}

Try / catch

bindings, err := unmarshalSavedQueryBindings(b)
if err != nil {
	return status.Errorf(codes.Internal, "saved query bindings corrupt: %v", err)
}

Prevention

When it happens

Trigger: A bindings array element with a members entry of the wrong type (e.g. number instead of string); unknown field names in an element when ProtojsonUnmarshaler is configured with DiscardUnknown=false; enum fields stored as unrecognized strings.

Common situations: Rows written by external tooling or hand-edited in psql; a proto field rename making old JSON keys unknown; a writer that used encoding/json (snake_case, no canonical form) instead of protojson.

Understand the failure class

Background: "cannot parse invalid wire-format data", "cannot unmarshal", "failed unmarshalling": protobuf unmarshal errors explained — this error's family across 10 libraries.

Related errors


AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06). Data as JSON: /api/errors/8acd2d075eefe184. Report an issue: GitHub.