<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Eliseo Fernandez Suarez</title>
    <description>The latest articles on DEV Community by Eliseo Fernandez Suarez (@eliseomdq).</description>
    <link>https://dev.to/eliseomdq</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4083985%2F07a4e145-07a5-4f87-93fc-5fd10a8ad865.jpeg</url>
      <title>DEV Community: Eliseo Fernandez Suarez</title>
      <link>https://dev.to/eliseomdq</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eliseomdq"/>
    <language>en</language>
    <item>
      <title>How we run five coding agents side by side in one window</title>
      <dc:creator>Eliseo Fernandez Suarez</dc:creator>
      <pubDate>Sun, 23 Aug 2026 23:12:04 +0000</pubDate>
      <link>https://dev.to/eliseomdq/how-we-run-five-coding-agents-side-by-side-in-one-window-32gf</link>
      <guid>https://dev.to/eliseomdq/how-we-run-five-coding-agents-side-by-side-in-one-window-32gf</guid>
      <description>&lt;p&gt;I build NestMux, a desktop app that runs Claude Code, Codex, Gemini CLI, Copilot and OpenCode in a grid of real terminals, each with its own account and its own git worktree. This is how it works inside, including the parts that are heuristics wearing a confident UI and the parts that do not survive a restart.&lt;/p&gt;

&lt;p&gt;If you are building something similar, most of this transfers. If you are deciding whether to use it, this is the honest version of what it does.&lt;/p&gt;

&lt;p&gt;Thirty-five seconds of it running, so the rest of this has something to attach to:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/p11NPjZUQi4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The unit is a pane, and a pane is smaller than you would think
&lt;/h2&gt;

&lt;p&gt;Every cell in the grid is described by a plain object. Simplified, and this really is most of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PaneNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;aiType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;claude&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;codex&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;copilot&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opencode&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;terminal&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;custom&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="nx"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;accountDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;        &lt;span class="c1"&gt;// becomes HOME for this pane's processes&lt;/span&gt;
  &lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;               &lt;span class="c1"&gt;// '' means a plain shell&lt;/span&gt;
  &lt;span class="nx"&gt;repoPath&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;         &lt;span class="c1"&gt;// cwd override: the worktree this pane works in&lt;/span&gt;
  &lt;span class="nx"&gt;shellId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;          &lt;span class="c1"&gt;// which shell to spawn on Windows&lt;/span&gt;
  &lt;span class="nx"&gt;borderColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no AI-specific code behind any of it. A pane spawns a shell with &lt;code&gt;node-pty&lt;/code&gt;, redirects &lt;code&gt;HOME&lt;/code&gt; to &lt;code&gt;accountDir&lt;/code&gt;, sets the cwd to &lt;code&gt;repoPath&lt;/code&gt;, and types &lt;code&gt;cmd&lt;/code&gt; into it. Claude Code is a pane whose &lt;code&gt;cmd&lt;/code&gt; is &lt;code&gt;claude&lt;/code&gt;. A plain terminal is a pane whose &lt;code&gt;cmd&lt;/code&gt; is empty. Adding support for a new agent CLI is a row in a list, which is why custom CLIs are a user-facing feature rather than a release.&lt;/p&gt;

&lt;p&gt;The reason it is worth saying out loud: everything below is about the environment around the process, not about the agent. We do not parse the agent's output, we do not wrap its API, and we do not know what it is doing. It is a program in a terminal.&lt;/p&gt;

&lt;p&gt;The two things that make it more than five terminals are what &lt;code&gt;accountDir&lt;/code&gt; and &lt;code&gt;repoPath&lt;/code&gt; are pointed at.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;accountDir&lt;/code&gt; is an isolated HOME, so two panes can be signed into two different Claude accounts at once, and &lt;code&gt;~/.claude&lt;/code&gt; means something different in each. &lt;code&gt;repoPath&lt;/code&gt; is a git worktree, so four agents editing the same repository do not overwrite each other. I wrote up &lt;a href="https://dev.to/eliseomdq/isolation-file-ownership-and-cleanup-the-boring-half-of-running-coding-agents-in-parallel-on-1n0f"&gt;the Windows specifics of both&lt;/a&gt; separately, because that part is genuinely fiddly and not interesting here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Broadcast is four lines and that is the good part
&lt;/h2&gt;

&lt;p&gt;Sending one prompt to every pane sounds like it needs a protocol. It does not. The terminal emulator already hands you every keystroke:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onData&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;targets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;broadcastMode&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;panes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pane&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pty&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the whole feature. Because it operates on raw input rather than on a message, it works with anything that reads a terminal: agents with their own TUI, a REPL, &lt;code&gt;git rebase -i&lt;/code&gt;. Nothing had to be taught about it.&lt;/p&gt;

