ppy/osu · error · InvalidOperationException
Setting {nameof(CurrentSkin)}'s value directly is not suppor
Error message
Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead. What it means
Thrown by the CurrentSkin.ValueChanged handler when a new Skin is assigned to CurrentSkin whose SkinInfo does not equal CurrentSkinInfo.Value. The API contract is that skin switching must go through CurrentSkinInfo (the source of truth), not CurrentSkin (the derived live instance). Directly setting CurrentSkin bypasses reload logic.
Source
Thrown at osu.Game/Skinning/SkinManager.cs:129
realm.Write(r =>
{
foreach (var skin in defaultSkins)
{
if (r.Find<SkinInfo>(skin.SkinInfo.ID) == null)
r.Add(skin.SkinInfo.Value);
}
});
CurrentSkinInfo.ValueChanged += skin =>
{
CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin);
};
CurrentSkin.Value = argonSkin;
CurrentSkin.ValueChanged += skin =>
{
if (!skin.NewValue.SkinInfo.Equals(CurrentSkinInfo.Value))
throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead.");
SourceChanged?.Invoke();
};
skinExporter = new LegacySkinExporter(storage)
{
PostNotification = obj => PostNotification?.Invoke(obj)
};
}
/// <summary>
/// Returns the dropdown ordering for use mainly by the skin selection UI.
/// Inserts the defaults first, then 'random skin', then custom ones.
/// Returns a list of <see cref="Live{SkinInfo}"/> items.
/// </summary>
public IList<Live<SkinInfo>> GetAllUsableSkins()
{
var skins = new List<Live<SkinInfo>>();
View on GitHub (pinned to d9c73e12ad)
Solutions
- Switch skins via CurrentSkinInfo.Value = skinInfo instead of mutating CurrentSkin.
- Read CurrentSkin only for display; never assign to it.
- If you must mutate a live skin, ensure it belongs to the current CurrentSkinInfo first.
Example fix
// before skinManager.CurrentSkin.Value = mySkin; // after skinManager.CurrentSkinInfo.Value = mySkin.SkinInfo;
Defensive patterns
Strategy: validation
Validate before calling
// Never assign CurrentSkin directly; always go through CurrentSkinInfo. skinManager.CurrentSkinInfo.Value = newSkinInfo;
Prevention
- Treat CurrentSkin as read-only.
- Switch skins only via CurrentSkinInfo.
- Audit code that holds the CurrentSkin bindable.
When it happens
Trigger: Code sets skinManager.CurrentSkin.Value = someSkin directly, bypassing CurrentSkinInfo. The change handler detects the mismatch with CurrentSkinInfo and rejects it.
Common situations: Porting skin-switching code from a system that manipulated a live Skin value; copy-paste from another Bindable pattern; an extension/component that grabbed CurrentSkin and tried to drive it.
Related errors
- Attempting to use this setter will not work correctly. Use s
- A {nameof(PoolableSkinnableSample)} cannot be applied multip
- Attempting to save a skin which is not yet tracked. Call {na
- Attempting to add a new component to a target container whic
- Attempting to remove a new component from a target container
AI-assisted analysis of ppy/osu@d9c73e12ad (2026-08-13).
Data as JSON: /api/errors/3480ce59e07aa8fb.
Report an issue: GitHub.