Skip to content

Hide the console window of zmx children spawned by the Windows shell - #640

Merged
coneilen merged 1 commit into
mainfrom
coneilen-hide-zmx-attach-console
Oct 6, 2026
Merged

coneilen merged 1 commit into
mainfrom
coneilen-hide-zmx-attach-console

Conversation

@coneilen

@coneilen coneilen commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

The Windows shell spawned zmx.exe attach (a console-subsystem program) from the GUI-subsystem graphcode-windows.exe without CREATE_NO_WINDOW, so Windows gave it a new console. On the beta10 Dev Box qualification (Windows 11, Windows Terminal as default console host) this opened a visible, empty window titled .\zmx.exe that stole foreground every time a loop's terminal attached. This PR makes every zmx child the shell spawns run without a console window.

Changes

  • Add ZmxSession.child, which builds the std.process.Child for a zmx command, keeps each caller's existing stdio contract (attach: stdin/stdout piped, stderr ignored; control: all ignored), and always sets create_no_window = true.
  • TerminalSurface.Workspace.startSession (attach, the reported window) and pollResizeControl (resize, which already set the flag) now spawn through it. The stdin PIPE_NOWAIT setup and stdout reading are unchanged.
  • WorkspaceTeardown.Live.killSessions (zmx kill, which could also pop a console during workspace deletion) now spawns through it.
  • New Zig test in src\ZmxSession.zig (an already-registered root) and a static source contract in Tools\windows\Tests\WindowsShell.Tests.ps1 requiring the three zmx spawn sites to go through ZmxSession.child.

Audit of the other child spawns in graphcode-windows\src: DaemonSupervisor already uses CREATE_NO_WINDOW; WorktreeStatus git children already set create_no_window; App.zig CreateProcessW calls relaunch the GUI shell itself; App.zig Child.run calls are test fixtures. Left out of scope because they are not zmx/terminal work: Codespaces.capture (gh), WindowsRepositoryDialogs clone (git) and SSH validation (ssh), and WindowsUpdateInstall.runPowerShell (powershell.exe). They are console programs started without create_no_window and may show a console window the same way; that should be a separate change.

Test plan

RED: zig 0.15.2 test src\ZmxSession.zig (helper routed through, flag not yet set) -> 1 passed; 0 skipped; 1 failed. "zmx children keep their stdio contract and never open a console window...expected true, found false" at ZmxSession.zig:47 (attach.create_no_window)
GREEN: zig 0.15.2 test src\ZmxSession.zig -> All 2 tests passed.
REGRESSION: zig 0.15.2 test src\TerminalSurface.zig with the WindowsShell.Tests.ps1 target, link and include flags -> All 141 tests passed; zig 0.15.2 test src\WorkspaceTeardown.zig with its WindowsShell.Tests.ps1 flags -> All 229 tests passed

Additional evidence, exactly as run:

  • Static contract RED, before any source change: the WindowsShell.Tests.ps1 static contracts only (Zig removed from PATH, so the script stops at Resolve-TestZig, which follows every static contract) failed with Windows shell contract: zmx children must be created without a console window. After the change the same run passes every static contract and stops at A working Zig executable is required for executable Windows shell tests.
  • Neither test root references startSession, pollResizeControl, or killSessions, so Zig's lazy analysis does not compile them in the runs above. I verified this with a deliberate invalid enum tag at the TerminalSurface.zig call sites, which the normal TerminalSurface.zig runs (with and without -fno-emit-bin) accepted. With a temporary, uncommitted test block that references those functions, the invalid tags were rejected at TerminalSurface.zig:1567, :1703 and WorkspaceTeardown.zig:345. With the real tags, WorkspaceTeardown.zig passed and TerminalSurface.zig compiled and failed only at link (lld-link: undefined symbol: winghostty_..., because the scratch reference pulls in Winghostty host symbols the section does not link). That is compile-level evidence only.
  • The TerminalSurface.zig run linked ghostty-vt-static.lib from another local worktree built at the same Winghostty pin (6286560d), copied read-only into a scratch directory. I did not run zig build prepare-terminal-vt.

Limits: no live window observation was made, and GraphCode is not installed on this machine. This PR does not show that the .\zmx.exe window no longer appears on a Dev Box, or that zmx attach --size behaves the same at runtime without a console. That needs a re-run of the Dev Box qualification. The full shell build (zig build), validate.ps1 -Task windows-shell, and the other WindowsShell.Tests.ps1 sections were not run locally and are left to CI.

Checklist

  • I have read the Contributing Guidelines
  • I have signed off my commits (git commit -s) per the DCO
  • Tests pass locally (make test): not run. This is a Windows-only change and make test is the macOS Xcode suite. The focused Zig runs above are the local evidence.
  • Code follows the existing style (make check): not run. make check lints Swift only, and this PR changes no Swift.
  • I added the test/contract before the implementation and observed the intended RED failure

graphcode-windows.exe is a GUI-subsystem process, so every console-subsystem
zmx.exe child it spawns without CREATE_NO_WINDOW gets a brand-new console. On
hosts where Windows Terminal is the default console host, the zmx attach child
opened a visible, empty window titled .\zmx.exe that stole foreground.

Route the attach, resize, and kill children through ZmxSession.child, which
keeps each child's existing stdio contract (attach pipes stdin and stdout) and
always sets create_no_window. The resize child already set the flag; attach
and kill did not.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Colin Neilens <coneilen@microsoft.com>
@coneilen
coneilen merged commit 9fd95ee into main Oct 6, 2026
24 checks passed
coneilen added a commit that referenced this pull request Oct 7, 2026
Record the immutable, unpublished 0.1.78-windows.beta12 candidate built from
1199de5 with the beta10 Dev Box fixes (#640-#643). Beta10 is failed/superseded
and beta11 is unbuilt/superseded; both are kept unchanged. Exact artifact stays
complete and the other six gates stay open. The parity count is updated to the
ledger's current 98 surfaces: 60 Validated / 38 Partial.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Colin Neilens <coneilen@microsoft.com>
@coneilen coneilen mentioned this pull request Oct 7, 2026
2 of 5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant