IOException: Success
Some time ago, I encountered and resolved a truly bizarre issue: certain players were unable to save their game due to an IOException with the error message "Success". To this day, I still receive inquiries from fellow devs about how to address this problem. This article documents the issue and provides a reference for the workaround.
Update: It's August 2026 and this is still an ongoing issue. The handling code example below was updated to be even more robust based on our latest findings.
Root cause of the IOException: Success#
The exception was triggered by a File.Delete call.
After exhausting the "this shouldn't happen" phase of debugging (without any way to reproduce the issue locally),
I discovered that the exception occurred when the file was actually deleted successfully.
Hence, the error message "Success."
This anomaly only affected players running our game through CrossOver,
a compatibility layer that allows Mac users to run Windows programs.
It appears that CrossOver incorrectly interpreted a success exit code from a system call as an error,
which was then surfaced as an exception by the .NET runtime.
The workaround#
Unfortunately, the solution was more of a workaround, implemented in three stages.
First, I've wrapped all File.Delete calls in a try-catch block and ignored exceptions where the message was "Success."
This resolved the issue for most players.
Second, while the initial fix worked for many, our logs still showed anomalies.
Turns out that .NET exception messages are localized.
Players running the game in different languages encountered "Success" exceptions translated into their system's language (Why are system exceptions localized,
Microsoft‽).
Matching the message string was clearly a dead end, so I've dropped it in favor of checking the HResult property,
which is not affected by localization.
The exception is now ignored when its HResult indicates success: either a non-negative value,
or 0x80070000, a Win32 error HResult that carries the error code 0, ERROR_SUCCESS.
This ensured that the fix was robust across all languages.
Third, to future-proof the workaround, I've added another layer of protection:
if File.Exists fails but the file no longer exists, the operation is treated as a success.
Warnings are logged for these cases, allowing us to monitor any further issues.
The Code listing 1 below shows the final code snippet with all the protections combined. Similar safety checks were also added for other file operations to prevent similar issues.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private const int HRESULT_WIN32_ERROR_SUCCESS = unchecked((int)0x80070000);
public static bool IsPhantomSuccessIoException(IOException ex) {
return ex.HResult >= 0 || ex.HResult == HRESULT_WIN32_ERROR_SUCCESS;
}
public static void DeleteFile(string filePath) {
try {
File.Delete(filePath);
} catch (IOException ex) when (IsPhantomSuccessIoException(ex) || File.Exists(filePath) == false) {
Log.Warning($"File.Delete on '{filePath}' reported HResult "
+ $"0x{ex.HResult:X} \"{ex.Message}\", treating as success.");
}
}
public static void MoveFile(string source, string dest) {
try {
File.Move(source, dest);
} catch (IOException ex) when (IsPhantomSuccessIoException(ex)) {
Log.Warning($"File.Move from '{source}' to '{dest}' reported "
+ $"phantom HResult 0x{ex.HResult:X} \"{ex.Message}\", treating as success.");
}
}
Final thoughts#
I've reported this problem to the CrossOver team, and they were able to reproduce it, however, I haven't received any updates from them since. I am still receiving questions about this issue so it's still out there in the wild, possibly encountered by people who use older versions of CrossOver.
I hope this documentation helps anyone who encountered this strange issue. If nothing else, I hope you found it a bit amusing to learn about. Debugging this kind of issue truly keeps me on my toes!