&lt;p&gt;The cost of that simplicity is real and worth stating. It broadcasts everything, including &lt;code&gt;Ctrl+C&lt;/code&gt; and arrow keys. It does not check whether a pane is ready, so if one agent is still booting it gets your prompt as input to whatever prompt it happens to be showing. There is no "wait for all panes to be idle". That is a design position, not an oversight: the moment you add readiness detection you are parsing agent output, and then every CLI update can break you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Attribution is the part that is actually hard
&lt;/h2&gt;

&lt;p&gt;Showing CPU, memory and open ports per pane sounds like bookkeeping. It is the hardest thing in the app.&lt;/p&gt;

&lt;p&gt;The problem is that the pane's own process is a shell, and nobody cares about the shell. The agent is its child. The dev server the agent started is a grandchild, or worse, has been reparented and is nobody's child at all. If you measure the PID you spawned, every pane reads about 77 MB and looks idle.&lt;/p&gt;

&lt;p&gt;So you resolve the whole tree per pane, per polling cycle. On Windows that means one &lt;code&gt;Get-CimInstance Win32_Process&lt;/code&gt; snapshot for the entire machine, cached for a second and a half and shared by everything that needs it in that cycle, then walked in memory. (&lt;a href="https://dev.to/eliseomdq/wmic-is-gone-and-your-node-process-tree-is-silently-wrong-on-windows-11-3e8c"&gt;Why not &lt;code&gt;pidtree&lt;/code&gt;&lt;/a&gt; is its own story.)&lt;/p&gt;

&lt;p&gt;And when the parent link is gone, which on Windows it frequently is, the fallback is the filesystem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;rootPath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\/&lt;/span&gt;&lt;span class="sr"&gt;+$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;snap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathByPid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a process's executable path or command line points inside a worktree, that process belongs to that worktree's pane. For processes that do not carry the path either, there is a third pass that reads each process's current directory, which on Windows means going through &lt;code&gt;ntdll&lt;/code&gt; because there is no API for it.&lt;/p&gt;

&lt;p&gt;Be clear about what this is: a heuristic. It attributes correctly for the common cases and it misses elevated processes and processes from other users, because &lt;code&gt;OpenProcess&lt;/code&gt; refuses them. The resource bar is a good signal and it is not accounting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reviewing is a git command and a parser
&lt;/h2&gt;

&lt;p&gt;Four agents produce four diffs, and the first version of this app made you read them with &lt;code&gt;git diff&lt;/code&gt; in a fifth pane. The diff viewer is deliberately thin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;execFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;git&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;-C&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;worktreePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;diff&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--no-color&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;--unified=3&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then parse the unified diff into files and hunks in about a hundred lines. No library, no server, no LSP.&lt;/p&gt;

&lt;p&gt;Two decisions in there that matter more than the parsing. Files over 10,000 lines get marked oversized and are not rendered, because one regenerated lockfile will otherwise freeze the renderer while it builds a hundred thousand DOM rows. And &lt;code&gt;base&lt;/code&gt; defaults to &lt;code&gt;HEAD&lt;/code&gt;, which means what you see is uncommitted work. That is the right default for reviewing an agent that just finished, and it is the wrong one if the agent committed as it went, which several of them now do by default. You can pass another base. Most people do not know that, which is a UI failure rather than an engine one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What survives a restart, and what does not
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;session.json&lt;/code&gt; holds the pane descriptors. Types, account names, colors, repo paths, layout, tab structure. It is written atomically, serialize first, write to a temp sibling, rename over the real file, because a crash mid-write used to leave truncated JSON that failed to parse and took the whole workspace with it.&lt;/p&gt;

&lt;p&gt;What it does not hold is any terminal state. Scrollback lives in memory in the main process and dies with the PTY. Reopening the app respawns the panes and re-runs the CLIs. Your grid comes back, your agents restart, and the conversation on screen is gone.&lt;/p&gt;

&lt;p&gt;In practice that hurts less than it should, because the agents keep their own histories and most of them can resume a session. But it is a real limit and the app does not pretend otherwise: the panes are containers for processes, not a saved document.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would fix first
&lt;/h2&gt;

&lt;p&gt;The thing the app is worst at is the thing this whole design makes hardest. Every pane is an opaque process, which is why adding a new agent is trivial. It is also why there is no unified log: no single timeline with timestamps and exit codes across panes, telling you which agent touched which file and in what order. There is a per-pane transcript you can export and that is all.&lt;/p&gt;

&lt;p&gt;That gap is the price of not parsing agent output, and I have not found a way to close it that does not involve becoming a wrapper around each CLI. Filesystem watching per worktree is the obvious idea and it tells you what changed without telling you who did it. If you have a better one, that is the comment I want.&lt;/p&gt;

&lt;p&gt;NestMux runs on Windows 10 and up, macOS 13 and up, and Linux, same build on all three, local-first with no telemetry. It is at &lt;a href="https://nestmux.com" rel="noopener noreferrer"&gt;nestmux.com&lt;/a&gt;, free during launch.&lt;/p&gt;

