fix(cli): make the Windows release exe survive a double click (#183)

A console program started from Explorer gets a console of its own, prints
its usage because no model was given, exits, and the console vanishes with
it: a window that flashes and disappears, indistinguishable from a crash.

- cli: when the console was created for this process alone, the no-model
  path says it is a command-line program and waits for Enter before
  closing. From a terminal nothing changes.
- release-host: the archive README.txt opens with that fact and a complete
  command, and no longer points a Windows user at a bash script as the
  only instruction. The Windows build links the MSVC runtime statically so
  the exe does not depend on the VC++ Redistributable.
- 0.23.0: version bump and changelog entry.
This commit is contained in:
Raffaele 2026-08-29 09:01:00 +02:00 • committed by GitHub
parent 039b8f1e2f
commit d754dfd6fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 58 additions and 8 deletions

View file

@ -45,7 +45,8 @@ jobs:
cmake_extra: ""
- os: windows-latest
target: windows-x86_64
cmake_extra: -DGGML_AVX2=ON -DGGML_FMA=ON -DGGML_F16C=ON
# Static MSVC runtime: the exe must not depend on the VC++ Redistributable being installed.
cmake_extra: -DGGML_AVX2=ON -DGGML_FMA=ON -DGGML_F16C=ON -DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded
runs-on: ${{ matrix.os }}
steps:
- name: Resolve tag
@ -101,12 +102,26 @@ jobs:
strip "$NAME/bmoe-cli" || true
fi
cp LICENSE "$NAME/"
if [ "${{ runner.os }}" = "Windows" ]; then
EXE='.\bmoe-cli.exe'; SHELL_NAME='PowerShell or cmd'
else
EXE='./bmoe-cli'; SHELL_NAME='a terminal'
fi
cat > "$NAME/README.txt" <<EOF
bmoe-cli $TAG, ${{ matrix.target }} (portable build: x86_64 assumes AVX2, aarch64 assumes
armv8.2-a+dotprod+fp16). If it fails to start on your CPU, build from source:
bmoe-cli $TAG, ${{ matrix.target }}
This is a command-line program. Double-clicking it opens a console that closes again at
once, because it was given no model. Open $SHELL_NAME in this folder and run:
$EXE -m path/to/model.gguf --moe-stream --overlap --dense-weights anon --cache-mb 2000 --io-threads 4 -t 8 -n 64 --chatml --no-think -p "Explain MoE routing."
-m is the only required flag; $EXE --help lists the rest and $EXE --list-archs the
supported MoE architectures. Portable build: x86_64 assumes AVX2, aarch64 assumes
armv8.2-a+dotprod+fp16. If it fails to start on your CPU, build from source with
scripts/build-host.sh in the repository, one command.
Benchmark on this machine, printing the tables for a benchmark-report issue:
Benchmark on this machine, printing the tables for a benchmark-report issue (Linux and
macOS; on Windows run the protocol command from docs/community-benchmarks.md by hand):
BMOE_CLI=\$PWD/bmoe-cli scripts/bench-report.sh /path/to/model.gguf
from a checkout of https://github.com/Helldez/BigMoeOnEdge (docs/community-benchmarks.md).
EOF

View file

@ -4,7 +4,18 @@ All notable changes to this project are documented here. The format follows
[Keep a Changelog](https://keepachangelog.com/), and the project aims to follow
Semantic Versioning.
## [0.23.0] - 2026-08-28
## [0.23.0] - 2026-08-29
### Fixed
- **Windows `bmoe-cli.exe` looked broken when double-clicked (#181).** A console program started
from Explorer gets a console of its own, prints its usage because no model was given, exits,
and the console vanishes with it: a window that flashes and disappears, indistinguishable from a
crash. Now, when the console was created for this process alone, the no-model path says it is a
command-line program and waits for Enter before closing; from a terminal nothing changes. The
release archive's README.txt opens with the same fact and a complete Windows command, and no
longer points a Windows user at a bash script as the only instruction. The Windows build also
links the MSVC runtime statically, so the exe no longer needs the VC++ Redistributable to start.
### Added
- **Prefill-phase attribution in `BMOE_DONE` and the CSV `# summary`.** `prefill_cpu_s` /

View file

@ -19,7 +19,7 @@ cmake_minimum_required(VERSION 3.21)
# The version is declared here and nowhere else in the build: the engine reports it (bmoe-cli
# --version, and the run-parameter preamble of every metrics CSV), so a committed benchmark file
# says which engine produced it. Keep it in step with CHANGELOG.md and the app's versionName.
project(bigmoeonedge VERSION 0.22.0 LANGUAGES CXX)
project(bigmoeonedge VERSION 0.23.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

View file

@ -29,6 +29,11 @@
#include <string>
#include <thread>
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
using namespace bmoe;
static int env_int(const char * k, int dflt) {
@ -353,6 +358,18 @@ static int run_session_loop(const RunConfig & cfg,
return rc;
}
// True when Explorer (a double click) created this console for us alone, so it will vanish the
// instant we return and nothing we printed gets read. A terminal the user already had open also
// holds the console and stays; the process count tells the two apart.
static bool console_is_ours_alone() {
#if defined(_WIN32)
DWORD pid;
return GetConsoleProcessList(&pid, 1) == 1;
#else
return false;
#endif
}
static void print_usage(const char * argv0) {
std::printf(
"usage: %s -m <model.gguf> [options]\n"
@ -709,6 +726,13 @@ int main(int argc, char ** argv) {
if (cfg.model_path.empty()) {
print_usage(argv[0]);
// Double-clicked: without this the window closes before the usage can be read, and the
// program looks like it failed to start.
if (console_is_ours_alone()) {
std::fprintf(stderr, "\nbmoe-cli is a command-line program: run it from a terminal with -m <model.gguf>.\n"
"Press Enter to close this window.\n");
std::getchar();
}
return 1;
}

View file

@ -51,8 +51,8 @@ android {
applicationId 'io.bigmoeonedge.example'
minSdk 29
targetSdk 34
versionCode 37
versionName '0.22.0'
versionCode 38
versionName '0.23.0'
buildConfigField 'String', 'GIT_SHA', "\"${gitSha}\""
ndk {
// The engine ships as prebuilt arm64 binaries staged by build-android.ps1.