{"record":{"id":"e7c24cd07a4fb7b7","repo":"usememos/memos","slug":"storage-setting-is-required","errorCode":null,"errorMessage":"storage setting is required","messagePattern":"storage setting is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"store/storage.go","lineNumber":90,"sourceCode":"\tif FindStorage(setting, setting.DefaultStorageId) == nil {\n\t\tfallback := builtinStorage(storepb.StorageType_STORAGE_TYPE_LOCAL)\n\t\tif len(setting.Storages) > 0 {\n\t\t\tfallback = setting.Storages[0]\n\t\t} else {\n\t\t\tsetting.Storages = append(setting.Storages, fallback)\n\t\t}\n\t\tsetting.DefaultStorageId = fallback.Id\n\t}\n\tmoveDefaultStorageFirst(setting)\n\n\tsynchronizeLegacyStorageFields(setting)\n}\n\n// PrepareInstanceStorageSettingUpdate preserves storages still referenced by\n// existing attachments and assigns a new identity when a physical namespace changes.\nfunc PrepareInstanceStorageSettingUpdate(incoming, existing *storepb.InstanceStorageSetting) error {\n\tif incoming == nil {\n\t\treturn errors.New(\"storage setting is required\")\n\t}\n\n\tif existing != nil {\n\t\tNormalizeInstanceStorageSetting(existing)\n\t}\n\t// A request may reference a storage the server preserves without resending\n\t// it; adopt the stored entry so the reference survives validation and\n\t// normalization instead of being silently replaced by a fallback default.\n\tif incoming.DefaultStorageId != \"\" && FindStorage(incoming, incoming.DefaultStorageId) == nil {\n\t\tpreserved := FindStorage(existing, incoming.DefaultStorageId)\n\t\tif preserved == nil {\n\t\t\treturn errors.Errorf(\"default storage %q is not configured\", incoming.DefaultStorageId)\n\t\t}\n\t\tincoming.Storages = append(incoming.Storages, proto.CloneOf(preserved))\n\t}\n\n\tlegacyRequest := len(incoming.Storages) == 0\n\tif !legacyRequest {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/usememos/memos/blob/14d757ce1fb31c78590f374bc042f8dbedbc20d7/store/storage.go#L72-L108","documentation":"PrepareInstanceStorageSettingUpdate (store/storage.go:88) normalizes and validates an incoming InstanceStorageSetting for a workspace/instance update; a nil incoming setting is a protocol-level mistake, so it fails fast with errors.New(\"storage setting is required\") at line 90. The function then adopts preserved storages, validates IDs/types/S3 configs, and re-identifies storages whose physical namespace changed — none of which is meaningful without a setting object.","triggerScenarios":"Calling PrepareInstanceStorageSettingUpdate(nil, existing) — e.g. an UpdateInstanceSetting/UpdateWorkspaceSetting handler that extracts the storage setting from the request and the request omitted the storage_setting field, leaving the extracted proto nil.","commonSituations":"API clients (or older SDK versions) sending a workspace setting update that only touches a non-storage field while the server code unconditionally prepares the storage portion; schema/proto drift after upgrading; hand-rolled integrations constructing the request protobuf incompletely.","solutions":["Always populate the storage_setting field in the UpdateWorkspaceSetting/instance-setting request, even when only its sub-fields change.","Guard the call site: only invoke the prepare function when the incoming setting is non-nil, and skip the storage-branch otherwise.","After upgrading memos, regenerate client stubs so an omitted storage_setting is distinguishable from an empty one."],"exampleFix":"// before\nsetting := req.GetSetting().GetStorageSetting() // nil when field omitted\nif err := store.PrepareInstanceStorageSettingUpdate(setting, existing); err != nil { return err }\n\n// after\nsetting := req.GetSetting().GetStorageSetting()\nif setting == nil {\n    return status.Errorf(codes.InvalidArgument, \"storage setting is required\")\n}\nif err := store.PrepareInstanceStorageSettingUpdate(setting, existing); err != nil { return err }","handlingStrategy":"type-guard","validationCode":"if req.GetSetting().GetStorageSetting() == nil {\n    return status.Errorf(codes.InvalidArgument, \"storage setting is required\")\n}\n// only then:\nerr := store.PrepareInstanceStorageSettingUpdate(req.GetSetting().GetStorageSetting(), existing)","typeGuard":"func hasStorageSetting(req *v1.UpdateWorkspaceSettingRequest) bool {\n    return req.GetSetting() != nil && req.GetSetting().GetStorageSetting() != nil\n}","tryCatchPattern":"if err := store.PrepareInstanceStorageSettingUpdate(incoming, existing); err != nil {\n    if err.Error() == \"storage setting is required\" {\n        return status.Errorf(codes.InvalidArgument, \"%v\", err)\n    }\n    return status.Errorf(codes.Internal, \"failed to prepare storage setting: %v\", err)\n}","preventionTips":["Always populate storage_setting in workspace setting update requests, even for unrelated field changes.","Guard with GetStorageSetting() != nil before calling the prepare function.","Regenerate proto stubs after server upgrades so omitted vs empty fields are handled correctly."],"tags":["store","storage","workspace-setting","validation"],"backgroundTag":null,"analyzedSha":"14d757ce1fb31c78590f374bc042f8dbedbc20d7","analyzedAt":"2026-08-15T09:27:36.538Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}