ppy/osu · error · InvalidOperationException
Attempting to save a skin which is not yet tracked. Call {na
Error message
Attempting to save a skin which is not yet tracked. Call {nameof(EnsureMutableSkin)} first. What it means
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.
Source
Thrown at osu.Game/Skinning/SkinManager.cs:293
// save once to ensure the required json content is populated.
// currently this only happens on save.
result.PerformRead(skin => Save(skin.CreateInstance(this)));
CurrentSkinInfo.Value = result;
return true;
}
return false;
});
}
/// <summary>
/// Save a skin, serialising any changes to skin layouts to relevant JSON structures.
/// </summary>
/// <returns>Whether any change actually occurred.</returns>
public bool Save(Skin skin)
{
if (!skin.SkinInfo.IsManaged)
throw new InvalidOperationException($"Attempting to save a skin which is not yet tracked. Call {nameof(EnsureMutableSkin)} first.");
return skinImporter.Save(skin);
}
/// <summary>
/// Perform a lookup query on available <see cref="SkinInfo"/>s.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns>
public Live<SkinInfo> Query(Expression<Func<SkinInfo, bool>> query)
{
return Realm.Run(r => r.All<SkinInfo>().FirstOrDefault(query)?.ToLive(Realm));
}
public event Action SourceChanged;
public Drawable GetDrawableComponent(ISkinComponentLookup lookup) => lookupWithFallback(s => s.GetDrawableComponent(lookup));
View on GitHub (pinned to d9c73e12ad)
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.
Example fix
// before
skinManager.Save(skin);
// after
skinManager.EnsureMutableSkin(() =>
{
skinManager.Save(skin);
}); Defensive patterns
Strategy: validation
Validate before calling
if (!skin.SkinInfo.IsManaged)
throw new InvalidOperationException("Call EnsureMutableSkin before Save."); Type guard
static bool IsSkinTracked(Skin s) => s.SkinInfo.IsManaged;
Try / catch
try { skinManager.Save(skin); }
catch (InvalidOperationException) { skinManager.EnsureMutableSkin(() => skinManager.Save(skin)); } Prevention
- Call EnsureMutableSkin before Save.
- Avoid saving the default/argon skin directly.
- Promote skins to tracked entities before edits.
When it happens
Trigger: 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().
Common situations: 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.
Related errors
- Failed to import new beatmap
- Setting {nameof(CurrentSkin)}'s value directly is not suppor
- Invalid component type: {component}
- Beatmap file {b.File!.Filename} could not be opened!
- Filename ""{filename}"" is not allowed.
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/60c1bd9382189bd0.
Report an issue: GitHub.