Windows: MCP servers spawned under Bun never complete the stdio handshake (-32000); Node works
Channel plugins (telegram, and by extension any plugin whose MCP server runs under Bun) never connect on Windows: the server process starts and does real work, but the MCP stdio handshake never completes and Claude Code closes the connection with -32000.
Two earlier reports — #36964 and #39776 — describe the same symptom from different angles. Both are closed and locked, so I can't add to them; this issue exists to hold the isolated diagnosis and a fix.
Environment
- Windows 11 Pro 10.0.26200
- Claude Code 2.1.226 (npm-global install)
telegram@claude-plugins-officialv0.0.6- Bun 1.3.14 (official bun.sh installer,
~/.bun/bin/bun.exe) - Node 24.14.0
What happens
With the stock plugin config, /mcp shows plugin:telegram:telegram ✗ failed and the debug log has:
MCP server "plugin:telegram:telegram": Starting connection with timeout of 30000ms
MCP server "plugin:telegram:telegram" Server stderr: "bun" is not recognized as an internal or external command
MCP server "plugin:telegram:telegram": Connection failed after 238ms (-32000): MCP error -32000: Connection closed
Pointing command at an absolute bun.exe path gets past that, and then the more interesting failure appears:
MCP server "plugin:telegram:telegram": Starting connection with timeout of 30000ms
MCP server "plugin:telegram:telegram" Server stderr: <cannot find path>
MCP server "plugin:telegram:telegram": Connection failed after 18938ms (-32000)
The server is not dead at this point. It writes its pid file and polls Telegram successfully — getUpdates is live, inbound messages are received. What never arrives is anything on stdin: the initialize request is never delivered, so await mcp.connect(new StdioServerTransport()) never resolves. That also explains #39776's observation that "code after mcp.connect() never executes" — the IIFE isn't dropped, the await simply never returns. Claude Code gives up after ~19s and the server is left behind as an orphaned poller holding the Telegram long-poll slot.
Isolation
| # | Setup | Result |
|---|---|---|
| 1 | bun server.ts by hand, stdin held open | ✅ polls, emits notifications/claude/channel for pending updates |
| 2 | Same, with PATH stripped to system32;Windows | ✅ works — rules out env-var dependencies inside the server |
| 3 | Spawned by Claude Code, absolute bun.exe path | ❌ server runs and polls, handshake times out, -32000 |
| 4 | Spawned by Claude Code, node.exe instead, same script | ✅ Successfully connected (transport: stdio) in 2990ms, Channel notifications registered, full DM round-trip incl. permission-prompt relay |
Rows 3 and 4 differ only in the runtime binary, which points at Bun's stdin pipe not interoperating with the pipes a Node parent creates on Windows.
Secondary observation: bun not resolving
The first-stage failure ("bun" is not recognized) reproduced even with bun.exe on the launching shell's PATH and the session started from a fresh terminal. I could not isolate whether the MCP spawn environment is sanitized or whether a stale environment is inherited somewhere up the chain, so I'm reporting the observation rather than asserting the mechanism.
Worth noting regardless: an npm-installed Bun provides only a bun.cmd shim with no bun.exe, and CreateProcess cannot execute a .cmd without a shell — so every user who installed Bun via npm hits this unconditionally, independent of PATH.
Fix
server.ts in the telegram plugin uses only erasable TypeScript and no Bun-specific APIs, so it runs unmodified under Node ≥ 22.6 via type stripping — and Node is guaranteed present because Claude Code ships through npm. I've opened anthropics/claude-plugins-official#5157, which adds a launcher keeping Bun on POSIX and using Node on win32.
That fixes the channel plugins, but the underlying issue belongs here: any plugin MCP server spawned under Bun on Windows will hit the same stdio wall.