pardeike/Harmony · error · Exception

Unexpected end of stack trace

Error message

Unexpected end of stack trace

What it means

AccessTools.GetOutsideCaller walks the current stack trace and returns the first frame outside Harmony's own namespace. It throws a plain Exception('Unexpected end of stack trace') only when every frame belongs to Harmony, i.e. no external caller exists - which should be practically impossible in normal use.

Solutions

  1. Avoid calling caller-detection AccessTools APIs from inside Harmony-generated code; pass an explicit MethodInfo instead.
  2. Check the runtime's stack trace behavior (tracing restrictions, trimmed stacks).
  3. Upgrade Harmony; report the runtime scenario if it persists.
Defensive patterns

Strategy: try-catch

Validate before calling

var frames = new StackTrace().GetFrames() ?? Array.Empty<StackFrame>();
var hasOutside = frames.Any(f => f.GetMethod()?.DeclaringType?.Namespace != "Harmony");

Try / catch

try { var caller = AccessTools.GetOutsideCaller(); }
catch (Exception ex) when (ex.Message == "Unexpected end of stack trace") { /* called from inside Harmony-compiled code; pass explicit MethodInfo instead */ }

Prevention

When it happens

Trigger: Calling an AccessTools API that resolves the outside caller from a context where the entire stack consists of Harmony methods - e.g. invoked from within Harmony-compiled dynamic methods/reverse patches that erased or replaced frames, or a filtered/restricted stack trace.

Common situations: Using caller-detection APIs inside Harmony patches compiled into Harmony-named dynamic methods, or exotic runtimes that truncate stack traces.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of pardeike/Harmony@e7872dc170 (2026-09-15). Data as JSON: /api/errors/179f3ec5d3e15d19. Report an issue: GitHub.

Appendix: source

Thrown at Harmony/Tools/AccessTools.cs:2019

			var method = harmonyMethod.GetOriginalMethod() as MethodInfo;
			if (method is null)
				throw new NullReferenceException($"Delegate {typeof(DelegateType)} has no defined original method");
			return MethodDelegate<DelegateType>(method, instance, harmonyMethod.nonVirtualDelegate is false, null);
		}

		/// <summary>Returns who called the current method</summary>
		/// <returns>The calling method/constructor (excluding the caller)</returns>
		///
		public static MethodBase GetOutsideCaller()
		{
			var trace = new StackTrace(true);
			foreach (var frame in trace.GetFrames())
			{
				var method = frame.GetMethod();
				if (method.DeclaringType?.Namespace != typeof(Harmony).Namespace)
					return method;
			}
			throw new Exception("Unexpected end of stack trace");
		}

#if NET35
		static readonly MethodInfo m_PrepForRemoting = Method(typeof(Exception), "PrepForRemoting") // MS .NET
			?? Method(typeof(Exception), "FixRemotingException"); // mono .NET
		static readonly FastInvokeHandler PrepForRemoting = MethodInvoker.GetHandler(m_PrepForRemoting);
#endif

		/// <summary>Rethrows an exception while preserving its stack trace (throw statement typically clobbers existing stack traces)</summary>
		/// <param name="exception">The exception to rethrow</param>
		///
		public static void RethrowException(Exception exception)
		{
#if NET35
			_ = PrepForRemoting(exception);
#else
			System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(exception).Throw();
#endif

View on GitHub (pinned to e7872dc170)