Windows: ZMQ ipc:// transport not supported — TUI fails to start after /dev/tty fix #41

Closed
opened 2026-05-09 13:42:56 -07:00 by mind6 · 9 comments
mind6 commented 2026-05-09 13:42:56 -07:00 (Migrated from github.com)

Summary

After the /dev/tty fix in #40, the next Windows blocker is that Kaimon binds/connects ZMQ sockets using the ipc:// (Unix domain socket) transport, which the standard ZMQ_jll Windows build does not support. Result: kaimon aborts at TUI init.

Reproducer

Windows 11 / Julia 1.12.5 / Kaimon 1.3.1 with PR #40 applied:

ERROR: ZMQ: Protocol not supported
Stacktrace:
  [1] bind(socket::ZMQ.Socket, endpoint::String)
    @ ZMQ .../ZMQ/src/socket.jl:115
  [2] _start_event_pub!(mgr::Kaimon.ConnectionManager)
    @ Kaimon .../src/gate_client.jl:315
  [3] start!(mgr::Kaimon.ConnectionManager)
    @ Kaimon .../src/gate_client.jl:1525
  [4] init!(m::Kaimon.KaimonModel, _t::Tachikoma.Terminal)
    @ Kaimon .../src/tui/lifecycle.jl:221

Confirmed by direct probe — bind(sock, "ipc://./test.sock") on the ZMQ_jll shipped to Windows raises ZMQ: Protocol not supported. This is not a Kaimon bug per se, but a long-standing libzmq/Windows situation: see zeromq/libzmq#153, zeromq/pyzmq#1462, zeromq/zeromq.js#478. libzmq has experimental named-pipe IPC (PR zeromq/libzmq#3717) but it isn't enabled in the binary builds Julia ships.

Scope of the change

ipc:// is used in five files:

  • src/gate.jl:2092 — gate REQ/REP endpoint per session
  • src/gate.jl:2108 — gate PUB/SUB stream endpoint per session
  • src/gate.jl:2668 — service REP endpoint client-side connect
  • src/gate_client.jl:315 — global event PUB (the first one to crash)
  • src/gate_client.jl:640 — fallback when reading session metadata
  • src/service_endpoint.jl:30,31,101 — service endpoint bind + cleanup
  • src/extension_manager.jl:138 — extension SUB connect

Endpoints are also persisted in session metadata JSON files (sock_dir/<sid>.json), so the wire format on disk is affected too.

Suggested fix: TCP loopback on Windows

Switch transports based on Sys.iswindows():

  • Unix: keep ipc://...sock (cheap, no port allocation).
  • Windows: use tcp://127.0.0.1:<port>, with a port chosen dynamically via bind(sock, \"tcp://127.0.0.1:*\") and the resolved endpoint read back via get_last_endpoint(sock) (ZMQ.jl exposes the LAST_ENDPOINT socket option). Persist the resolved tcp://... URL in session metadata exactly as ipc://... is today — the rest of the code only sees an opaque endpoint string.

Sketch:

