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

  1. Make all __state parameters within one patch class use the exact same (assignable) type
  2. Store the old state in a differently-named state type or move the new patch method to its own patch class
  3. Use a shared object-typed __state and cast internally, if a unified type is impractical
  4. 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

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


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)