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
- Find the offending element and compare its keys/types against the SavedQueryBinding proto
- Re-serialize the element with protojson.Marshal and update the row
- If DiscardUnknown is the issue, either fix the data or confirm unmarshaler options match the writers
- 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
- Never hand-edit the bindings column in SQL; use the store API
- Keep protojson unmarshaler options consistent between writers and readers
- Add migrations for any SavedQueryBinding proto field renames
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
- failed to unmarshal saved query payload
- failed to marshal saved query binding
- failed to parse database list
- failed to parse container list
- failed to unmarshal iam policy for %s
AI-assisted analysis of bytebase/bytebase@1870550677 (2026-09-06).
Data as JSON: /api/errors/8acd2d075eefe184.
Report an issue: GitHub.