Windows: APE exit status is shifted left by 8 (exit code does not round-trip; failures read as success in POSIX shells)

Author: farkasmarkCreated Sep 9, 2026Updated Sep 10, 2026

Summary

On Windows, a program built with cosmocc exits with its status shifted left by 8 — i.e. the raw wait()-style status rather than the exit code. return 1 becomes a Windows process exit code of 256, return 42 becomes 10752.

The practical consequence is that in a POSIX-style shell on Windows (MSYS2, Cygwin, Git Bash) every failure looks like success, because those shells keep only the low byte and code << 8 has a low byte of 0 for any code < 256. cmd.exe and PowerShell see the shifted value instead. Either way, the exit status does not round-trip.

I hit this in CI for a project that ships an APE: a build tool correctly called exit(1), printed its error, wrote no output file, and the shell still saw success. Anything of the form mytool && next-step silently proceeds after a failure.

Reproduction

c
/* exitcode.c */
#include <stdlib.h>
int main(int argc, char **argv) {
    return argc > 1 ? atoi(argv[1]) : 1;
}
bash
cosmocc -O0 -o exitcode.com exitcode.c

Run it, asking for a specific exit code, and compare what the shell reports:

bash
# MSYS2 bash
for c in 0 1 2 3 42 127 255; do ./exitcode.com $c; echo "$c -> $?"; done
powershell
# PowerShell
foreach ($c in 0,1,2,42,255) { & .\exitcode.exe $c; "$c -> $LASTEXITCODE" }
bat
:: cmd.exe (delayed expansion, otherwise %ERRORLEVEL% expands at parse time)
cmd /v:on /c "exitcode.exe 42 & echo !ERRORLEVEL!"

Observed

requested MSYS2 bash $? PowerShell $LASTEXITCODE cmd !ERRORLEVEL! code << 8
0 0 0 0 0
1 0 256 256 256
2 0 512 512 512
3 0 768 768
42 0 10752 10752 10752
127 0 32512 32512
255 0 65280 65280 65280

The observed value equals code << 8 exactly, for every value tested.

Expected

The exit code should round-trip: return 42 should be observable as 42.

Control — the same source built with a native Windows compiler

Same .c, same shells, built with MSYS2's UCRT64 GCC, so this isolates the behaviour to the APE rather than to the shells:

requested native gcc (bash) native gcc (PowerShell) cosmocc APE (PowerShell)
0 0 0 0
1 1 1 256
2 2 2 512
42 42 42 10752
255 255 255 65280

exit(n) and return n from main behave identically; both are affected.

Only the 0 case is correct, which is why this is easy to miss — a passing build behaves exactly as expected, and only failures are silently swallowed.

Environment

  • Windows 11 Pro, 10.0.26200 build 26200, AMD64
  • cosmocc 4.0.2 from https://cosmo.zip/pub/cosmocc/cosmocc-4.0.2.zip (sha256 85b8c37a406d862e656ad4ec14be9f6ce474c1b436b9615e91a55208aced3f44)
  • cosmocc (GCC) 14.1.0
  • Shells: MSYS2 (MSYS) bash, PowerShell 5.1.26100.9444, cmd.exe

Not verified

I did not have a Linux machine available to run the same .com as a control, so I have not confirmed first-hand that the same binary round-trips its exit code on Linux — only that a native Windows binary does on Windows. If it would help, I am happy to run any additional probe.