pardeike/Harmony · error · Exception

Postfix patch must have a "void" return type

Error message

Postfix patch {fix} must have a "void" return type

What it means

Harmony requires a non-pass-through postfix to return void (or to be a valid pass-through). When the postfix's return type differs from its first parameter's type (so it is not pass-through) and the return type is not void/continuable, Harmony cannot integrate the return value and throws. This guards against silently ignoring a postfix return value.

Solutions

  1. Declare the postfix as `static void Postfix(...)`.
  2. If you intend to modify the result, make it a pass-through: first parameter of type T (receiving the result) and return type exactly T.
  3. If you intended conditional skipping of the original method, that belongs in a prefix returning bool, not a postfix.

Example fix

// before
static int Postfix(int result) { Log(result); return result + 1; }

// after
static void Postfix(ref int result) { Log(result); result = result + 1; }
Defensive patterns

Strategy: validation

Validate before calling

var first = fix.GetParameters().FirstOrDefault();
if (fix.ReturnType != typeof(void) && (first is null || fix.ReturnType != first.ParameterType))
    throw new InvalidOperationException($"{fix} must be void or a valid pass-through");

Type guard

bool IsValidPostfix(MethodInfo fix) =>
    fix.ReturnType == typeof(void) ||
    (fix.GetParameters().FirstOrDefault() is { } p && fix.ReturnType == p.ParameterType);

Try / catch

try { processor.Patch(); }
catch (Exception ex) when (ex.Message.Contains("must have a \"void\" return type")) { log.Error(ex); }

Prevention

When it happens

Trigger: Registering a postfix whose return type is a non-void type that does not match its first parameter's type, e.g. `static bool Postfix()` or `static int Postfix(object result)`, through PatchClassProcessor/CreateReplacement.

Common situations: Writing a postfix that accidentally returns a value (forgot `void`); moving a prefix's body into a postfix while keeping its bool/int return; generic patch methods whose return type doesn't unify with the result parameter.

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

Appendix: source

Thrown at Harmony/Internal/MethodCreator.cs:338

				{
					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));

				var tmpBoxVars = new List<KeyValuePair<LocalBuilder, Type>>();
				config.AddCodes(this.EmitCallParameter(fix, false, out var tmpInstanceBoxingVar, out var tmpObjectVar, out var refResultUsed, tmpBoxVars));

View on GitHub (pinned to e7872dc170)