function _gate_endpoint(sock_dir, sid; suffix=\"\")
    if Sys.iswindows()
        # caller binds with port=*, then reads LAST_ENDPOINT
        return \"tcp://127.0.0.1:*\"
    else
        return \"ipc://\" * joinpath(sock_dir, \"\$(sid)\$(suffix).sock\")
    end
end

function _bind_dynamic!(sock, endpoint_template)
    bind(sock, endpoint_template)
    # ZMQ.jl: get the resolved endpoint after wildcard bind
    return String(ZMQ.get(sock, ZMQ.LAST_ENDPOINT))
end

Then gate.jl/gate_client.jl save the resolved endpoint (with concrete port) into session metadata, and clients connect to that. Cleanup paths that today rm the .sock file simply become no-ops on Windows.

Trade-offs vs. IPC:

  • Same security posture as IPC for single-user dev workstation (loopback only, OS firewall blocks remote).
  • No libzmq rebuild required.
  • ⚠️ Multiple Kaimon instances on the same machine work fine (dynamic ports), but stale tcp:// entries in old session metadata need explicit cleanup; the existing _maybe_cleanup_stale_session! logic keying off pid/mtime should still work.
  • ⚠️ A loopback ZMQ socket is visible to other local users; if that's a concern, gate it behind ZAP/CURVE auth or document the difference.

Alternative considered: ship a Windows-only ZMQ_jll rebuilt with -DZMQ_HAVE_IPC=ON -DZMQ_HAVE_WINDOWS_NAMED_PIPES=ON. That works but pulls Kaimon into the BinaryBuilder / Yggdrasil rabbit hole and risks divergence from upstream ZMQ_jll. TCP loopback is a smaller, more portable change.

Bigger picture: state of Windows support

For context — there don't appear to be any Windows tracking issues on the repo today, and the Discourse announcement thread doesn't mention Windows. The two real blockers I've hit running kaimon cold on Windows 11 are:

  1. /dev/tty open in _start_stdout_capture! → fixed by #40 (one line).
  2. ZMQ ipc:// everywhere → this issue.

Beyond those, there are a handful of Unix-flavored references (/dev/ttys* paths, the tty shell-out in tool_definitions.jl) but those are explicitly documented as macOS/Linux-only features (external-TTY attach), so they degrade gracefully — they shouldn't block a Windows user from running the core gate + TUI. So the realistic path to "Kaimon works on Windows" looks like:

  • PR #40/dev/ttyCON
  • This issueipc://tcp://127.0.0.1 on Windows
  • Optional: document that the external-TTY (tty-path) feature is Unix-only
  • CI: a Windows runner in GitHub Actions to keep this from regressing

Happy to put together a PR for the TCP-loopback switch if the design above sounds reasonable — wanted to file this first to check whether you'd prefer that approach, a ZMQ_jll rebuild, or something else (e.g. inproc + a single-process design on Windows).

## Summary After the `/dev/tty` fix in [#40](https://github.com/kahliburke/Kaimon.jl/pull/40), the next Windows blocker is that Kaimon binds/connects ZMQ sockets using the `ipc://` (Unix domain socket) transport, which the standard `ZMQ_jll` Windows build does not support. Result: `kaimon` aborts at TUI init. ## Reproducer Windows 11 / Julia 1.12.5 / Kaimon 1.3.1 with PR #40 applied: ``` ERROR: ZMQ: Protocol not supported Stacktrace: [1] bind(socket::ZMQ.Socket, endpoint::String) @ ZMQ .../ZMQ/src/socket.jl:115 [2] _start_event_pub!(mgr::Kaimon.ConnectionManager) @ Kaimon .../src/gate_client.jl:315 [3] start!(mgr::Kaimon.ConnectionManager) @ Kaimon .../src/gate_client.jl:1525 [4] init!(m::Kaimon.KaimonModel, _t::Tachikoma.Terminal) @ Kaimon .../src/tui/lifecycle.jl:221 ``` Confirmed by direct probe — `bind(sock, "ipc://./test.sock")` on the `ZMQ_jll` shipped to Windows raises `ZMQ: Protocol not supported`. This is not a Kaimon bug per se, but a long-standing libzmq/Windows situation: see [zeromq/libzmq#153](https://github.com/zeromq/libzmq/issues/153), [zeromq/pyzmq#1462](https://github.com/zeromq/pyzmq/issues/1462), [zeromq/zeromq.js#478](https://github.com/zeromq/zeromq.js/issues/478). libzmq has experimental named-pipe IPC (PR [zeromq/libzmq#3717](https://github.com/zeromq/libzmq/pull/3717)) but it isn't enabled in the binary builds Julia ships. ## Scope of the change `ipc://` is used in five files: - `src/gate.jl:2092` — gate REQ/REP endpoint per session - `src/gate.jl:2108` — gate PUB/SUB stream endpoint per session - `src/gate.jl:2668` — service REP endpoint client-side connect - `src/gate_client.jl:315` — global event PUB (the first one to crash) - `src/gate_client.jl:640` — fallback when reading session metadata - `src/service_endpoint.jl:30,31,101` — service endpoint bind + cleanup - `src/extension_manager.jl:138` — extension SUB connect Endpoints are also persisted in session metadata JSON files (`sock_dir/<sid>.json`), so the wire format on disk is affected too. ## Suggested fix: TCP loopback on Windows Switch transports based on `Sys.iswindows()`: - **Unix:** keep `ipc://...sock` (cheap, no port allocation). - **Windows:** use `tcp://127.0.0.1:<port>`, with a port chosen dynamically via `bind(sock, \"tcp://127.0.0.1:*\")` and the resolved endpoint read back via `get_last_endpoint(sock)` (ZMQ.jl exposes the `LAST_ENDPOINT` socket option). Persist the resolved `tcp://...` URL in session metadata exactly as `ipc://...` is today — the rest of the code only sees an opaque endpoint string. Sketch: ```julia function _gate_endpoint(sock_dir, sid; suffix=\"\") if Sys.iswindows() # caller binds with port=*, then reads LAST_ENDPOINT return \"tcp://127.0.0.1:*\" else return \"ipc://\" * joinpath(sock_dir, \"\$(sid)\$(suffix).sock\") end end function _bind_dynamic!(sock, endpoint_template) bind(sock, endpoint_template) # ZMQ.jl: get the resolved endpoint after wildcard bind return String(ZMQ.get(sock, ZMQ.LAST_ENDPOINT)) end ``` Then `gate.jl`/`gate_client.jl` save the **resolved** endpoint (with concrete port) into session metadata, and clients connect to that. Cleanup paths that today `rm` the `.sock` file simply become no-ops on Windows. Trade-offs vs. IPC: - ✅ Same security posture as IPC for single-user dev workstation (loopback only, OS firewall blocks remote). - ✅ No libzmq rebuild required. - ⚠️ Multiple Kaimon instances on the same machine work fine (dynamic ports), but stale `tcp://` entries in old session metadata need explicit cleanup; the existing `_maybe_cleanup_stale_session!` logic keying off pid/mtime should still work. - ⚠️ A loopback ZMQ socket is visible to other local users; if that's a concern, gate it behind `ZAP`/CURVE auth or document the difference. Alternative considered: ship a Windows-only `ZMQ_jll` rebuilt with `-DZMQ_HAVE_IPC=ON -DZMQ_HAVE_WINDOWS_NAMED_PIPES=ON`. That works but pulls Kaimon into the BinaryBuilder / Yggdrasil rabbit hole and risks divergence from upstream `ZMQ_jll`. TCP loopback is a smaller, more portable change. ## Bigger picture: state of Windows support For context — there don't appear to be any Windows tracking issues on the repo today, and the Discourse announcement thread doesn't mention Windows. The two real blockers I've hit running `kaimon` cold on Windows 11 are: 1. `/dev/tty` open in `_start_stdout_capture!` → fixed by [#40](https://github.com/kahliburke/Kaimon.jl/pull/40) (one line). 2. ZMQ `ipc://` everywhere → this issue. Beyond those, there are a handful of Unix-flavored references (`/dev/ttys*` paths, the `tty` shell-out in `tool_definitions.jl`) but those are explicitly documented as macOS/Linux-only features (external-TTY attach), so they degrade gracefully — they shouldn't block a Windows user from running the core gate + TUI. So the realistic path to "Kaimon works on Windows" looks like: - [x] PR [#40](https://github.com/kahliburke/Kaimon.jl/pull/40) — `/dev/tty` → `CON` - [ ] **This issue** — `ipc://` → `tcp://127.0.0.1` on Windows - [ ] Optional: document that the external-TTY (`tty`-path) feature is Unix-only - [ ] CI: a Windows runner in GitHub Actions to keep this from regressing Happy to put together a PR for the TCP-loopback switch if the design above sounds reasonable — wanted to file this first to check whether you'd prefer that approach, a `ZMQ_jll` rebuild, or something else (e.g. inproc + a single-process design on Windows).
kahliburke commented 2026-06-08 23:16:20 -07:00 (Migrated from github.com)

@mind6 Thanks and sorry for the delay in responding. I know Windows is a sore spot for things now, I don't use it or have a good environment for testing, so it is challenging. Let me catch up and see what the best option might be.

@mind6 Thanks and sorry for the delay in responding. I know Windows is a sore spot for things now, I don't use it or have a good environment for testing, so it is challenging. Let me catch up and see what the best option might be.
kahliburke commented 2026-06-19 14:49:51 -07:00 (Migrated from github.com)

The IPC→TCP switch is now in on 2.0-integration (8ce7def). Thanks @1-Bart-1 for the detailed design writeup, and @Yihuki — this ports the still-needed pieces of your PR #31, which predated a large refactor (the gate was extracted into a standalone KaimonGate package and gate.jl/gate_client.jl were split), so #31 couldn't merge as-is.

Already in place before this (so it's not in the diff):

  • /dev/ttyCON (#40, @mind6) — landed.
  • The gate's per-session REQ/REP + stream sockets already bind TCP with an ephemeral port on Windows (bind(sock, "tcp://host:0") + read back the resolved port), and serve() defaults to TCP mode on Windows. So concurrent sessions aren't limited.

This change handles the remaining hardcoded ipc:// sockets — the ones that were still crashing startup (your stacktrace pointed at _start_event_pub!). Every change is behind Sys.iswindows(), so the Unix path is unchanged:

  • UTF-8 console code page (SetConsoleOutputCP/CP(65001)) so the TUI renders box-drawing correctly.
  • Event PUB (Kaimon↔extensions): fixed TCP 127.0.0.1:9878.
  • Service endpoint (gate↔Kaimon tool callbacks): fixed TCP 127.0.0.1:9877.

On ports: the service endpoint and event PUB are per-Kaimon-instance singletons (one each, shared by every session/extension), so a fixed port for each is the direct analog of the single fixed kaimon-service.sock / kaimon-events.sock path on Unix — same single-instance assumption, no behavior change vs. today. (The per-session sockets stay dynamic, as above.) Both are overridable via KAIMON_SERVICE_TCP_PORT / KAIMON_EVENT_PUB_TCP_PORT.

I can't test on Windows — the macOS/Linux suites are green (which only proves I didn't break the ipc:// path), but the Windows path is unverified. Could a Windows user confirm? Specifically: a cold kaimon start, a gate connecting (Gate.serve() / start_session), an extension receiving an event, and a tool callback from inside a gate session.

#31 and #40 are effectively superseded by what's now on 2.0-integration.

The IPC→TCP switch is now in on `2.0-integration` (`8ce7def`). Thanks @1-Bart-1 for the detailed design writeup, and @Yihuki — this ports the still-needed pieces of your PR #31, which predated a large refactor (the gate was extracted into a standalone `KaimonGate` package and `gate.jl`/`gate_client.jl` were split), so #31 couldn't merge as-is. **Already in place before this** (so it's not in the diff): - `/dev/tty` → `CON` (#40, @mind6) — landed. - The gate's **per-session** REQ/REP + stream sockets already bind TCP with an **ephemeral port** on Windows (`bind(sock, "tcp://host:0")` + read back the resolved port), and `serve()` defaults to TCP mode on Windows. So concurrent sessions aren't limited. **This change** handles the remaining hardcoded `ipc://` sockets — the ones that were still crashing startup (your stacktrace pointed at `_start_event_pub!`). Every change is behind `Sys.iswindows()`, so the Unix path is unchanged: - UTF-8 console code page (`SetConsoleOutputCP/CP(65001)`) so the TUI renders box-drawing correctly. - **Event PUB** (Kaimon↔extensions): fixed TCP `127.0.0.1:9878`. - **Service endpoint** (gate↔Kaimon tool callbacks): fixed TCP `127.0.0.1:9877`. On ports: the service endpoint and event PUB are **per-Kaimon-instance singletons** (one each, shared by every session/extension), so a fixed port for each is the direct analog of the single fixed `kaimon-service.sock` / `kaimon-events.sock` path on Unix — same single-instance assumption, no behavior change vs. today. (The *per-session* sockets stay dynamic, as above.) Both are overridable via `KAIMON_SERVICE_TCP_PORT` / `KAIMON_EVENT_PUB_TCP_PORT`. **I can't test on Windows** — the macOS/Linux suites are green (which only proves I didn't break the `ipc://` path), but the Windows path is unverified. **Could a Windows user confirm?** Specifically: a cold `kaimon` start, a gate connecting (`Gate.serve()` / `start_session`), an extension receiving an event, and a tool callback from inside a gate session. #31 and #40 are effectively superseded by what's now on `2.0-integration`.
BambOoxX commented 2026-06-30 06:56:37 -07:00 (Migrated from github.com)

Hi I just tested on my Windows machine with the latest commit (8ba732f8fc), and it seems to work !
Side note, display works in VSCode, but the keyboard interaction is a bit sketchy, switching panels only works with caps lock, (not with shift or num pad).

Hi I just tested on my Windows machine with the latest commit (8ba732f8fc0371c7040ff41ebfc7f1af6985f7dc), and it seems to work ! Side note, display works in VSCode, but the keyboard interaction is a bit sketchy, switching panels only works with caps lock, (not with shift or num pad).
kahliburke commented 2026-06-30 10:45:41 -07:00 (Migrated from github.com)

@BambOoxX Thanks for the report. That is strange on the VSCode side, you're using the built in terminal in vscode is what I gather? Luckily you can also mouse click those (or at least I can ;))

@BambOoxX Thanks for the report. That is strange on the VSCode side, you're using the built in terminal in vscode is what I gather? Luckily you can also mouse click those (or at least I can ;))
BambOoxX commented 2026-06-30 10:50:21 -07:00 (Migrated from github.com)

@kahliburke Yup, just the basic VSCode terminal, and the mouse does not work either it seems. In the default windows terminal, keyboard works fine, but only the right-click seems to work and goes to tab 0

@kahliburke Yup, just the basic VSCode terminal, and the mouse does not work either it seems. In the default windows terminal, keyboard works fine, but only the right-click seems to work and goes to tab 0
kahliburke commented 2026-06-30 11:28:11 -07:00 (Migrated from github.com)

@BambOoxX Terminal support in windows is so much worse :( makes me sad. I was under the impression that newer versions of VSCode had enhanced things to use the kitty terminal protocol but I've never tested it. A quick search brought up this json to go into settings.json file, maybe worth a try?

{
    // Required for Windows to correctly pass modern escape sequences
    "terminal.integrated.windowsUseConptyDll": true,

    // Activates advanced key mappings (e.g., distinguishing Ctrl+I from Tab)
    "terminal.integrated.enableKittyKeyboardProtocol": true,

    // Recommended: Boosts rendering performance for terminal images
    "terminal.integrated.gpuAcceleration": "on"
}
@BambOoxX Terminal support in windows is so much worse :( makes me sad. I was under the impression that newer versions of VSCode had enhanced things to use the kitty terminal protocol but I've never tested it. A quick search brought up this json to go into settings.json file, maybe worth a try? ``` { // Required for Windows to correctly pass modern escape sequences "terminal.integrated.windowsUseConptyDll": true, // Activates advanced key mappings (e.g., distinguishing Ctrl+I from Tab) "terminal.integrated.enableKittyKeyboardProtocol": true, // Recommended: Boosts rendering performance for terminal images "terminal.integrated.gpuAcceleration": "on" } ```
BambOoxX commented 2026-07-01 08:14:20 -07:00 (Migrated from github.com)

@kahliburke unfortunately, these lines changed nothing in my VSCode...

@kahliburke unfortunately, these lines changed nothing in my VSCode...
kahliburke commented 2026-07-01 13:10:44 -07:00 (Migrated from github.com)

@BambOoxX Thanks for trying and the feedback. Have you used or considered installing WSL2? I believe it could offer you a better experience in many ways ... you could run terminal packages and VSCode through that, I've seen it work quite well.

@BambOoxX Thanks for trying and the feedback. Have you used or considered installing WSL2? I believe it could offer you a better experience in many ways ... you could run terminal packages and VSCode through that, I've seen it work quite well.
kahliburke commented 2026-07-08 22:19:33 -07:00 (Migrated from github.com)

@BambOoxX I'm closing this out as 2.0 has shipped and addresses at least some of what's described. If we want to iterate on more Windows fixes and support, please open an issue against it.

@BambOoxX I'm closing this out as 2.0 has shipped and addresses at least some of what's described. If we want to iterate on more Windows fixes and support, please open an issue against it.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kahliburke/Kaimon.jl#41
No description provided.