wtf failure

MAUI iOS Archive Fails on Windows: The Real Cause

A MAUI iOS archive fails on Windows with a cryptic Unzip error. Two documented bugs looked like the cause and weren’t — here’s the real fix.

A MAUI iOS archive fails on Windows with a cryptic Unzip error. I burned an afternoon blaming two real, documented bugs before finding the actual cause: a plain JSON file. Have you hit “The filename, directory name, or volume label syntax is incorrect” or “The given path’s format is not supported” while archiving a .NET MAUI iOS app through Visual Studio’s Pair-to-Mac? Save yourself the detour. Here’s the whole mess, dead ends included.

If you’re here for the certificate side of Pair-to-Mac hell instead, I already wrote that post. This one starts after the certs were sorted and a different, uglier bug showed up.

The setup

Device testing was never the problem. Deploying a Debug build straight to my iPhone through Visual Studio’s Run button worked on the first real attempt. No archive, no .ipa – just an install and a launch. That’s worth saying up front. Everything that follows only matters if you actually need a distributable .ipa file, for TestFlight or ad-hoc install. If you only need to test on a device, you may not have this problem at all.

I needed the .ipa. So I wrote a PowerShell script to automate it, the same way I already had one for Android. It failed. I tried Visual Studio’s own Build > Publish… wizard instead, assuming my script was the problem. It failed identically. Same error, same garbled path, both times:

System.IO.IOException: The filename, directory name, or volume label syntax is incorrect. :
'C:\Users\jon\AppData\Local\Xamarin\iOS\Archives\2026-09-08\GrimWarden.Mobile...xcarchive
\Products\Applications\GrimWarden.Mobile.app\D:\dev\SyntaxCircus\grimwarden\src\GrimWarden.Mobile'

Look at that path. It’s a real Windows folder, followed by another entire Windows folder. They’re stitched together as if the build system forgot which filesystem it was on. Somewhere inside the .app bundle sat a literal reference to D:\dev\SyntaxCircus\grimwarden\src\GrimWarden.Mobile. Windows had no idea what to do with a folder name that contains a drive letter.

That’s the shape of the bug. Finding the cause took two wrong turns.

Red herring #1: a real GitHub issue, wrong bug

A quick search turned up dotnet/maui#22359, which described almost exactly what I was seeing. The reported cause: a folder literally named Resources under Platforms/iOS bakes its absolute path into the .app bundle during archive. The fix is to rename the folder and reference its contents explicitly instead.

My project had exactly that folder, holding nothing more exotic than PrivacyInfo.xcprivacy, Apple’s required privacy manifest file. I moved it out of Resources, added an explicit BundleResource item pointing at the new location, and reran the build.

Identical failure. Same garbled path, character for character.

A real bug, a real fix, and the wrong culprit entirely. That’s the part that stings about debugging by search – a matching-looking issue feels like confirmation. It can send you fixing something that was never actually broken in your case.

Red herring #2: blaming a symlink, then killing a 20-minute hang

Next theory: a native .xcframework dependency. Both Sentry.Maui and the RevenueCat SDK ship compiled Cocoa frameworks. Apple’s framework format commonly uses internal symlinks (the classic Versions/Current convention). A related family of bugs exists too: dotnet/macios#23678 and dotnet/maui#19916. Both describe symlinks inside .xcframework packages getting mishandled during exactly this kind of cross-machine archive.

I had a data point that seemed to back this up. An old Xamarin.iOS project of mine, Slot Shark, had working archives on record, but only under a legacy Platform=iPhone build configuration. It never worked under the modern runtime-identifier-only path this newer MAUI project uses. So I tried forcing it: -p:Platform=iPhone, tacked onto the same publish command. The theory: it would route the build through Slot Shark’s older, apparently-safer pipeline.

It didn’t fail. It didn’t succeed, either. Instead, it just sat there.

Twenty minutes later I checked on it, expecting an error I could read. Instead I found a dotnet process that had used sixteen seconds of CPU time in twenty minutes. It was holding open several established SSH connections to the Mac and doing, as far as I could tell, absolutely nothing. Not stalled on a huge compile. Not slow. Just gone quiet, forever, until someone kills it by hand.

I killed it by hand.

A build that hangs instead of failing isn’t a workaround. It’s a second problem wearing the first one’s clothes, and it cost twenty real minutes to learn that.

Plan C: build the MAUI iOS archive on the Mac instead

