fix(win): put stdout in binary mode before the serve handshake (closes #748) - #749
Merged
Conversation
kimi_k3 and inkling never called _setmode(..., _O_BINARY). On Windows the CRT
opens stdout in TEXT mode and rewrites '\n' as '\r\n', so the engine emits
\x01\x01READY\x01\x01\r\n
and coli -- which matches the sentinel byte-exactly -- waits for a byte that was
mangled on the way out. No error is printed, because nothing failed: one side is
waiting, the other already sent something else.
@brad-evony hit it in #748 on Windows 10: 93 layers loaded over 42 minutes, the
tokenizer read, and then silence. Ctrl-C landed in read_engine_turn's
stream.read(1), which is exactly where the handshake blocks.
colibri.c has done this since #195 and its comment describes the symptom word
for word ('the READY sentinel never matches and chat hangs'). The two newer
engines were written without it and nothing noticed for months. Not a v1.4.0
regression -- the bug predates it. v1.4.0 only made it reachable, because before
that the archives contained no kimi_k3.exe at all (#720).
It goes in compat.h rather than being pasted into each engine: pasting is how it
went missing, and this is the third Windows-specific behaviour to be found
living in colibri.c alone (after select()/fd_set in #736).
Adds tests/test_serve_sentinel.c, which writes the handshake through a real
stream and asserts the bytes contain no CR. Nothing in the tree looked at those
bytes before, which is why this reached a user rather than CI. It can only pass
on Linux/macOS -- that is the point of a regression guard, and CI builds every
engine on Windows since #736, so it runs where it can fail.
olmoe.c has no serve mode and is untouched. colibri.c is untouched: it is
already correct, and 14 open PRs touch that file.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
minne100
added a commit
to minne100/colibri
that referenced
this pull request
Aug 1, 2026
Addresses the main blocker on JustVugg#712: the engine built clean and touched no existing files, but coli could not reach it. c/coli: - model_arch(): return 'qwen' for any model_type containing 'qwen' - engine_for(): resolve 'qwen36' binary - need_model(): qwen36 build target - chat banner model_id: qwen36-colibri c/qwen36.c (SERVE=1 mode): - implements the colibri gateway wire protocol (READY + STAT + SUBMIT/ CANCEL + DATA + DONE + PROF), mirroring kimi_k3.c / inkling.c so 'coli chat' / 'coli web' / 'coli serve' can drive the engine - READY/STAT handshake, SUBMIT payload -> encode_text -> step() prefill + per-token step() decode, stream decoded bytes as DATA frames, stop on EOS (Qwen3 151645, Q36_EOS override), DONE/STAT on completion - temperature + top-p sampler (ported from kimi_k3.c, qsort O(V log V)) - Windows binary-mode fix inlined (JustVugg#748 hang: CRT rewrites n->rn and the gateway never matches READY). Uses _setmode(_fileno(stdout/stdin), _O_BINARY) before the sentinel; switch to compat.h's coli_serve_binary_mode() once JustVugg#749 lands (helper not in base yet).
ErikTromp
added a commit
to ErikTromp/colibri-hy3
that referenced
this pull request
Aug 3, 2026
Port JustVugg#749's shared helper so Hy3 (and inkling/kimi on this merge base) do not hang forever on Windows when CRT TEXT mode rewrites the sentinel as CRLF (JustVugg#748). Co-authored-by: Cursor <cursoragent@cursor.com>
kreuzzelg
pushed a commit
to kreuzzelg/colibri
that referenced
this pull request
Aug 3, 2026
Addresses the main blocker on JustVugg#712: the engine built clean and touched no existing files, but coli could not reach it. c/coli: - model_arch(): return 'qwen' for any model_type containing 'qwen' - engine_for(): resolve 'qwen36' binary - need_model(): qwen36 build target - chat banner model_id: qwen36-colibri c/qwen36.c (SERVE=1 mode): - implements the colibri gateway wire protocol (READY + STAT + SUBMIT/ CANCEL + DATA + DONE + PROF), mirroring kimi_k3.c / inkling.c so 'coli chat' / 'coli web' / 'coli serve' can drive the engine - READY/STAT handshake, SUBMIT payload -> encode_text -> step() prefill + per-token step() decode, stream decoded bytes as DATA frames, stop on EOS (Qwen3 151645, Q36_EOS override), DONE/STAT on completion - temperature + top-p sampler (ported from kimi_k3.c, qsort O(V log V)) - Windows binary-mode fix inlined (JustVugg#748 hang: CRT rewrites n->rn and the gateway never matches READY). Uses _setmode(_fileno(stdout/stdin), _O_BINARY) before the sentinel; switch to compat.h's coli_serve_binary_mode() once JustVugg#749 lands (helper not in base yet).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Kimi K3 and Inkling hang forever on Windows in
coli chat,coli webandcoli serve.What happens
The engine announces itself with a byte sentinel:
colimatches it byte-exactly. On Windows the CRT opens stdout in TEXT mode and rewrites\nas\r\n, so what actually arrives is\x01\x01READY\x01\x01\r\n— and the gateway waits for a byte that was mangled on the way out. No error is printed, because nothing failed: one side is waiting, the other already sent something else.@brad-evony hit this in #748 on Windows 10: 93 layers loaded over 42 minutes, tokenizer read, then silence. His Ctrl-C traceback lands in
read_engine_turn’sstream.read(1)— exactly where the handshake blocks.Why it was there to hit
colibri.chas called_setmode(..., _O_BINARY)since #195, and its comment describes the symptom word for word:colibri.cinkling.c,kimi_k3.cNot a v1.4.0 regression — the bug predates it. v1.4.0 only made it reachable: before that the archives contained no
kimi_k3.exeat all (#720), so no Windows user could get far enough to hang.The fix, and where it lives
coli_serve_binary_mode()incompat.h, called by both serve loops before the sentinel.In
compat.hrather than pasted into each engine, because pasting is how it went missing. This is now the third Windows-specific behaviour found living incolibri.calone — afterselect()/fd_setin #736. Each time, the newer engines were written without it and nothing noticed.The test that should have existed
tests/test_serve_sentinel.cwrites the handshake through a real stream and asserts the bytes contain no CR. Nothing in the tree had ever looked at those bytes, which is precisely why this reached a user instead of CI.It can only pass on Linux/macOS — that is what a regression guard is, and CI has built every engine on Windows since #736, so it now runs where it can actually fail. It is picked up automatically by the rule-derived gate list from #733.
Scope
compat.h,kimi_k3.c(+2 lines),inkling.c(+2), one new test, one Makefile rule.olmoe.chas no serve mode and is untouched.colibri.cis untouched — it is already correct, and 14 open PRs touch that file.All four engines build;
make test-cpasses.