pardeike/Harmony · error · Exception

Return type of pass through postfix

Error message

Return type of pass through postfix {fix} does not match type of its first parameter

What it means

Harmony detected a postfix declared as a 'pass through' postfix: one whose return type matches the type of its first parameter. However, the return type compared against the first parameter's type was not equal, so the pass-through contract is broken. Harmony requires that a pass-through postfix returns exactly the same type as its first argument (which receives the original method's return value), so it throws instead of silently emitting broken IL.

Solutions

  1. Make the patch method's return type exactly equal to the type of its first parameter (e.g. `static int Postfix(int result)`).
  2. If you do not want pass-through semantics, change the return type to void so the check takes the normal postfix path.
  3. If you only want to observe the result, use a void postfix with a parameter named `result` of the original's return type instead.

Example fix

// before
static string Postfix(int result) { return result.ToString(); }

// after
static int Postfix(int result) { Console.WriteLine(result); return result; }
Defensive patterns

Strategy: validation

Validate before calling

var first = fix.GetParameters().FirstOrDefault();
if (first is not null && fix.ReturnType != first.ParameterType)
    throw new InvalidOperationException($"Pass-through postfix {fix} must return exactly {first.ParameterType}");

Type guard

bool IsValidPassThrough(MethodInfo fix) =>
    fix.GetParameters().FirstOrDefault() is { } p && fix.ReturnType == p.ParameterType;

Try / catch

try { harmony.PatchAll(); }
catch (Exception ex) when (ex.Message.Contains("does not match type of its first parameter")) { log.Error(ex); }

Prevention

When it happens

Trigger: Registering a postfix (via PatchClassProcessor/PatchMethods/CreateReplacement in the new MethodCreator pipeline) where the patch method's first parameter is typed to receive the result (pass-through style) but the method's declared return type differs from that parameter type, e.g. `static int Prefix(int result)` or `static object Postfix(string result)`.

Common situations: Refactoring the return type of a patch method without updating the first parameter; copying a pass-through patch and changing `int` to `long`; writing `object Postfix(T result)` hoping boxing works (it does not - exact type match is required).

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/a683e4412bfd04a0. Report an issue: GitHub.

Appendix: source

Thrown at Harmony/Internal/MethodCreator.cs:336

				}
				tmpBoxVars.Do(tmpBoxVar =>
				{
					config.AddCode(new CodeInstruction(originalIsStatic ? OpCodes.Ldarg_0 : OpCodes.Ldarg_1));
					config.AddCode(Ldloc[tmpBoxVar.Key]);
					config.AddCode(Unbox_Any[tmpBoxVar.Value]);
					config.AddCode(Stobj[tmpBoxVar.Value]);
				});

				if (fix.ReturnType != typeof(void))
				{
					var firstFixParam = fix.GetParameters().FirstOrDefault();
					var hasPassThroughResultParam = firstFixParam is not null && fix.ReturnType == firstFixParam.ParameterType;
					if (hasPassThroughResultParam)
						result = true;
					else
					{
						if (firstFixParam is not null)
							throw new Exception($"Return type of pass through postfix {fix} does not match type of its first parameter");

						throw new Exception($"Postfix patch {fix} must have a \"void\" return type");
					}
				}
			}
			return result;
		}

		internal bool AddFinalizers(bool catchExceptions)
		{
			var rethrowPossible = true;
			var original = config.original;
			var originalIsStatic = original.IsStatic;
			config.finalizers.Do(fix =>
			{
				if (catchExceptions)
					config.AddCode(this.MarkBlock(ExceptionBlockType.BeginExceptionBlock));

View on GitHub (pinned to e7872dc170)