pardeike/Harmony · error · HarmonyException
__state type mismatch in patch
Error message
__state type mismatch in patch "{fix.DeclaringType.FullName}.{fix.Name}": previous __state was declared as "{maybeLocal.LocalType.FullName}" but this patch expects "{type.FullName}" What it means
When multiple patches share per-patch __state, Harmony declares one local per patch class; if a later patch (fix) reuses the same declaring type key and expects a different __state type than the already-declared local, Harmony throws HarmonyException naming both types and the offending patch. The declared local type must be assignable-from the previously declared one (checked via Type.IsAssignableFrom).
Solutions
- Make all __state parameters within one patch class use the exact same (assignable) type
- Store the old state in a differently-named state type or move the new patch method to its own patch class
- Use a shared object-typed __state and cast internally, if a unified type is impractical
- Read the exception: it names the patch, the existing type, and the expected type — align them
Example fix
// before
class Patch { void Prefix(int __state) {} void Postfix(string __state) {} } // mismatch
// after
class Patch { void Prefix(int __state) {} void Postfix(int __state) {} }
// or move Postfix to a separate class Defensive patterns
Strategy: validation
Validate before calling
// before patching, assert all __state params in a patch class agree:
var types = typeof(MyPatches).GetMethods()
.SelectMany(m => m.GetParameters())
.Where(p => p.Name == "__state")
.Select(p => p.ParameterType.IsByRef ? p.ParameterType.GetElementType() : p.ParameterType)
.Distinct().ToList();
if (types.Count > 1) throw new InvalidOperationException($"__state types disagree: {string.Join(", ", types)}"); Type guard
bool StateTypesCompatible(Type declared, Type wanted) => declared.IsAssignableFrom(wanted) || wanted.IsAssignableFrom(declared);
Try / catch
try { harmony.PatchAll(typeof(MyPatches)); }
catch (HarmonyException ex) when (ex.Message.Contains("__state type mismatch"))
{ Log.Error($"Fix __state declarations: {ex.Message}"); throw; } Prevention
- Keep exactly one __state type per patch class; document it at the top of the class
- Move patches needing different state into separate patch classes
- Review __state parameters whenever adding a patch method to an existing class
- Prefer object-typed shared state with explicit casting if flexibility is needed
When it happens
Trigger: Two patch methods in the same patch class declaring different __state parameter types (e.g. one takes `int __state`, another `long __state`); adding a second patch method to an existing patch class after a code change with an incompatible __state type; base/derived class patch state collisions via AssignLocal state sharing.
Common situations: Refactoring a patch class and adding new patch methods with a changed __state type; copying a patch method into an existing patch class without adjusting __state to match; generic patch classes instantiated with different type parameters.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Return type of pass through postfix
- Could not create replacement method
- Prefix patch has not "bool" or "void" return type
- Postfix patch must have a "void" return type
- Cannot assign method return type
AI-assisted analysis of pardeike/Harmony@e7872dc170 (2026-09-15).
Data as JSON: /api/errors/c484c1f322b143fe.
Report an issue: GitHub.
Appendix: source
Thrown at Harmony/Internal/MethodCreator.cs:88
config.WithFixes(fix =>
{
var declaringType = fix.DeclaringType;
if (declaringType is null)
return;
var varName = declaringType.AssemblyQualifiedName;
_ = config.localVariables.TryGetValue(varName, out var maybeLocal);
foreach (var injection in config.InjectionsFor(fix, InjectionType.State))
{
var parameterType = injection.parameterInfo.ParameterType;
var type = parameterType.IsByRef ? parameterType.GetElementType() : parameterType;
if (maybeLocal != null)
{
if (!type.IsAssignableFrom(maybeLocal.LocalType))
{
var message = $"__state type mismatch in patch \"{fix.DeclaringType.FullName}.{fix.Name}\": " +
$"previous __state was declared as \"{maybeLocal.LocalType.FullName}\" but this patch expects \"{type.FullName}\"";
throw new HarmonyException(message);
}
else
{
continue;
}
}
var privateStateVariable = config.DeclareLocal(type);
config.AddLocal(varName, privateStateVariable);
config.AddCodes(this.GenerateVariableInit(privateStateVariable));
}
});
config.finalizedVariable = null;
if (config.finalizers.Count > 0)
{
config.finalizedVariable = config.DeclareLocal(typeof(bool));
config.AddCodes(this.GenerateVariableInit(config.finalizedVariable));
config.exceptionVariable = config.DeclareLocal(typeof(Exception));
View on GitHub (pinned to e7872dc170)