{"record":{"id":"60c1bd9382189bd0","repo":"ppy/osu","slug":"attempting-to-save-a-skin-which-is-not-yet-tracked","errorCode":null,"errorMessage":"Attempting to save a skin which is not yet tracked. Call {nameof(EnsureMutableSkin)} first.","messagePattern":"Attempting to save a skin which is not yet tracked\\. Call (.+?) first\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"osu.Game/Skinning/SkinManager.cs","lineNumber":293,"sourceCode":"                    // save once to ensure the required json content is populated.\r\n                    // currently this only happens on save.\r\n                    result.PerformRead(skin => Save(skin.CreateInstance(this)));\r\n                    CurrentSkinInfo.Value = result;\r\n                    return true;\r\n                }\r\n\r\n                return false;\r\n            });\r\n        }\r\n\r\n        /// <summary>\r\n        /// Save a skin, serialising any changes to skin layouts to relevant JSON structures.\r\n        /// </summary>\r\n        /// <returns>Whether any change actually occurred.</returns>\r\n        public bool Save(Skin skin)\r\n        {\r\n            if (!skin.SkinInfo.IsManaged)\r\n                throw new InvalidOperationException($\"Attempting to save a skin which is not yet tracked. Call {nameof(EnsureMutableSkin)} first.\");\r\n\r\n            return skinImporter.Save(skin);\r\n        }\r\n\r\n        /// <summary>\r\n        /// Perform a lookup query on available <see cref=\"SkinInfo\"/>s.\r\n        /// </summary>\r\n        /// <param name=\"query\">The query.</param>\r\n        /// <returns>The first result for the provided query, or null if no results were found.</returns>\r\n        public Live<SkinInfo> Query(Expression<Func<SkinInfo, bool>> query)\r\n        {\r\n            return Realm.Run(r => r.All<SkinInfo>().FirstOrDefault(query)?.ToLive(Realm));\r\n        }\r\n\r\n        public event Action SourceChanged;\r\n\r\n        public Drawable GetDrawableComponent(ISkinComponentLookup lookup) => lookupWithFallback(s => s.GetDrawableComponent(lookup));\r\n\r","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/ppy/osu/blob/d9c73e12adff2feaae4a3e158d36fe5883faf6ca/osu.Game/Skinning/SkinManager.cs#L275-L311","documentation":"Thrown by SkinManager.Save(skin) when skin.SkinInfo.IsManaged is false, meaning the Skin has not been attached to the realm store. Saving requires the SkinInfo to be a tracked realm entity so changes can be persisted; an untracked skin cannot be serialized back.","triggerScenarios":"Calling Save() on a Skin that was constructed in-memory (e.g. default skin or a newly created one) without first making it tracked via EnsureMutableSkin().","commonSituations":"Editing the default/argon skin in-place (which is read-only/untracked); creating a Skin programmatically and trying to save before registering it; forgetting to call EnsureMutableSkin in an editor flow.","solutions":["Call skinManager.EnsureMutableSkin() before Save() to guarantee the skin is tracked and mutable.","If operating on the current skin, ensure the user has selected a non-default (mutable) skin.","Wrap Save in the same flow that promotes a live skin to a tracked SkinInfo."],"exampleFix":"// before\nskinManager.Save(skin);\n\n// after\nskinManager.EnsureMutableSkin(() =>\n{\n    skinManager.Save(skin);\n});","handlingStrategy":"validation","validationCode":"if (!skin.SkinInfo.IsManaged)\n    throw new InvalidOperationException(\"Call EnsureMutableSkin before Save.\");","typeGuard":"static bool IsSkinTracked(Skin s) => s.SkinInfo.IsManaged;","tryCatchPattern":"try { skinManager.Save(skin); }\ncatch (InvalidOperationException) { skinManager.EnsureMutableSkin(() => skinManager.Save(skin)); }","preventionTips":["Call EnsureMutableSkin before Save.","Avoid saving the default/argon skin directly.","Promote skins to tracked entities before edits."],"tags":["skinning","skin-manager","realm","tracking","persistence"],"backgroundTag":null,"analyzedSha":"d9c73e12adff2feaae4a3e158d36fe5883faf6ca","analyzedAt":"2026-08-13T14:12:54.015Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}