</description>
      <category>electron</category>
      <category>ai</category>
      <category>devtools</category>
      <category>architecture</category>
    </item>
    <item>
      <title>wmic is gone and your Node process tree is silently wrong on Windows 11</title>
      <dc:creator>Eliseo Fernandez Suarez</dc:creator>
      <pubDate>Sun, 23 Aug 2026 22:59:00 +0000</pubDate>
      <link>https://dev.to/eliseomdq/wmic-is-gone-and-your-node-process-tree-is-silently-wrong-on-windows-11-3e8c</link>
      <guid>https://dev.to/eliseomdq/wmic-is-gone-and-your-node-process-tree-is-silently-wrong-on-windows-11-3e8c</guid>
      <description>&lt;p&gt;The last post I wrote here ended on a line I had to go and check: killing a shell does not kill what the shell started. This is what I found underneath it, and one of the things is a dependency a lot of people have without knowing.&lt;/p&gt;

&lt;p&gt;Numbers below come from Windows 11 Home, build 26200, in August 2026. Run the commands yourself, they take a minute. I work on NestMux, which is where I hit this, but nothing here is specific to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The symptom
&lt;/h2&gt;

&lt;p&gt;A terminal pane running a coding agent reported 77 MB of memory. That is roughly the PowerShell host and nothing else. The agent inside it, and the dev server the agent had started, were not being counted.&lt;/p&gt;

&lt;p&gt;So the process tree was empty. Every pane looked like an idle shell.&lt;/p&gt;

