{"slug": "the-newline-that-silenced-every-windows-agent", "title": "The Newline That Silenced Every Windows Agent", "summary": "Munder Difflin's Windows agents silently failed to communicate because cmd.exe truncates command-line arguments at the first newline, causing multi-line startup protocols to be cut to a single line. The bug, which affected half of the company's downloads, was fixed in version 0.4.4 by bypassing cmd.exe and launching the real interpreter directly with argv arrays.", "body_md": "# The Newline That Silenced Every Windows Agent\n\nA cmd.exe parsing rule from the 1980s meant Windows agents received exactly one line of their multi-line startup protocol — and nothing errored. The anatomy of our worst silent failure, and the fix.\n\nWindows agents booted, rendered, and\nanswered prompts — while never sending or receiving a single message. The cause: npm installs\nCLIs as `.cmd`\n\nshims, `.cmd`\n\nfiles must run through `cmd.exe`\n\n,\nand **cmd.exe truncates an argument at its first newline**. Our startup protocol is\nmulti-line. Every Windows agent got line one and lost the rest. Here's the full anatomy, because\nthis bug class is hiding in more codebases than ours.\n\nHalf of our downloads are Windows. For an uncomfortably long time, the core of the product —\n[agents talking to each other](/blog/can-claude-code-agents-talk-to-each-other/) — did not work\nfor those users, and neither they nor we could tell. This is the autopsy.\n\n## The setup [#](#the-setup)\n\nWhen Munder Difflin spawns an agent, it passes the **hive protocol** as a command-line argument\nto the engine CLI: a multi-line block that names the agent, points at its `inbox/`\n\nand\n`outbox/`\n\nfolders, explains the [file-mailbox rules](/blog/atomic-file-mailboxes-for-agents/),\nand tells it where its memory lives. One argument, many lines. On POSIX systems this is\ncompletely unremarkable — argv entries can contain any byte except NUL, and newlines ride along\nhappily.\n\n## The three-layer trap [#](#the-three-layer-trap)\n\n**Layer one: npm shims.** On Windows, `npm install -g`\n\ndoesn’t put a real executable on your\nPATH. It writes a `.cmd`\n\nbatch shim that locates node and runs the actual JS entry point.\n\n**Layer two: CreateProcess.** A `.cmd`\n\nis not a PE executable, so it can’t be the target of\n`CreateProcess`\n\ndirectly. Node’s `child_process`\n\n(and everything built on it) handles this by\nsilently rewriting your spawn into `cmd.exe /d /s /c \"your command here\"`\n\n.\n\n**Layer three: cmd.exe.** And cmd.exe, parsing that command string with rules essentially\nunchanged since DOS, **stops at the first newline**. Everything after it isn’t escaped or\nmangled — it’s simply gone.\n\nStack the layers and you get: a multi-line protocol enters the spawn, and a one-line protocol arrives at the agent. Deterministically. On every Windows machine. With zero errors.\n\n## Why it was invisible [#](#why-it-was-invisible)\n\nHere’s what made this bug expensive: **every observable signal said healthy.**\n\nThe process spawned — exit code irrelevant, it’s a long-running CLI. The agent’s terminal\nrendered in the app and responded to typing. The engine received *a* prompt (line one:\nsomething like “You are an autonomous agent in a collaborating hive”), which is coherent enough\nthat the agent behaved plausibly. It just never mentioned inboxes, because it had never heard\nof them.\n\nFrom the floor’s perspective, Windows agents were simply… quiet. And “the agent didn’t happen to send mail” is indistinguishable from “the agent cannot send mail” unless you go looking. No log line existed to catch, because truncation isn’t a failure — cmd.exe was working as documented. The documentation is just from 1987.\n\n## The fix: refuse the middleman [#](#the-fix-refuse-the-middleman)\n\nThe fix is conceptually simple: **never let a prompt-carrying spawn touch cmd.exe.** The spawner\nnow opens the `.cmd`\n\nshim, decodes what it actually points at — npm shims are formulaic — and\nlaunches the real interpreter directly: `node.exe C:\\...\\cli.js --args`\n\n, passed as an argv\narray, which goes through `CreateProcess`\n\nwith the multi-line argument intact.\n\nThen reality added a second chapter, as it does. OpenCode’s npm package ships a *compiled\nbinary*, so npm writes an interpreter-less shim the decoder didn’t model — it returned null for\nevery Windows OpenCode install and fell back to the truncating path. That’s the\n[0.4.4](/blog/launching-munder-difflin-v0-4-4/) follow-up: direct-executable shims are handled,\nand — the real lesson — **the fallback is no longer silent.** If the decoder meets a shim it\ncan’t parse, it logs exactly what it couldn’t decode. The next variant of this bug will\nannounce itself.\n\n## The takeaways we actually wrote down [#](#the-takeaways-we-actually-wrote-down)\n\n**A fallback that doesn’t log is a bug with a delay timer.** The first fix’s silent fallback is why the OpenCode variant survived a release.**“Nothing errored” is a claim, not evidence.** The failure mode of dropping*part*of an input is nastier than crashing, because every health check you own passes. Our[auto-update postmortem](/blog/why-our-auto-update-never-ran/)is the same lesson wearing a different coat.**Test the platform’s spawn path, not your code’s intent.** Our protocol handling was correct on every platform. The three layers underneath it were not ours — and the user doesn’t care whose layer it was.\n\nIf you’re building anything that passes structured prompts to CLIs on Windows —\n[hooks](/blog/the-hook-shim-pattern/), harnesses, wrappers — go check what your spawns do when\nthe target is a `.cmd`\n\n. We’ll wait. It’s probably ten minutes and one very bad surprise.\n\n## FAQ\n\nWhy did agent messaging fail only on Windows?\n\nOn macOS and Linux, the multi-line hive protocol passes to the engine binary as one argv entry, newlines intact. On Windows, npm installs CLIs as .cmd shims, which can't go directly to CreateProcess — so the spawn ran through cmd.exe, and cmd.exe cuts an argument at its first newline. The agent got line one of its job description and nothing else.\n\nWhy didn't anything error?\n\nBecause nothing failed by any definition the code knew. The process spawned, the engine started, the agent answered prompts. The only casualty was the text after the first newline — which happened to contain everything about inboxes, outboxes, and memory. Truncation isn't an error; it's just less.\n\nHow was it fixed?\n\nThe spawner now reads the .cmd shim, extracts the real interpreter and script it points at, and launches that directly with an argv array — no cmd.exe in the path. A follow-up handled npm's interpreter-less shims for compiled binaries like OpenCode's. Anything undecodable falls back to the old path, but now logs what it couldn't decode.", "url": "https://wpnews.pro/news/the-newline-that-silenced-every-windows-agent", "canonical_source": "https://munderdiffl.in/blog/the-newline-that-silenced-windows-agents/", "published_at": "2026-08-19 00:00:00+00:00", "updated_at": "2026-08-19 07:11:20.547936+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools"], "entities": ["Munder Difflin", "Windows", "cmd.exe", "npm", "OpenCode"], "alternates": {"html": "https://wpnews.pro/news/the-newline-that-silenced-every-windows-agent", "markdown": "https://wpnews.pro/news/the-newline-that-silenced-every-windows-agent.md", "text": "https://wpnews.pro/news/the-newline-that-silenced-every-windows-agent.txt", "jsonld": "https://wpnews.pro/news/the-newline-that-silenced-every-windows-agent.jsonld"}}