With two theories burned, I reasoned my way to a structural fix instead of a code fix. The plan: build the archive natively on the Mac, so it never has to cross the Windows-to-Mac boundary where the corruption seemed to happen. No zip shipped over the wire, no Unzip task trying to recreate a foreign path. Just a local build on the machine that actually understands its own filesystem.

That meant writing a shell script that mirrored my PowerShell one. It also meant a setup runbook for a Mac I own but have never once used directly. It exists exclusively as a Pair-to-Mac appliance – a black box with a keyboard I’ve touched twice. So the runbook had to assume zero familiarity. It covered how to open Terminal, how to install the .NET SDK, and how to clone a private repo over HTTPS with a token. I wasn’t about to untangle SSH key setup on a machine I don’t live in.

It would have worked, eventually. I never got the chance to find out, because the actual fix showed up first – from somewhere I hadn’t tried yet.

Why the MAUI iOS Archive Fails on Windows

I handed the whole mess to a different AI – Codex, running against the same repo – mostly out of frustration. I expected another plausible-sounding theory to go test. It found the real cause in one pass, and it wasn’t in my project at all. It was a default I never knew existed.

Microsoft.NET.Sdk.Razor, the SDK this project builds on, automatically includes root-level .json files as Content build items. Nobody adds them; the SDK just does it, quietly, for every project that uses it. Two files in my project were catching that default: appsettings.json and sasscompiler.json.

Here’s the part that actually broke things. The iOS SDK bundles every Content item into the .app package – even ones explicitly marked CopyToPublishDirectory=Never. Merely being a Content item was enough. During the Windows-to-Mac archive, the archive carried those two files’ absolute Windows paths straight through into the bundle structure. One of them landed in the .xcarchive as a literal path fragment: GrimWarden.Mobile.app/D:/dev/.... That’s the exact corruption from the very first error message, three sections ago. Not a symlink. Not a Resources folder. A build default I’d never had a reason to look at.

The two-line fix

The fix was two lines:

<Content Remove="appsettings.json" />
<Content Remove="sasscompiler.json" />

Neither file lost anything. I’d already separately included appsettings.json as an EmbeddedResource, compiled straight into the assembly. That’s a completely different mechanism, untouched by any of this. Its own build task reads sasscompiler.json directly off disk, and it never needed to be an MSBuild item in the first place.

Codex went a step further than the minimal fix, too. It added a dotnet clean step to my publish script. The reasoning: a dozen failed archive attempts could have left corrupted, drive-qualified paths cached in the remote build state. Even a correct fix might keep failing against those stale leftovers. And it wrote a regression test that parses the raw .csproj XML directly. The test asserts those two Content Remove lines stay in place and that appsettings.json keeps its EmbeddedResource entry. It’s a real safety net, running entirely against the shared test project with no iOS toolchain required.

The actual lesson

Two rounds of theorizing about why the MAUI iOS archive fails on Windows, both backed by real, documented, plausible-looking bugs – and both wrong. That’s the uncomfortable part. A GitHub issue that matches your symptoms is evidence, not proof. The fact that a fix is documented somewhere doesn’t mean it’s your fix. That’s especially true when reapplying it produces the exact same failure instead of a new one.

The thing that actually broke my archive wasn’t a native binary or a symlink. It wasn’t exotic enough to deserve a GitHub issue of its own. It was a default item glob nobody had a reason to suspect, because nobody was looking at Content items at all. Everyone, myself included, was looking at native dependencies and platform-specific folders instead.

If there’s one habit worth taking from this: when your fix produces the identical failure, that’s not confirmation you fixed part of it. That’s a sign you fixed the wrong thing entirely. The search for the real cause should start over, instead of continuing down the same road slightly further.

Practical takeaways

  • If your MAUI iOS archive fails on Windows again with the identical error after a fix, you fixed the wrong bug, not an incomplete one – stop patching that theory and look elsewhere.
  • A build that hangs instead of erroring isn’t a workaround succeeding slowly. Kill it, and treat the hang itself as a separate finding.
  • Before blaming a native dependency’s binary internals, check what your own SDK includes by default. ContentNone, and similar item types often pick up files nobody explicitly added.
  • If a Content item doesn’t need to be copied anywhere, remove it entirely rather than setting CopyToPublishDirectory=Never – some downstream tooling (like the iOS SDK’s bundler here) still processes the item regardless of that setting.
  • When you’ve sunk real time into a theory, get a second opinion before sinking more – another engineer, or another tool entirely, looking at the problem fresh.

Further reading

Credits

Photo by Igor Omilaev on Unsplash

1 comment on “MAUI iOS Archive Fails on Windows: The Real Cause

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.