You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
manage_camera(action="screenshot") taken while the Editor is in play mode drives the PlayerLoop from a context that is already inside it. Unity reports the re-entrancy, and from that point the Editor emits an internal assertion continuously. In our case it wrote 20.4 GB to Editor.log and the Editor crashed unattended overnight.
Still present on main (v10.1.0, c14de1e6dc01) — verified 2026-07-28.
Cause
ScreenshotUtility.CaptureCompositedAfterFrame waits for the capture by stepping the Editor:
EditorApplication.Step() drives the PlayerLoop. But this is reached from TransportCommandDispatcher.RequestMainThreadPump → UnitySynchronizationContext.ExecuteTasks, which Unity already executes from inside the PlayerLoop. So the loop is driven from within itself.
Unity's message, with the package stack (verbatim from Editor.log):
An abnormal situation has occurred: the PlayerLoop internal function has been called recursively.
UnityEngine.StackTraceUtility:ExtractStackTrace ()
MCPForUnity.Runtime.Helpers.ScreenshotUtility:CaptureCompositedAfterFrame (int,int) (at .../ScreenshotUtility.cs:191)
MCPForUnity.Runtime.Helpers.ScreenshotUtility:CaptureComposited (string,int,bool,bool,int,string) (at .../ScreenshotUtility.cs:221)
MCPForUnity.Editor.Tools.ManageScene:CaptureScreenshot (...) (at .../ManageScene.cs:618)
MCPForUnity.Editor.Tools.Cameras.ManageCamera:HandleCommand (...) (at .../ManageCamera.cs:74)
MCPForUnity.Editor.Tools.CommandRegistry:ExecuteCommand (...) (at .../CommandRegistry.cs:305)
MCPForUnity.Editor.Services.Transport.TransportCommandDispatcher:ProcessCommand (...) (at .../TransportCommandDispatcher.cs:366)
MCPForUnity.Editor.Services.Transport.TransportCommandDispatcher:ProcessQueue () (at .../TransportCommandDispatcher.cs:262)
MCPForUnity.Editor.Services.Transport.TransportCommandDispatcher:<RequestMainThreadPump>g__Pump|11_0 () (at .../TransportCommandDispatcher.cs:184)
UnityEngine.UnitySynchronizationContext/WorkRequest:Invoke ()
UnityEngine.UnitySynchronizationContext:Exec ()
UnityEngine.UnitySynchronizationContext:ExecuteTasks ()
Note the last three frames: the call arrives through Unity's own main-thread pump, which runs inside the PlayerLoop.
What follows
After the re-entrant step, the log fills with one line and never stops:
Access version should be odd when acquiring lock
Measured in the crash-time log:
Flood begins at byte offset
~1,364,796
Total log size
20,444,189,922 bytes (20.4 GB)
Share of the log that is this one line
>99.99%
Sampled at 25%, 50% and 75% of the file — every window is that line back to back. Disk space was not the trigger; 1,227 GB remained free.
It accumulates over a session — a leaked capturer per timed-out capture
Symptomatically this bites hardest in long-running sessions; the scrolling message has appeared several times and restarting the Editor clears it. There is a concrete mechanism.
ScreenshotCapturer.Begin spawns a hidden GameObject that is destroyed only after WaitForEndOfFrame resumes:
privateSystem.Collections.IEnumeratorStart(){yieldreturnnewWaitForEndOfFrame();// never resumes if no frame is rendered
..._onComplete?.Invoke(tex);Destroy(gameObject);// so this is never reached}
WaitForEndOfFrame does not fire when the frame is not rendered — paused play mode, unfocused Editor, minimized player. The __MCP_ScreenshotCapturer__ object is then never destroyed and its coroutine stays suspended for the life of the session, while the synchronous caller has already given up after timeoutSteps.
The two defects compound, and #1230 supplies the link:
A paused Editor renders no frames, so every subsequent capture's WaitForEndOfFrame can never fire — each one times out and leaks its capturer.
So the first screenshot of a session can put the Editor into a state where every later screenshot is guaranteed to leak.
Suggested diagnostic: take several play-mode screenshots, then count objects named __MCP_ScreenshotCapturer__. They should be zero.
Secondary symptom: captures silently return the wrong thing
The for loop exits on timeoutSteps whether or not done was set, so callers can receive a null or half-composited texture with no error. Three identical calls in one session returned a correct 640x419 image, then a blank 512x512, then a 448x448 frame with all Screen Space - Overlay UI missing.
Steps to reproduce
Open a project and enter play mode. (Set Application.runInBackground = true if the Editor will be unfocused; otherwise frames don't tick.)
Don't pump the Editor from a context already inside the PlayerLoop. If the request arrives during ExecuteTasks, defer the capture to a later editor tick instead of calling EditorApplication.Step().
Better, make the play-mode path fully async: return the pending MCP command's result from the WaitForEndOfFrame callback rather than blocking the dispatcher until done.
Give ScreenshotCapturer a timeout/lifetime so it destroys itself if the end-of-frame never arrives.
At minimum, treat !done after timeoutSteps as an error returned to the caller, rather than returning a partial texture as success.
A crash.dmp (3 MB) and log extracts can be supplied if useful.
Summary
manage_camera(action="screenshot")taken while the Editor is in play mode drives the PlayerLoop from a context that is already inside it. Unity reports the re-entrancy, and from that point the Editor emits an internal assertion continuously. In our case it wrote 20.4 GB toEditor.logand the Editor crashed unattended overnight.Still present on
main(v10.1.0,c14de1e6dc01) — verified 2026-07-28.Cause
ScreenshotUtility.CaptureCompositedAfterFramewaits for the capture by stepping the Editor:EditorApplication.Step()drives the PlayerLoop. But this is reached fromTransportCommandDispatcher.RequestMainThreadPump→UnitySynchronizationContext.ExecuteTasks, which Unity already executes from inside the PlayerLoop. So the loop is driven from within itself.Unity's message, with the package stack (verbatim from
Editor.log):Note the last three frames: the call arrives through Unity's own main-thread pump, which runs inside the PlayerLoop.
What follows
After the re-entrant step, the log fills with one line and never stops:
Measured in the crash-time log:
Sampled at 25%, 50% and 75% of the file — every window is that line back to back. Disk space was not the trigger; 1,227 GB remained free.
It accumulates over a session — a leaked capturer per timed-out capture
Symptomatically this bites hardest in long-running sessions; the scrolling message has appeared several times and restarting the Editor clears it. There is a concrete mechanism.
ScreenshotCapturer.Beginspawns a hidden GameObject that is destroyed only afterWaitForEndOfFrameresumes:WaitForEndOfFramedoes not fire when the frame is not rendered — paused play mode, unfocused Editor, minimized player. The__MCP_ScreenshotCapturer__object is then never destroyed and its coroutine stays suspended for the life of the session, while the synchronous caller has already given up aftertimeoutSteps.The two defects compound, and #1230 supplies the link:
Step()pauses play mode (per fix(screenshot): restore pause state after composited capture #1230's own description).WaitForEndOfFramecan never fire — each one times out and leaks its capturer.So the first screenshot of a session can put the Editor into a state where every later screenshot is guaranteed to leak.
Suggested diagnostic: take several play-mode screenshots, then count objects named
__MCP_ScreenshotCapturer__. They should be zero.Secondary symptom: captures silently return the wrong thing
The
forloop exits ontimeoutStepswhether or notdonewas set, so callers can receive a null or half-composited texture with no error. Three identical calls in one session returned a correct 640x419 image, then a blank 512x512, then a 448x448 frame with all Screen Space - Overlay UI missing.Steps to reproduce
Application.runInBackground = trueif the Editor will be unfocused; otherwise frames don't tick.)manage_camera(action="screenshot", include_image=true).An abnormal situation has occurred: the PlayerLoop internal function has been called recursively.Access version should be odd when acquiring lockrepeats without limit;Editor.loggrows without bound.Environment
10.0.0(4ee62b097584), installed fromgit?path=/MCPForUnity#mainmain=c14de1e6dc01(v10.1.0)Prior work in this area
CaptureCompositedAfterFrameand theStep()pump.Step()pausing play mode.Both left the re-entrancy in place.
Suggested fixes
ExecuteTasks, defer the capture to a later editor tick instead of callingEditorApplication.Step().WaitForEndOfFramecallback rather than blocking the dispatcher untildone.ScreenshotCapturera timeout/lifetime so it destroys itself if the end-of-frame never arrives.!doneaftertimeoutStepsas an error returned to the caller, rather than returning a partial texture as success.A
crash.dmp(3 MB) and log extracts can be supplied if useful.