{"record":{"id":"8acd2d075eefe184","repo":"bytebase/bytebase","slug":"failed-to-unmarshal-saved-query-binding","errorCode":null,"errorMessage":"failed to unmarshal saved query binding","messagePattern":"failed to unmarshal saved query binding","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/store/saved_query.go","lineNumber":302,"sourceCode":"\tif err != nil {\n\t\treturn \"\", errors.Wrapf(err, \"failed to marshal saved query bindings\")\n\t}\n\treturn string(out), nil\n}\n\nfunc unmarshalSavedQueryBindings(b []byte) ([]*storepb.SavedQueryBinding, error) {\n\tif len(b) == 0 {\n\t\treturn nil, nil\n\t}\n\tvar elements []json.RawMessage\n\tif err := json.Unmarshal(b, &elements); err != nil {\n\t\treturn nil, errors.Wrapf(err, \"failed to unmarshal saved query bindings\")\n\t}\n\tbindings := make([]*storepb.SavedQueryBinding, 0, len(elements))\n\tfor _, element := range elements {\n\t\tvar binding storepb.SavedQueryBinding\n\t\tif err := common.ProtojsonUnmarshaler.Unmarshal(element, &binding); err != nil {\n\t\t\treturn nil, errors.Wrapf(err, \"failed to unmarshal saved query binding\")\n\t\t}\n\t\tbindings = append(bindings, &binding)\n\t}\n\treturn bindings, nil\n}\n\n// SavedQueryPolicyEtag derives the etag from the stored bindings themselves,\n// so no column has to be kept in step with them. Two policies with the same\n// grants produce the same etag, which is what compare-and-swap needs: a write\n// is rejected only when the grants actually moved.\nfunc SavedQueryPolicyEtag(bindings []*storepb.SavedQueryBinding) (string, error) {\n\tmarshalled, err := marshalSavedQueryBindings(bindings)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\tsum := sha256.Sum256([]byte(marshalled))\n\treturn hex.EncodeToString(sum[:]), nil\n}","sourceCodeStart":284,"sourceCodeEnd":320,"githubUrl":"https://github.com/bytebase/bytebase/blob/1870550677fe08f0d2a78c07acd27541464eb945/backend/store/saved_query.go#L284-L320","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n{\"member\": \"users/a@x.com\"} // wrong field name -> unknown field error\n// after\n{\"members\": [\"users/a@x.com\"]}","handlingStrategy":"try-catch","validationCode":"for _, el := range elements {\n\tvar b storepb.SavedQueryBinding\n\tif err := common.ProtojsonUnmarshaler.Unmarshal(el, &b); err != nil {\n\t\treturn fmt.Errorf(\"element not a valid SavedQueryBinding: %w\", err)\n\t}\n}","typeGuard":null,"tryCatchPattern":"bindings, err := unmarshalSavedQueryBindings(b)\nif err != nil {\n\treturn status.Errorf(codes.Internal, \"saved query bindings corrupt: %v\", err)\n}","preventionTips":["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"],"tags":["go","protojson","unmarshal","bindings"],"backgroundTag":"protobuf-unmarshal-failed","analyzedSha":"1870550677fe08f0d2a78c07acd27541464eb945","analyzedAt":"2026-09-06T21:16:13.665Z","contentChangedAt":"2026-09-06T21:16:13.665Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}