Post

Debugging dotnet build Hangs Inside Claude Code's macOS Sandbox

Debugging dotnet build Hangs Inside Claude Code's macOS Sandbox

In one of my recent side projects, TwistReader, I had Claude Code add a test project for one of my MAUI app’s view models. That should have been a completely boring, mechanical task. Instead, dotnet build started hanging for exactly five minutes at a time, and it took a proper investigation to figure out why.

I am sharing this because the cause isn’t really about MAUI. It’s about what happens when a normal dotnet build runs inside an AI coding agent’s sandbox on macOS, and I suspect more people will run into this as agentic tools become part of the daily workflow.

The first hang

The plan was simple: add a plain net10.0 target framework to the app project next to net10.0-maccatalyst and net10.0-ios, so a desktop xUnit test project could reference the view models without needing a MAUI workload. This is the documented pattern, and it’s what I would normally just do without thinking twice.

Inside the agent’s sandbox, the build just sat there:

1
Determining projects to restore...

For five minutes. Then it exited, having done nothing.

Isolating the variable

Before chasing any theory, I have run the exact same command in my own VS Code terminal, outside the sandbox. That build finished in 1.4 seconds. Same project, same machine, same SDK. The only difference was the sandbox itself.

That one comparison turned a vague “something is slow” into a concrete, reproducible problem, and it’s the move I would recommend first whenever an agent reports a build issue you can’t immediately explain: reproduce it yourself, unsandboxed, before blindly trusting any diagnosis from an LLM.

Wall #1: MSBuild’s worker-node IPC

A diagnostic-level build log (-v:diag) showed exactly where the process froze: right after NuGet’s restore-graph walk dispatched an MSBuild task with BuildInParallel=true against one of the project’s references. That task spawns a separate, out-of-process MSBuild worker node and talks to it over a Unix domain socket, which is just IPC (inter-process communication, the general term for how two separate processes on the same machine exchange data, whether that’s a socket, a named pipe, or shared memory). Claude Code’s sandbox blocks bind() on Unix sockets for child processes, so the handshake never completes.

A project with no references (my Domain layer, for example) never needs this and built fine, with no sandbox changes at all. The app project, with three references, hit it immediately.

The fix is to force a single MSBuild node, so it never tries to spawn a separate worker process in the first place:

1
dotnet build src/TwistReader.App -m:1 -nodeReuse:false

I originally credited a sandbox setting I had added, allowLocalBinding, as part of this fix. It wasn’t. That setting only controls IP/TCP port binding, not Unix domain sockets. Those are gated by a separate setting, allowUnixSockets / allowAllUnixSockets, that I never touched. The -m:1 -nodeReuse:false flags above work because they avoid the Unix-socket operation entirely, not because anything granted permission for it. Codex caught this while reviewing the PR for this post, and I had Claude verify it against the sandbox’s own source.

With that fix in place, the build got much further, all the way to compiling the app project itself, before failing with:

1
2
error MSB4216: Could not run the "ComputeManagedAssemblies" task because MSBuild
could not create or connect to a task host with runtime "NET" and architecture "arm64".

Same root cause, different subsystem. ILLink’s ComputeManagedAssemblies task always runs in a dedicated out-of-process task host, for isolation, and that’s the same kind of socket IPC the sandbox blocks.

This one turned out to have a proper, maintained fix. dotnet/macios added _UseDesktopTaskAssemblies specifically for this class of problem, originally for iOS (PR #25417), later extended to MacCatalyst, macOS, and tvOS (PR #25662). It forces those tasks to run in-process instead:

1
dotnet build src/TwistReader.App -f net10.0-ios -m:1 -nodeReuse:false -p:_UseDesktopTaskAssemblies=true

With both flags, a build of my app project for iOS went from a five-minute hang to 9 seconds, sandboxed. The property applies to the MSBuild task host itself, not to any particular IDE, which is why it fixes the same failure here: an IDE is just a wrapper around the same dotnet/MSBuild invocation, and it’s that invocation, not what’s driving it, that hits the broken task host. It’s kept as an explicit, supported override today.

Wall #3: the one I couldn’t fix

The same combination of flags did not get me all the way to a real app build. Apple’s actool, which compiles asset catalogs for app icons and splash screens, needs to talk to the CoreSimulatorService daemon over XPC and write scratch files outside the sandbox’s allowed paths. Both are blocked, the same way opening another app is blocked in this sandbox:

1
2
actool error: CoreSimulatorService connection became invalid.
actool error: You don't have permission to save the file "..." in the folder "T".

I haven’t found a settings-based fix for this one, and it hits iOS and MacCatalyst identically, since both compile the same asset catalog.

My first thought was that this must be the same problem CI runners deal with: Xcode and CoreSimulatorService need an Aqua session (a GUI-bound launchd session), not a plain SSH session, and self-hosted CI docs are explicit about that requirement. But that theory doesn’t hold up here.

My Mac already has a logged-in Aqua session running. When Claude routed the build through launchctl asuser to explicitly bind it to that session, nothing changed: same CoreSimulatorService connection became invalid error, same permission denials on the scratch folder. So it isn’t a missing-session problem the way it is on headless CI. Something in the sandbox is denying the XPC call and the filesystem writes outright, regardless of session.

For anything that actually needs actool to run, I let Claude just bypass the sandbox for that one command for now. If you’ve run into this with Claude Code’s sandbox, or know a way to get actool/CoreSimulatorService working without turning the sandbox off entirely, I would like to hear and discuss about it. Reach out to me in the comments or on my socials.

Conclusion

Three separate restrictions were stacking on top of each other, and it would have been easy to misdiagnose the first one and stop there. Forcing a single MSBuild node and also ILLink’s task host in-process fixed two of the three, and they are now my default flags for any sandboxed MAUI local build. The third, image resource compilation, still needs the sandbox turned off entirely.

As always, I hope this post is helpful for some of you.

Until the next post, happy coding, everyone!


Disclaimer: The first draft of this post was written by Claude while we were working through this investigation together in a live debugging session. I reviewed and edited it before publishing. The title image of this post was created by ChatGPT, based on a prompt Claude prepared for this task.

This post is licensed under CC BY 4.0 by the author.