Skip to content

Default opencode (server+TUI) corrupts ConPTY-hosted parent shell on exit; shell crashes with NT_STATUS/CLR error code #26480

@Zyl0812

Description

@Zyl0812

Description

Summary

On Windows, exiting opencode in its default server+TUI mode (via /exit or Ctrl+C×2) kills the parent shell when that shell is hosted in a ConPTY (the standard Win32 pseudoconsole used by every modern Windows terminal: Warp, Windows Terminal, VS Code, etc.). The same opencode binary, run from a native conhost.exe window, leaves the parent shell alive. This points at console-handle / FreeConsole cleanup in opencode (or its bun runtime) that is benign against full conhost but corrupts ConPTY's internal state.

The user-visible symptom is that the host terminal's pane closes immediately, because the host terminal correctly reacts to its child shell dying.

This is a more focused diagnosis of the same issue partially addressed in #6189 (closed). #6189 only covered the Ctrl+C key-handler exit path; the bug here reproduces equally on /exit and is not about input handling.

Differential reproduction matrix

Console host opencode mode Parent shell dies?
conhost.exe (classic Windows console window) default (server+TUI) No
ConPTY (Warp / Windows Terminal / VS Code) default
(server+TUI) **Y
ConPTY opencode serve … + opencode attach … No
ConPTY, with `cmYes**
ConPTY, with powershell -NoExit -Command "opencode" default Yes

Three things narrow the cause:

  1. **Console-host ted shells die.Native conhost is fine.
  2. Server-child dependentopencode attach (no spawned server child) is fine. The trigger is in the cleanup path of the server child opencode forks in default mode.
  3. Shell agnostic — both PowerShell and cmd die. So it isn't PowerShell's .NET Console doing something special; it's something happening to the shared ConPTY console state that any shell will then trip over.

Captured shell exit code (PowerShell case)

I instrumented Warp's Windows PTY callback to call GetExitCodeProcess on the shell handle the moment ChildExitWatcher fires:

ChildExitWatcher: shell process handle signaled, exit_code=0x80131623 (2148734499), sending Message::ChildExited to
event loop

Decoding 0x80131623:

  • bit 31 (severity) = 1 (error)
  • facility = 0x013 = FACILITY_URT (.NET CLR)
  • code = 0x1623, range = .NETSystem.IO / ObjectDisposedException family.

I.e., PowerShell truntime threw anunhandled exception when next accessing the Console** (e.g., Console.In/Console.Out reading from a stream backed by a now-disposed/closed handle). With cmd as the host shell the exact code is presumably different, but the precondition is the same: ConPTY console state has been left in a state that the next console read fails.
This pattern is co

  • The server child or its child processes calling FreeConsole (and/or AttachConsole) on shutdown. Microsoft's own ConPTY documentation explicitly notes that FreeConsole/AttachConsole are not fully suppave thepseudoconsole in an inconsistent state (which is invisible under classic conhost because conhost manages console attachment via kernel objects rather than pipe state).
  • Closing inheritendles in a waythat, in pipe-backed ConPTY, causes the underlying pipe to advance to EOF for everyone — even though the same close is a no-op for other consumers under conhost's reference-counted console-object model.
  • bun runtime fd cleanup ([bun-issues that touch console handles in the past] FYI for cross-reference) treating console handles like ordinary fds and close()-ing them.

What I'd like to learn from the opencode side

Verifying which ither fix itupstream or file a precise bug at bun/Go runtime. Concrete asks:

  1. On Windows, doecode serveprocess when default mode forks it) callFreeConsole, AttachConsole, SetStdHandle, or CloseHandleon any ofSTD_INPUT_HANDLE/STD_OUTPUT_HANDLE/STD_ERROR_HANDLE` during shutdown?
  2. Is the server child created with bInheritHandles = TRUE and the parent's std handles inherited as-is? If so, does the server child later close those handles, even via the runtime's automatic
    fd-on-exit cleanup
  3. Does any teardoGenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) or (CTRL_BREAK_EVENT, 0) against the shared console? (Already partly addressed by In the Windows system, pressing Ctrl+C will automatically exit the terminal #6189, but worth confirming for the /exit path too.)

A minimal mitigation that has worked for similar issues in other projects:

  • Spawn the server child detached, in its own console
    (`CREATE_NEW_CONSOo it can nevershare console handles with the user's interactive shell.
  • Or, before forking the server child, duplicate the std handles into ones the server child owns exclusively (and don't pass through the originals).

Cross-references

Environment

  • OS: Windows 11 Home, build 26200
  • Shell tested: PowerShell 7 (also reproduces in cmd /k opencode and powershell -NoExit -Command "opencode")
  • Console hosts tested: ConPTY (via Warp) reproduces; classic conhost.exe does not
  • opencode version 如 1.0.202>`
  • Repro is 100% de

I'm happy to capture additional traces (Process Monitor, WinDbg post-mortem, etc.) if that helps narrow down the exact API call.

Plugins

No response

OpenCode version

No response

Steps to reproduce

  1. On Windows 11, open a ConPTY-backed terminal — any of: Warp, Windows Terminal, VS Code's integrated terminal.
  2. In the resulting PowerShell 7 (or pwsh) prompt, run:
    opencode
  3. Wait for the OpenCode TUI to load.
  4. Exit OpenCode using either of:
  • Type /exit and press Enter, or
  • Press Ctrl+C twice in quick succession.
  1. Observed: the parent PowerShell process terminates immediately (the host terminal pane closes, because the host correctly reacts to its child shell dying). On Warp the pane vanishes; on Windows Terminal the tab closes; on VS Code the integrated terminal session ends.
  2. Expected: OpenCode exits and control returns to the PowerShell prompt, which is what happens in a native conhost.exe window with the same opencode binary.

Screenshot and/or share link

No response

Operating System

Windows 11

Terminal

Warp

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions