IvanMurzak / IvanMurzak/Unity-MCP

Race condition in FileLogStorage.Clear() / BufferedFileLogStorage.Clear() causes "file being used by another process" on Console/ClearLogs

Open
#855 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug help wanted
Dominant language
C#
Stars
4.3k
Forks
379
Avg merge
6h 52m
Merged PRs (30d)
17

Description

Environment

Package: com.ivanmurzak.unity.mcp v0.82.3 (latest, registry source)
Unity: 6000.3.8f1
OS: Windows 11
Reproduces consistently in an actively-running Editor session (game in Play Mode / logging happening), intermittently otherwise
Summary
Calling the Console / Clear Logs MCP tool (console-clear-logs) intermittently fails with:

Tool execution failed for 'Console / Clear Logs': One or more errors occurred.
(The process cannot access the file 'Temp\mcp-server\ai-editor-logs.txt'
because it is being used by another process.)
This happens repeatedly across sessions whenever any Unity log message is emitted around the same time as a Clear Logs call.

Root cause

Console.ClearLogs.cs marshals to the main thread and runs Debug.ClearDeveloperConsole() then logCollector.Clear(). FileLogStorage.Clear() (and the BufferedFileLogStorage override) do:

lock (_fileMutex)
{
fileWriteStream?.Dispose();
fileWriteStream = null;
if (File.Exists(filePath))
File.Delete(filePath); // <-- fails here intermittently
}
UnityLogCollector subscribes to Application.logMessageReceivedThreaded, which Unity invokes synchronously, on the calling thread, for every Debug.Log* call — including calls made by the plugin's own internal logger (UnityLogger → Debug.LogError/LogDebug) as well as any other game/editor code logging at that moment.

If a log message is emitted on the main thread in the window between fileWriteStream = null and File.Delete(filePath), OnLogMessageReceived fires re-entrantly on the same thread (the lock is re-entrant for the owning thread, so this doesn't deadlock — it runs inline) and calls AppendInternal, which does:

fileWriteStream ??= CreateWriteStream(_requestedFileName, out fileName, out filePath);
This re-opens a fresh FileStream (FileShare.ReadWrite, no FileShare.Delete) on the exact file Clear() is about to delete. When the outer Clear() call reaches File.Delete(filePath), that fresh handle is still open, and Windows refuses the delete with IOException (the "another process" wording is generic .NET/Win32 phrasing — here it's actually a second handle from the same process).

Why this is easy to hit

Application.logMessageReceivedThreaded fires for any log line, including ones the plugin itself emits (e.g. FileLogStorage's own _logger.LogDebug("Creating log file stream: {file}", filePath) in CreateWriteStream). In an actively-developed project with a running game loop, some log line landing in that narrow window is close to guaranteed over a long session.

Evidence this is already known

Your own test suite has a documented flake referencing this: PR #848 (ToolThreadSafetyTests) notes "the documented ai-editor-logs.txt TestToolConsole/ConsoleClearLogs file-lock flakes" as a pre-existing environmental issue. This report is meant to give the underlying root cause for that flake.

Suggested fix (roughly most→least surgical)

In Clear(), temporarily unsubscribe (Application.logMessageReceivedThreaded -= OnLogMessageReceived) before disposing/deleting, and re-subscribe after.
Open the FileStream with FileShare.Delete in CreateWriteStream, and tolerate a concurrently-open handle during delete (or rename-then-delete).
Guard AppendInternal against re-entrant re-creation while a Clear()/ResetLogFile() is in progress on the same thread (e.g. an _isClearing flag checked before CreateWriteStream is called from AppendInternal).
Stack trace (example, trimmed)

fail: [03:39:23:5315] [AI] ToolRunnerCollection Tool execution failed for 'Console / Clear Logs': One or more errors occurred. (The process cannot access the file '...\Temp\mcp-server\ai-editor-logs.txt' because it is being used by another process.)
at System.Reflection.RuntimeMethodInfo.Invoke (...)
at com.IvanMurzak.ReflectorNet.MethodWrapper.InvokeDict (...)
at com.IvanMurzak.McpPlugin.RunTool.Run (...)
UnityEngine.Debug:LogError (object)
com.IvanMurzak.Unity.MCP.Utils.UnityLogger:Log<...> (... at ./Library/PackageCache/com.ivanmurzak.unity.mcp@64753ff18699/Runtime/Logger/UnityLogger.cs:119)
...

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Console.ClearLogs.cs, FileLogStorage.Clear(), BufferedFileLogStorage.Clear(), and UnityLogCollector.OnLogMessageReceived. Reproduce the failure with ToolThreadSafetyTests or an active Play Mode session while logs are emitted, then trace the clear and append paths. Done means Console/Clear Logs no longer fails with a file-lock error during concurrent logging, with the relevant tests passing.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, unity
Domain
game-dev, tooling
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.