&lt;h2&gt;
  
  
  wmic does not exist any more
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wmic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Get-Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;term&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'wmic'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recognized&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmdlet...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Microsoft deprecated WMIC in Windows 10 21H1 and started removing it from the image in Windows 11 22H2. On a current install it is simply not there. This is documented and it was announced, and it is still going to break things for years, because &lt;code&gt;wmic&lt;/code&gt; is buried inside libraries rather than in the code people wrote.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which brings us to pidtree
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pidtree&lt;/code&gt; is the standard way to get a process tree from Node. It backs &lt;code&gt;pkill&lt;/code&gt;-style behavior in a lot of tooling and it has millions of downloads a week. On Windows, version 0.6.0 shells out to &lt;code&gt;wmic&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i pidtree@0.6.0
node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"require('pidtree')(process.pid,{root:true}).then(console.log).catch(e=&amp;gt;console.log('FAIL:',e.message))"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAIL: spawn wmic ENOENT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Version 1.0.0 fixed it. It still tries &lt;code&gt;wmic&lt;/code&gt; first, and falls back to PowerShell &lt;code&gt;Get-CimInstance&lt;/code&gt; when that fails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i pidtree@1.0.0
node &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;"const m=require('pidtree');(m.default||m)(process.pid,{root:true}).then(console.log)"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OK [ 30520, 36000, 29244 ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So check what you actually resolve, not what your &lt;code&gt;package.json&lt;/code&gt; says. A caret range of &lt;code&gt;^0.6.0&lt;/code&gt; will never reach 1.0.0. That was our case: the range said &lt;code&gt;^0.6.0&lt;/code&gt;, the lockfile said &lt;code&gt;0.6.0&lt;/code&gt;, and every call on Windows was throwing &lt;code&gt;ENOENT&lt;/code&gt; into a &lt;code&gt;Promise.allSettled&lt;/code&gt; that treated the rejection as "no children" and moved on. It was not an error anyone saw. It was a tree of size one.&lt;/p&gt;

&lt;p&gt;That is the part worth repeating. This failure does not look like a failure. It looks like a process with no children.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rolling your own: take one snapshot, not N queries
&lt;/h2&gt;

&lt;p&gt;If you cannot upgrade, the replacement is &lt;code&gt;Get-CimInstance Win32_Process&lt;/code&gt;. The instinct is to query per PID with a filter. Do not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# one snapshot of everything&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Get-CimInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Win32_Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ProcessId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;ParentProcessId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# 396 processes in 200 ms&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# five filtered queries&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-CimInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Win32_Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ParentProcessId=&lt;/span&gt;&lt;span class="bp"&gt;$PID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# 517 ms&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One full snapshot of 396 processes costs less than five filtered queries. The cost is in starting the CIM call, not in the rows. Take one snapshot per polling cycle, build a &lt;code&gt;parent -&amp;gt; children[]&lt;/code&gt; map from it, and walk the map in memory for every PID you care about. If you are polling several panes every few seconds, this is the difference between a background cost you can ignore and one you cannot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Killing the tree
&lt;/h2&gt;

&lt;p&gt;Now the part from the previous post, with the actual output.&lt;/p&gt;

&lt;p&gt;Start a grandchild. Root &lt;code&gt;cmd&lt;/code&gt; spawns a &lt;code&gt;cmd&lt;/code&gt;, which spawns &lt;code&gt;timeout&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$g&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Start-Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-PassThru&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WindowStyle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Hidden&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ArgumentList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'cmd /c timeout /t 90 /nobreak'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-CimInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Win&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="err"&gt;_Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Name='timeout.exe'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kill the root and only the root:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Stop-Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="err"&gt;Get-CimInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Win&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="err"&gt;_Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Filter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Name='timeout.exe'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# 1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Still alive. Windows has no process groups in the POSIX sense and no automatic reparenting cleanup. &lt;code&gt;Stop-Process&lt;/code&gt;, and &lt;code&gt;process.kill(pid)&lt;/code&gt; from Node, both terminate exactly one process. The descendants keep running, holding their file handles and their ports, with a parent PID that now points at a dead process.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;taskkill /F /T&lt;/code&gt; walks it properly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;taskkill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/F&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/T&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;/PID&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$g&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SUCCESS: the process with PID 29828 (child process of PID 34112) has been terminated.
SUCCESS: the process with PID 39928 (child process of PID 37072) has been terminated.
SUCCESS: the process with PID 34112 (child process of PID 37072) has been terminated.
SUCCESS: the process with PID 37072 (child process of PID 39620) has been terminated.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deepest first, then up. That ordering is the whole point: it resolves the tree while the links are still intact.&lt;/p&gt;

&lt;p&gt;Which gives you the trap. If you kill the root first and then go looking for the descendants, the links you needed are gone. The orphans are still there, they are still holding whatever they were holding, and nothing connects them to the thing you were trying to clean up. Resolve the tree first, or hand the root to &lt;code&gt;taskkill /F /T&lt;/code&gt; and let it do both.&lt;/p&gt;

&lt;p&gt;One hazard while you are in here: Windows reuses PIDs, and aggressively. A PID you cached thirty seconds ago and are about to force-kill may now be something else. If you keep a PID map across polling cycles, validate it against a fresh snapshot before you kill anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  When there is no parent link at all
&lt;/h2&gt;

&lt;p&gt;The uncomfortable case. A coding agent starts a dev server in the background. On Windows that server frequently ends up with no usable ancestry back to the pane: the intermediate process has exited, the parent PID is stale or recycled, and the binary path is just &lt;code&gt;node.exe&lt;/code&gt;. The command line does not carry the worktree it belongs to either.&lt;/p&gt;

&lt;p&gt;What is left is the process's current directory. If the server's cwd is inside a given worktree, it belongs to that worktree.&lt;/p&gt;

&lt;p&gt;There is no Node API for reading another process's cwd on Windows. On Linux it is &lt;code&gt;/proc/&amp;lt;pid&amp;gt;/cwd&lt;/code&gt;, on macOS it is &lt;code&gt;lsof&lt;/code&gt;, and on Windows you go through the kernel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;koffi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;koffi&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ntdll&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;koffi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ntdll.dll&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;NtQueryInformationProcess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ntdll&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;long __stdcall NtQueryInformationProcess(void*, int, _Out_ uint8_t*, uint32_t, _Out_ uint32_t*)&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// OpenProcess with PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,&lt;/span&gt;
&lt;span class="c1"&gt;// then NtQueryInformationProcess to get PROCESS_BASIC_INFORMATION,&lt;/span&gt;
&lt;span class="c1"&gt;// then ReadProcessMemory to walk:&lt;/span&gt;
&lt;span class="c1"&gt;//   PEB + 0x20 -&amp;gt; ProcessParameters -&amp;gt; CurrentDirectory (UNICODE_STRING)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The offsets are x64 Windows 10 and 11. They are not a public contract, which is the honest caveat here: this is a documented-enough structure that tooling has relied on it for twenty years, and it is still something Microsoft can move.&lt;/p&gt;

&lt;p&gt;Two things you will hit immediately. Elevated processes and processes from another user reject &lt;code&gt;OpenProcess&lt;/code&gt; with access denied, and you get nothing back for them, so whatever feature you built on this silently does not apply to Docker Desktop and to services. Log those once per PID rather than per poll, otherwise a five second polling loop buries the console.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Check whether you resolve &lt;code&gt;pidtree&lt;/code&gt; below 1.0.0. If you do, your Windows process trees are size one and nothing told you.&lt;/li&gt;
&lt;li&gt;One CIM snapshot per cycle beats per-PID queries, by a lot.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;process.kill&lt;/code&gt; and &lt;code&gt;Stop-Process&lt;/code&gt; are not tree kills. &lt;code&gt;taskkill /F /T&lt;/code&gt; is.&lt;/li&gt;
&lt;li&gt;Resolve the tree before you kill the root, not after.&lt;/li&gt;
&lt;li&gt;If you need attribution when the parent link is gone, cwd is the last reliable signal, and getting it on Windows means going through ntdll.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you maintain something that manages child processes on Windows and you have a cleaner answer than the PEB walk, I would like to hear it. That is the part of this I am least happy with.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>node</category>
      <category>javascript</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Isolation, file ownership and cleanup: the boring half of running coding agents in parallel on Windows</title>
      <dc:creator>Eliseo Fernandez Suarez</dc:creator>
      <pubDate>Sun, 23 Aug 2026 22:50:34 +0000</pubDate>
      <link>https://dev.to/eliseomdq/isolation-file-ownership-and-cleanup-the-boring-half-of-running-coding-agents-in-parallel-on-1n0f</link>
      <guid>https://dev.to/eliseomdq/isolation-file-ownership-and-cleanup-the-boring-half-of-running-coding-agents-in-parallel-on-1n0f</guid>
      <description>&lt;p&gt;Someone left a comment on my last post that was a better outline than the post was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Parallel agents are only practical when the workspace boundaries are boring and explicit. On Windows especially, I would care less about the launch trick and more about isolation, logs, file ownership, and cleanup after failed runs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is Alex Shev, and he is right. Four agents in a pane grid is the screenshot. The four things he listed are what decide whether you are still using the setup in a month.&lt;/p&gt;

&lt;p&gt;I work on NestMux, so I have a stake in this. Most of what is below is plain Windows and plain git, and the commands run the same whether or not you use it. Where I describe a decision we made, I say so, and I say where it still falls short.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isolation is not one HOME variable
&lt;/h2&gt;

&lt;p&gt;Two agents running under the same Windows account share &lt;code&gt;~/.claude&lt;/code&gt;, &lt;code&gt;~/.codex&lt;/code&gt;, &lt;code&gt;~/.gemini&lt;/code&gt;. Same config, and more importantly the same authenticated session. If you want two Claude accounts side by side, they need separate home directories.&lt;/p&gt;

&lt;p&gt;On Windows, &lt;code&gt;HOME&lt;/code&gt; is not the variable that gets you there. It is a POSIX convention that some tools honor and Windows itself does not. Set only &lt;code&gt;HOME&lt;/code&gt; and you get a half-redirected agent: the CLI writes its config to the new location while git writes &lt;code&gt;.gitconfig&lt;/code&gt; to the old one, and you will not notice until two panes start sharing a git identity. What you actually need per process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;HOME&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;accountDir&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;USERPROFILE&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;accountDir&amp;gt;&lt;/span&gt;
&lt;span class="py"&gt;HOMEDRIVE&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;C:&lt;/span&gt;
&lt;span class="py"&gt;HOMEPATH&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="se"&gt;\p&lt;/span&gt;&lt;span class="s"&gt;ath&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="s"&gt;o&lt;/span&gt;&lt;span class="se"&gt;\a&lt;/span&gt;&lt;span class="s"&gt;ccountDir&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Gemini CLI wants &lt;code&gt;GEMINI_CLI_HOME&lt;/code&gt; pointing at its own subdirectory on top of that.&lt;/p&gt;

&lt;p&gt;One trap if you are building the launcher rather than using one: Node's &lt;code&gt;os.homedir()&lt;/code&gt; does not reliably reflect a &lt;code&gt;USERPROFILE&lt;/code&gt; you injected at spawn time on Windows. If your app reads &lt;code&gt;homedir()&lt;/code&gt; to decide where its own storage lives, and it also redirects &lt;code&gt;USERPROFILE&lt;/code&gt; for child processes, those two will disagree. We ended up with a dedicated env var for storage and a separate function for "where a new shell should start", because collapsing them meant every new terminal opened inside the agent's config directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  The config you do want shared
&lt;/h3&gt;

&lt;p&gt;Full isolation is not what anyone wants. Your &lt;code&gt;CLAUDE.md&lt;/code&gt;, your &lt;code&gt;settings.json&lt;/code&gt; and your skills should be the same in every pane. So they get linked back to the global copy instead of duplicated.&lt;/p&gt;

&lt;p&gt;On Windows that link is more annoying than it sounds. &lt;code&gt;mklink&lt;/code&gt; for a file symlink needs elevation or Developer Mode. Junctions do not, so directories are fine. For files the fallback is a hardlink.&lt;/p&gt;

&lt;p&gt;That fallback has a sting. A hardlink is not a symlink, so &lt;code&gt;lstat().isSymbolicLink()&lt;/code&gt; returns false for it. If you later offer a "detach this account from the shared config" button and implement it as "replace symlinks with real copies", the hardlinked files are skipped, and the account goes on silently editing your global config while the UI says it is detached. Detecting them means comparing the file id:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lstatSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;lstatSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sameFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ino&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ino&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ino&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;bigint: true&lt;/code&gt; matters. The regular &lt;code&gt;ino&lt;/code&gt; comes back as &lt;code&gt;0&lt;/code&gt; on some Windows configurations, which makes every pair of files look identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  File ownership on Windows means open handles
&lt;/h2&gt;

&lt;p&gt;This is the one that generates support questions, and the error message actively points the wrong way.&lt;/p&gt;

&lt;p&gt;Set up a repo with a worktree, then put a process inside it with a real working directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\dev\repo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;worktree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\dev\feat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;feat&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Start-Process&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WorkingDirectory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\dev\feat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ArgumentList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'/c'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'timeout /t 60'&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;git&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\dev\repo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;worktree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;remove&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C:\dev\feat&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;--force&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;error: failed to delete 'C:/dev/feat': Permission denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Permissions have nothing to do with it. Windows will not delete a directory that is some process's current directory, and git reports the refusal as a permissions error. On Linux the same removal succeeds, which is why this tends to reach Windows users first as a bug report nobody can reproduce.&lt;/p&gt;

&lt;p&gt;Two things I only found by trying to write a reliable teardown:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PowerShell's &lt;code&gt;Set-Location&lt;/code&gt; does not reproduce it.&lt;/strong&gt; The PowerShell location is a provider concept layered on top of the process. The underlying working directory stays where the process started. So a &lt;code&gt;Start-Process powershell -Command "Set-Location C:\dev\feat; ..."&lt;/code&gt; will let the delete go through, and you will conclude the problem is not real. Use &lt;code&gt;-WorkingDirectory&lt;/code&gt;, or &lt;code&gt;cmd&lt;/code&gt;, or anything that sets the actual cwd.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Killing the shell is not enough.&lt;/strong&gt; In the run above I killed the &lt;code&gt;cmd.exe&lt;/code&gt; and the delete still failed. The holder was &lt;code&gt;timeout.exe&lt;/code&gt;, a child that inherited the working directory and outlived its parent. You need the process tree, not the process.&lt;/p&gt;

&lt;p&gt;The practical consequence for anything that manages worktrees: before you touch the filesystem, you have to know which panes are sitting inside the directory. That means recording each pane's cwd at spawn time and killing by path prefix, normalized, because on Windows the same worktree shows up as both &lt;code&gt;C:\dev\feat&lt;/code&gt; and &lt;code&gt;C:/dev/feat&lt;/code&gt; depending on who wrote the path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup after a failed run is where the state gets weird
&lt;/h2&gt;

&lt;p&gt;Here is the part I did not expect. A failed &lt;code&gt;git worktree remove&lt;/code&gt; is not a no-op. Continuing from the failure above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git &lt;span class="nt"&gt;-C&lt;/span&gt; C:&lt;span class="se"&gt;\d&lt;/span&gt;ev&lt;span class="se"&gt;\r&lt;/span&gt;epo worktree list
&lt;span class="go"&gt;C:/dev/repo  39ca293 [master]

&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;dir &lt;/span&gt;C:&lt;span class="se"&gt;\d&lt;/span&gt;ev&lt;span class="se"&gt;\f&lt;/span&gt;eat
&lt;span class="go"&gt;(empty)

&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git &lt;span class="nt"&gt;-C&lt;/span&gt; C:&lt;span class="se"&gt;\d&lt;/span&gt;ev&lt;span class="se"&gt;\r&lt;/span&gt;epo worktree remove C:&lt;span class="se"&gt;\d&lt;/span&gt;ev&lt;span class="se"&gt;\f&lt;/span&gt;eat &lt;span class="nt"&gt;--force&lt;/span&gt;
&lt;span class="go"&gt;fatal: 'C:\dev\feat' is not a working tree

&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;git &lt;span class="nt"&gt;-C&lt;/span&gt; C:&lt;span class="se"&gt;\d&lt;/span&gt;ev&lt;span class="se"&gt;\r&lt;/span&gt;epo worktree prune &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;span class="go"&gt;(nothing)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Git deleted the worktree contents, deleted the administrative directory under &lt;code&gt;.git/worktrees&lt;/code&gt;, and dropped the entry from &lt;code&gt;git worktree list&lt;/code&gt;. Then it hit the locked top-level directory and stopped. What survives is an empty folder that git no longer recognizes, will not remove, and does not consider dangling. The branch is still there. &lt;code&gt;prune&lt;/code&gt; has nothing to prune because the metadata is already gone.&lt;/p&gt;

&lt;p&gt;So the recovery is manual and the order matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Kill whatever holds the handle, process tree included.&lt;/li&gt;
&lt;li&gt;Delete the directory yourself.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git worktree prune&lt;/code&gt; afterwards, for the case where git did not get as far as clearing its own metadata.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Prune first and you can be pruning metadata that still references the directory you are about to delete.&lt;/p&gt;

&lt;p&gt;The rule we ended up with in the app, which cost us a bug to learn: if either of those steps fails, keep the entry and report the failure. The tempting version is to drop your own record and call it removed, since the user asked for it to be gone. Then the next refresh reads &lt;code&gt;git worktree list&lt;/code&gt;, or the leftover directory, and the worktree reappears looking healthy. Now it cannot be removed through the UI at all, because the code path for removing it assumes the state it just lost. A partial delete reported as success is worse than an error message.&lt;/p&gt;

&lt;p&gt;Two smaller things in the same area:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reconcile on read.&lt;/strong&gt; People delete worktrees outside your app, with &lt;code&gt;rm -rf&lt;/code&gt; or plain git. If your list comes from your own store, entries go stale. Compare against &lt;code&gt;git worktree list&lt;/code&gt; on every listing, mark what git no longer knows about, and drop entries whose directory is gone from disk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Never create a worktree inside &lt;code&gt;.git&lt;/code&gt;.&lt;/strong&gt; An early version of ours put some under &lt;code&gt;.git/worktrees&lt;/code&gt;, which is git's own metadata folder. Git will create it, then refuse to treat it as a working tree, so &lt;code&gt;git worktree remove&lt;/code&gt; fails permanently and the only exit is manual deletion. If you build paths from a repo path plus a branch name, check where you are about to land.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup commands that run before the agent does
&lt;/h2&gt;

&lt;p&gt;Most parallel-agent setups run something after creating a worktree: &lt;code&gt;npm install&lt;/code&gt;, a &lt;code&gt;.env&lt;/code&gt; copy, a build. That is a place where failures get swallowed.&lt;/p&gt;

&lt;p&gt;Three things worth having, none of them clever:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A per-command timeout.&lt;/strong&gt; The command that errors is fine, you see it. The one that costs you is the command that prints nothing and never exits. Waiting for a prompt on stdin will do it. Ten minutes per command and then kill it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cancel on teardown.&lt;/strong&gt; If the setup is still running when someone removes the worktree, cancel it first. Otherwise you are deleting a directory that a live &lt;code&gt;npm install&lt;/code&gt; is writing into, which puts you right back in the previous section.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redaction.&lt;/strong&gt; Setup logs catch &lt;code&gt;TOKEN=...&lt;/code&gt; lines from an env dump or a verbose install. If you persist those logs, strip them on the way in, not on the way out.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Logs: the part I do not have an answer for
&lt;/h2&gt;

&lt;p&gt;This is where the comment landed hardest and where I have the least to show.&lt;/p&gt;

&lt;p&gt;What exists in NestMux today is a per-pane transcript you can save and export as markdown, and a setup log per worktree capped at 200 lines with secrets redacted. What does not exist is anything unified: one timeline across panes, with timestamps, exit codes, and which worktree each line belonged to.&lt;/p&gt;

&lt;p&gt;That is exactly the artifact you need in the case the comment described. A run failed overnight, four agents were working, and the question is which one touched what, and in what order. A transcript per pane makes you reconstruct that by hand from four scrollbacks.&lt;/p&gt;

&lt;p&gt;I do not have a shipping date for it. I am writing it down as a gap rather than a plan, because the honest state is that pane-level transcripts were easy and a cross-pane log with correct attribution is not, particularly when panes come and go.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this adds up to
&lt;/h2&gt;

&lt;p&gt;The launch trick genuinely is the easy part. Everything expensive is in teardown: who holds the handle, what state a failed delete leaves behind, and whether your own record of the world still matches the disk afterward. On Windows that is a different set of failures than on Linux, and the error messages are worse.&lt;/p&gt;

&lt;p&gt;If you run agents in parallel and you have a teardown that survives a failed run, or a logging setup that actually answers "which agent did this", I would like to see it. Especially if it argues that the whole thing should be a script rather than an application.&lt;/p&gt;

</description>
      <category>windows</category>
      <category>git</category>
      <category>ai</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Running several coding agents in parallel on Windows, without WSL or tmux</title>
      <dc:creator>Eliseo Fernandez Suarez</dc:creator>
      <pubDate>Fri, 21 Aug 2026 01:50:06 +0000</pubDate>
      <link>https://dev.to/eliseomdq/running-several-coding-agents-in-parallel-on-windows-without-wsl-or-tmux-1onh</link>
      <guid>https://dev.to/eliseomdq/running-several-coding-agents-in-parallel-on-windows-without-wsl-or-tmux-1onh</guid>
      <description>&lt;p&gt;I work on NestMux, so read this the way you would read anything written by someone with a stake in it. Everything I claim about other tools comes from the documentation each vendor publishes, and I link it so you can check instead of trusting me.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;Running one coding agent is easy. Running four is where it gets interesting, and where it breaks.&lt;/p&gt;

&lt;p&gt;Four agents on the same repository will overwrite each other's files. They will fight over the same working tree, the same branch, and, if you use more than one provider, the same credentials directory. The usual answer is one git worktree per agent, one terminal pane per worktree, and some way to see all of it at once.&lt;/p&gt;

&lt;p&gt;That last part is where Windows falls out.&lt;/p&gt;

&lt;h2&gt;
  
  
  tmux is the quiet dependency
&lt;/h2&gt;

&lt;p&gt;Claude Code has an agent teams feature that puts each agent in its own split pane. The backend for those splits is tmux, with iTerm2 as the alternative. Anthropic's own documentation says split-pane mode is not supported in Windows Terminal.&lt;/p&gt;

&lt;p&gt;tmux does not run natively on Windows. It runs under WSL, which means your agents run in a Linux filesystem, talking to a Windows checkout across a boundary that is slow for file watching and awkward for anything with a native toolchain. If you build with MSBuild, Unity, or Unreal, that boundary is not a detail you can ignore.&lt;/p&gt;

&lt;p&gt;So the feature exists. It just does not exist for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tools built to solve this are Mac-first
&lt;/h2&gt;

&lt;p&gt;This is the part I expected to be wrong when I started checking, and it was not. As of August 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conductor&lt;/strong&gt;: macOS only, Apple Silicon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cmux&lt;/strong&gt;: macOS, Linux in beta, Windows on a waitlist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Superset&lt;/strong&gt;: macOS, with Windows and Linux listed as coming soon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sculptor&lt;/strong&gt;: macOS and Linux. Windows through WSL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these shipped in the last year, and every one of them treats Windows as a later problem. I do not think this is contempt. Mac is what the people building developer tools use, and you build for the machine on your desk.&lt;/p&gt;

&lt;p&gt;But the people who need this are not all on that machine. Game studios run Unity and Unreal on Windows. .NET shops run Windows. Regulated fintech and hospital IT run Windows because that is what their compliance posture was built around. Those are large teams with real budgets, and right now the answer they get is a waitlist.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually takes
&lt;/h2&gt;

&lt;p&gt;Nothing here requires a terminal multiplexer. It requires four things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One worktree per agent.&lt;/strong&gt; &lt;code&gt;git worktree add ../feature-x feature-x&lt;/code&gt; gives each agent its own checkout of the same repository sharing one object store. They can edit at the same time without touching each other's files, and you merge normally afterward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An isolated HOME per agent.&lt;/strong&gt; This is the one people miss. Claude Code reads &lt;code&gt;~/.claude&lt;/code&gt;, Codex reads &lt;code&gt;~/.codex&lt;/code&gt;, Gemini CLI reads &lt;code&gt;~/.gemini&lt;/code&gt;. Two agents under the same user account share the same config and, more to the point, the same authenticated session. If you want two Claude accounts running side by side, they need separate home directories. On Windows that means pointing &lt;code&gt;USERPROFILE&lt;/code&gt; and &lt;code&gt;HOME&lt;/code&gt; somewhere different per process, not just setting an env var and hoping.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A pane grid that is not a multiplexer.&lt;/strong&gt; tmux exists because terminals could not split themselves in 1980. A desktop application can just draw four terminals. Once you stop treating the multiplexer as a requirement, the Windows problem stops being a problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A way to review the output.&lt;/strong&gt; Four agents produce four diffs. Reading them one &lt;code&gt;git diff&lt;/code&gt; at a time is where the time savings go to die.&lt;/p&gt;

&lt;h2&gt;
  
  
  How NestMux does it
&lt;/h2&gt;

&lt;p&gt;Here is the part where I am selling something, clearly marked.&lt;/p&gt;

&lt;p&gt;NestMux is a desktop application that runs Claude Code, Codex, Gemini CLI, Copilot and OpenCode side by side in a resizable pane grid. Each pane gets its own account, its own isolated HOME, and its own git worktree. There is a diff viewer for reviewing what the agents did, MCP server management, a broadcast mode to send one prompt to every pane, and voice input through a local Whisper model.&lt;/p&gt;

&lt;p&gt;It runs on Windows 10 and up, macOS 13 and up, and Linux. Same build, same features on all three. It is local-first with no telemetry: the code and the keys stay on the machine.&lt;/p&gt;

&lt;p&gt;Thirty-five seconds of it running:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/p11NPjZUQi4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  If you do not want to use it
&lt;/h2&gt;

&lt;p&gt;You should not need a product for this, and depending on your setup you might not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do it by hand.&lt;/strong&gt; Windows Terminal has panes. &lt;code&gt;wt.exe split-pane&lt;/code&gt; will give you a grid, and you can drive each pane into a different worktree with a different &lt;code&gt;HOME&lt;/code&gt;. It is fiddly to set up and you rebuild it every session, but it is free and it works today.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use WSL and accept the boundary.&lt;/strong&gt; If your project is Node or Python and does not care where the filesystem lives, WSL plus tmux is a completely reasonable answer, and every Mac-first tool above becomes available to you through Sculptor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wait.&lt;/strong&gt; cmux has a Windows waitlist and Superset says it is coming. If you are not in a hurry, the market will probably solve this in a year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use one agent well.&lt;/strong&gt; A lot of the parallel-agents enthusiasm is people discovering that four mediocre agents produce four mediocre pull requests. One agent with good context often beats four without it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest limitation
&lt;/h2&gt;

&lt;p&gt;Parallel agents multiply output, and output is not the bottleneck for most teams. Review is. If you are already behind on reviewing what one agent writes, four will make that worse, not better. The setup pays off when the work is genuinely parallel: separate features, separate bugs, a migration split across independent files. It does not pay off when four agents are all guessing at the same underspecified ticket.&lt;/p&gt;

&lt;p&gt;If you run agents in parallel and you have found a different way through the Windows problem, I want to hear it, especially if it makes the case that this does not need a product at all.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>windows</category>
      <category>devtools</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
