Mono's mcs compiler emits a stray U+FEFF BOM character on stdout during
compilation. CodeDom's CSharpCodeProvider misinterprets this as a compiler
error (no ErrorNumber, ErrorText is just the BOM), causing HasErrors to be
true and results.CompiledAssembly to be null — even though mcs exits 0 and
wrote the DLL successfully.
Fix:
- Compile to a temp DLL path (GenerateInMemory=false + OutputAssembly)
instead of relying on results.CompiledAssembly.
- Skip phantom errors where ErrorNumber is empty and ErrorText is only
BOM/whitespace.
- Load the produced DLL via Assembly.Load(File.ReadAllBytes(...)) when
no real errors exist.
- Clean up the temp DLL in a finally block.
Fixes#1186
`CodeDomCompile` in `MCPForUnity/Editor/Tools/ExecuteCode.cs` pushed every
filtered assembly path into `CompilerParameters.ReferencedAssemblies`. Mono's
`CSharpCodeCompiler.BuildArgs` (verified at
mcs/class/System/Microsoft.CSharp/CSharpCodeCompiler.cs:388-392) turns each
reference into a literal `/r:"<absolute_path>"` flag and concatenates them
inline on the `mono.exe csc ...` command line.
Projects with ~100+ asmdefs (a perfectly normal large Unity project) overflow
Windows' 32 KB CreateProcess argument limit and `Process.Start` throws
Win32 `ERROR_FILENAME_EXCED_RANGE`, which Mono surfaces as:
SystemException: Error running …mono.exe: The filename or extension is too long.
…exactly the failure reported in #1144 at ExecuteCode.cs:115 on Windows
10/11 + Unity 2022.3.62f2 + MCP for Unity 9.6.9-beta.8.
Fix: write all `/r:"…"` lines to a GUID-named temp response file and pass
`@"<path>"` via `CompilerParameters.CompilerOptions` (which `BuildArgs`
appends verbatim, confirmed at line 396 of the same Mono source). One short
argument regardless of reference count — the 32 KB ceiling is no longer
reachable.
Both legacy mcs and Roslyn csc accept `@responsefile`, so the change is
cross-platform: macOS/Linux Mono behaves identically, and the path doesn't
have a 32 KB limit there to begin with. Response file is cleaned up in a
`finally` block (best-effort; OS reaps temp on its own otherwise).
Tests: added two EditMode regression tests in `ExecuteCodeTests.cs` that
exercise the codedom backend end-to-end:
- `Execute_CodedomBackend_CompilesAndRuns` — basic compile + execute.
- `Execute_CodedomBackend_ResolvesUnityTypes` — verifies Unity references
resolve through the response file.
Could not reproduce the exact 32 KB failure on macOS (Mono on POSIX doesn't
hit the limit), but the response-file path is the only path in the new code,
so the fix is mechanically equivalent for any reference-set size on any
platform.
Sources:
- Mono CSharpCodeCompiler.cs — BuildArgs converts ReferencedAssemblies to
`/r:"…"` inline and appends CompilerOptions verbatim.
- C# compiler — ResponseFiles option (`@responsefile` syntax).
The RoslynInstaller downloads only 4 NuGet packages but Microsoft.CodeAnalysis 4.12.0
on netstandard2.0 also references System.Runtime.CompilerServices.Unsafe v6.0.0.0,
which is NOT what Unity ships (Unity bundles v4.x). The reference is unresolved at
runtime, so Roslyn's StringTable static cctor throws FileNotFoundException, which
in turn poisons CSharpSyntaxTree's cctor: every parse / compile attempt then
throws TypeInitializationException.
The error is invisible because RoslynCompiler.Compile's catch block only logs
e.Message, and for TargetInvocationException that string is the generic
"Exception has been thrown by the target of an invocation." — the real cause
in InnerException is silently dropped.
Repro:
1. Trigger Tools > MCP for Unity > Install Roslyn on a fresh project.
2. Ask the MCP execute_code tool to compile any Roslyn-only snippet.
3. Observe: "Compilation failed: Roslyn compilation error: Exception has been
thrown by the target of an invocation." — with no further detail.
4. Drilling via reflection reveals:
TypeInitializationException for CSharpSyntaxTree
└─ TypeInitializationException for Roslyn.Utilities.StringTable
└─ FileNotFoundException: Could not load file or assembly
'System.Runtime.CompilerServices.Unsafe, Version=6.0.0.0'
Fix:
* RoslynInstaller.NuGetEntries: add the missing dependency so a fresh install
drops all 5 DLLs into Assets/Plugins/Roslyn/.
* RoslynCompiler.Compile catch: walk the InnerException chain so a future
bootstrap regression surfaces the actual cause (type + message) instead of
the generic invocation wrapper.
Verified locally: after dropping System.Runtime.CompilerServices.Unsafe.dll v6
into the plugins folder and forcing a domain reload, the execute_code tool
compiles C# 7+ snippets via the Roslyn backend successfully.
1.Add Compat based scripts revolving around UnityCompatShims.cs, that will document our current API Compatibility changes in several files.
2.Add custom screenshot folder selection
- Fix off-by-one in WrapperLineOffset (9 → 10)
- Cache resolved assembly paths in static field for performance
- Add encoding="utf-8" to CLI file read
- Add group="scripting_ext" to Python tool decorator
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>