From a0c68d7e16b5ac6a7e82911a5eef06286843be09 Mon Sep 17 00:00:00 2001 From: davidwed Date: Fri, 23 Oct 2026 09:14:07 +0200 Subject: [PATCH] fix: official Windows release binary crashed on startup The MSVC release build exited immediately after launch with 0xC0000409 (FAST_FAIL_INVALID_ARG, subcode 5) before drawing any frame. MinGW/clang64 builds of the same source were unaffected. Cause: setvbuf(stderr, nullptr, _IOLBF, 0) in the stderr redirect block. With a NULL buffer the UCRT validates size-2 against INT_MAX-2, so size=0 underflows to 0xFFFF...FE and is rejected as an invalid argument. The invalid-parameter handler then calls __fastfail(FAST_FAIL_INVALID_ARG). freopen() had already succeeded, which is why the log file was created but left empty. The C standard permits a NULL buffer with size 0 (size is ignored), but the UCRT does not. This only surfaced in the official binary because it links the MSVC UCRT statically (AGENTTY_STANDALONE + vcpkg x64-windows-static, 0 api-ms-win-crt imports), while a local clang64 build links it dynamically (13 imports) and tolerates the call. Change: pass BUFSIZ instead of 0, which is the conventional value for line-buffered stderr and keeps the intended line-buffering. No behaviour change on the non-crashing platforms. --- src/runtime/main.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/runtime/main.cpp b/src/runtime/main.cpp index cd612244..bfcb7ebc 100644 --- a/src/runtime/main.cpp +++ b/src/runtime/main.cpp @@ -2436,7 +2436,14 @@ int run_main(int argc, char** argv, const maya::unlocked& cap) { // Append so a crash's trailing output survives across sessions; // truncation would lose the very lines you'd want post-mortem. if (std::freopen(logpath.string().c_str(), "a", stderr)) { - std::setvbuf(stderr, nullptr, _IOLBF, 0); // line-buffered + // size must be non-zero: MSVC's UCRT validates `size - 2` against + // INT_MAX and rejects 0 as an invalid argument, which runs the + // invalid-parameter handler -> __fastfail(FAST_FAIL_INVALID_ARG) + // -> 0xC0000409 before the TUI ever draws a frame. The C standard + // allows a NULL buffer with size 0 (size is then ignored), but the + // UCRT does not, and only the MSVC release build links that CRT -- + // which is why a MinGW/clang64 build of the same source survived. + std::setvbuf(stderr, nullptr, _IOLBF, BUFSIZ); // line-buffered std::fprintf(stderr, "\n=== agentty session %ld ===\n", static_cast(agentty_pid())); }