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
- Make the patch method's return type exactly equal to the type of its first parameter (e.g. `static int Postfix(int result)`).
- If you do not want pass-through semantics, change the return type to void so the check takes the normal postfix path.
- 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
- Keep pass-through postfix signature as `static T Postfix(T result)` with identical T
- Never box result into object in pass-through patches
- Write a unit test that applies every patch in PatchAll before shipping
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
- __state type mismatch in patch
- Postfix patch must have a "void" return type
- Could not create replacement method
- Prefix patch has not "bool" or "void" return type
- Cannot assign method return type
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)