Skip to content

Implement PTY (Pseudo-Terminal) support for System.Diagnostics.Process#129425

Draft
Copilot wants to merge 25 commits into
mainfrom
copilot/implement-pty-support
Draft

Implement PTY (Pseudo-Terminal) support for System.Diagnostics.Process#129425
Copilot wants to merge 25 commits into
mainfrom
copilot/implement-pty-support

Conversation

Copilot AI commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

main PR

Description

Implements PTY support for System.Diagnostics.Process on supported Unix and Windows platforms, including new public APIs (PseudoTerminal, PseudoTerminalOptions, and ProcessStartInfo.PseudoTerminal) and native integration for process startup.

Follow-up feedback updates are included:

  • Clarified ProcessStartInfo.PseudoTerminal docs to state that stderr is not opened as a separate stream and is observed via StandardOutput.
  • Relaxed ProcessStartInfo.ThrowIfInvalid to allow StandardInputEncoding and StandardOutputEncoding when PTY is used.
  • On Unix PTY stream setup, when encodings are not specified, default to UTF-8 without BOM.
  • Added/updated PTY tests to validate encoding behavior and defaults, alongside existing PTY coverage and platform/environment gating.

Customer Impact

Enables terminal-emulator/interactive CLI scenarios (including proper PTY-backed stdin/stdout/stderr behavior) directly via System.Diagnostics.Process, with clearer stream semantics and configurable encodings for PTY use.

Regression

No known product regression is being fixed. This is primarily new functionality with follow-up correctness/behavior refinements from review feedback.

Testing

  • dotnet build /home/runner/work/runtime/runtime/src/libraries/System.Diagnostics.Process/src/System.Diagnostics.Process.csproj -p:Configuration=Release
  • dotnet build /t:test /home/runner/work/runtime/runtime/src/libraries/System.Diagnostics.Process/tests/System.Diagnostics.Process.Tests.csproj -p:Configuration=Release
  • ./build.sh clr+libs -rc release
  • ./build.sh clr+libs+host -rc release -lc release
  • System.Diagnostics.Process.Tests: 652 passed, 0 failed, 6 skipped on validated Unix target.

Risk

Moderate. The change introduces new API surface and platform-specific PTY plumbing, but includes validation, platform guards, and targeted test coverage (including follow-up tests for encoding behavior) to reduce risk.

Package authoring no longer needed in .NET 9

IMPORTANT: Starting with .NET 9, you no longer need to edit a NuGet package's csproj to enable building and bump the version.
Keep in mind that we still need package authoring in .NET 8 and older versions.

Copilot AI and others added 2 commits June 15, 2026 13:07
… assembly

- Add PseudoTerminal/PseudoTerminalOptions public classes with platform-specific impls
- Add SafePseudoConsoleHandle for Windows
- Wire up pal_process.c with pseudoTerminalSecondaryFd parameter (setsid + TIOCSCTTY)
- Add CreatePseudoConsole/ResizePseudoConsole/ClosePseudoConsole interop (Windows)
- Add OpenPseudoTerminal/ResizePseudoTerminal interop (Unix via pal_console.c)
- Update ProcessStartInfo with PseudoTerminal property and validation
- Update Process.cs start flow for PTY child handles and stream opening
- Update SafeProcessHandle.Windows.cs with PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
- Update SafeProcessHandle.Unix.cs to pass PTY secondary fd to ForkAndExecProcess
- Update ref assembly with new public API surface

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
…s PTY, add tests

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI self-assigned this Jun 15, 2026
Copilot AI review requested due to automatic review settings June 15, 2026 13:38
Copilot AI removed the request for review from Copilot June 15, 2026 13:38
@adamsitnik adamsitnik added the NO-REVIEW Experimental/testing PR, do NOT review it label Jun 15, 2026

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please address my feedback

Comment thread src/libraries/Common/src/Interop/Unix/System.Native/Interop.ForkAndExecProcess.cs Outdated
Comment thread src/libraries/Common/src/Interop/Unix/System.Native/Interop.PseudoTerminal.cs Outdated
Comment thread src/libraries/System.Diagnostics.Process/tests/PseudoTerminalTests.cs Outdated
Comment thread src/libraries/System.Diagnostics.Process/tests/PseudoTerminalTests.cs Outdated
Comment thread src/native/libs/System.Native/pal_console.c Outdated
Comment thread src/native/libs/System.Native/pal_console.c
Comment thread src/native/libs/System.Native/pal_process.c Outdated
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-diagnostics-process
See info in area-owners.md if you want to be subscribed.

@am11 am11 requested a review from tmds June 15, 2026 14:59
…solidation, magic number removal, posix_spawn PTY fix

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 15, 2026 15:06

Copilot AI commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please address my feedback

All feedback addressed in commit 2839dc9. Key changes:

  • SafeFileHandle? + AddRef/ReleaseRef for PTY handle in ForkAndExecProcess
  • SafeFileHandle out params for OpenPseudoTerminal (native signature updated to intptr_t*)
  • Removed manual PTY fd extraction; pass startInfo.PseudoTerminal?.Secondary directly
  • Handle safety with DangerousAddRef/DangerousRelease for pseudo console on Windows
  • AnonymousPipeClientStream for PTY streams on both platforms
  • Expression body for Create, documented Dispose effects, fixed exception doc
  • Removed magic numbers (Unix: NULL winsize when no options; Windows: documented 120x30 default)
  • Reordered Windows dispose (pipes first, then pseudo console)
  • Consolidated tests: removed redundant test, merged resize tests, [Theory] for validation
  • Nested DllImport as local function in isatty test
  • Prevented posix_spawn path when PTY is specified (need fork for setsid/TIOCSCTTY)

Copilot AI requested a review from adamsitnik June 15, 2026 15:08

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address all of my unresolved feedback

Comment thread src/libraries/Common/src/Interop/Unix/System.Native/Interop.ForkAndExecProcess.cs Outdated
Comment thread src/libraries/System.Diagnostics.Process/src/System/Diagnostics/PseudoTerminal.cs Outdated
Comment thread src/libraries/System.Diagnostics.Process/src/System/Diagnostics/PseudoTerminal.cs Outdated
Comment thread src/native/libs/System.Native/pal_console.c Outdated
Comment thread src/native/libs/System.Native/pal_console.c
… usePty, FileStream on Windows, simplify Dispose

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 15, 2026 15:53

Copilot AI commented Jun 15, 2026

Copy link
Copy Markdown
Contributor Author

@copilot address all of my unresolved feedback

All unresolved feedback addressed in the latest commit:

  • Dispose() simplified to expression body delegating to DisposeCore()
  • PseudoTerminalOptions.Columns/Rows are now mandatory int (no nullable, no defaults)
  • Create() requires non-null options with positive dimensions
  • ForkAndExecProcess uses bool usePty instead of redundant handle param
  • Windows reverted to FileStream (only Unix uses AnonymousPipeClientStream)
  • Answered platform support questions for openpty/ioctl/TIOCSWINSZ

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 15, 2026 15:56
Copilot AI requested a review from adamsitnik June 15, 2026 15:57
Copilot AI review requested due to automatic review settings June 18, 2026 12:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

src/libraries/System.Diagnostics.Process/src/Microsoft/Win32/SafeHandles/SafeProcessHandle.Windows.cs:187

  • When PseudoTerminal is used, STARTF_USESTDHANDLES is still set, but the current code skips initializing StartupInfo.hStdInput/Output/Error. If these remain zero/invalid, CreateProcess can fail with invalid parameter. Initialize the std handles unconditionally (using the handles provided by Process.Start).
                if (startInfo.PseudoTerminal is null)
                {
                    ProcessUtils.DuplicateAsInheritableIfNeeded(stdinHandle!, ref startupInfoEx.StartupInfo.hStdInput, ref stdinRefAdded);
                    ProcessUtils.DuplicateAsInheritableIfNeeded(stdoutHandle!, ref startupInfoEx.StartupInfo.hStdOutput, ref stdoutRefAdded);
                    ProcessUtils.DuplicateAsInheritableIfNeeded(stderrHandle!, ref startupInfoEx.StartupInfo.hStdError, ref stderrRefAdded);
                }

                // If STARTF_USESTDHANDLES is not set, the new process will inherit the standard handles.
                startupInfoEx.StartupInfo.dwFlags = Interop.Advapi32.StartupInfoOptions.STARTF_USESTDHANDLES;

Comment thread src/libraries/System.Diagnostics.Process/src/Resources/Strings.resx
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 18, 2026 12:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 29 out of 29 changed files in this pull request and generated 6 comments.

Comment on lines +1314 to +1318
// When PTY is used, the child handles are owned by the PseudoTerminal object.
if (startInfo.PseudoTerminal is null)
{
childErrorHandle?.Dispose();
if (startInfo.StandardInputHandle is null)
{
Comment thread src/native/libs/configure.cmake Outdated
Comment thread src/native/libs/System.Native/extra_libs.cmake
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

Copilot AI commented Jun 18, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please address my feedback

Addressed in 91231b88c.

I updated the native CMake logic to handle openpty/libutil on Linux (non-Android):

  • src/native/libs/configure.cmake: include libutil in the openpty probe when available.
  • src/native/libs/System.Native/extra_libs.cmake: link libutil on Linux when found.

Validation:

  • ./build.sh libs.native -os wasi -arch wasm -c Release
  • ./build.sh libs.native -os linux -arch x64 -c Release

Comment thread src/native/libs/System.Native/extra_libs.cmake Outdated
Comment thread src/libraries/System.Diagnostics.Process/tests/PseudoTerminalTests.cs Outdated
This reverts commit 91231b8.
Not needed for supported glibc versions.

Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Copilot AI requested review from Copilot and removed request for Copilot June 18, 2026 15:48
Comment thread src/native/libs/System.Native/extra_libs.cmake
Copilot AI and others added 2 commits June 18, 2026 16:06
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>

@adamsitnik adamsitnik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot address my feedback

Comment thread src/native/libs/Common/pal_config.h.in Outdated
Comment thread src/native/libs/System.Native/pal_console.c Outdated
Comment thread src/native/libs/System.Native/pal_console.c
Comment thread src/native/libs/System.Native/pal_console.c
Co-authored-by: adamsitnik <6011991+adamsitnik@users.noreply.github.com>
Comment thread src/libraries/System.Diagnostics.Process/tests/PseudoTerminalTests.cs Outdated
Co-authored-by: Adam Sitnik <adam.sitnik@gmail.com>
private static bool? s_isCreatePseudoConsoleSupported;

private static bool IsCreatePseudoConsoleSupported =>
s_isCreatePseudoConsoleSupported ??= !PlatformDetection.IsWindows || CanCreatePseudoConsole();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not threadsafe (bool? updates are not atomic)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jkotas this PR is not ready for review yet, it's just a proof of concept (I am trying to get a better understanding of how PTYs work across various OSes and what shape of the API we could support).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

src/libraries/System.Diagnostics.Process/src/System/Diagnostics/Process.cs:1291

  • When starting with a PTY on Windows, child*Handle values can remain null (the Windows SetChildHandlesForPseudoTerminal is a no-op), but ValidateInheritedHandles assumes all three are non-null when inheritedHandles is non-empty. This can turn ProcessStartInfo.InheritedHandles + PseudoTerminal into a runtime NullReferenceException in release builds. Consider guarding the validation so it only runs when all three std handles are available (or otherwise explicitly define/validate std-handle behavior for PTY starts on Windows).
                    if (startInfo.PseudoTerminal is not null)
                    {
                        SetChildHandlesForPseudoTerminal(startInfo.PseudoTerminal, ref childInputHandle, ref childOutputHandle, ref childErrorHandle);
                    }
                    else if (childInputHandle is null || childOutputHandle is null || childErrorHandle is null)
                    {
                        if (startInfo.StartDetached)
                        {
                            SafeFileHandle nullDeviceHandle = File.OpenNullHandle();
                            childInputHandle ??= nullDeviceHandle;
                            childOutputHandle ??= nullDeviceHandle;
                            childErrorHandle ??= nullDeviceHandle;
                        }
                        else if (ProcessUtils.PlatformSupportsConsole)
                        {
                            childInputHandle ??= Console.OpenStandardInputHandle();
                            childOutputHandle ??= Console.OpenStandardOutputHandle();
                            childErrorHandle ??= Console.OpenStandardErrorHandle();
                        }
                    }

                    ProcessStartInfo.ValidateInheritedHandles(childInputHandle, childOutputHandle, childErrorHandle, inheritedHandles);
                }

Comment on lines +22 to +24
#if HAVE_POSIX_ADVISE
#include <stdlib.h>
#endif
Comment thread src/native/libs/System.Native/pal_process_wasi.c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-System.Diagnostics.Process NO-REVIEW Experimental/testing PR, do NOT review it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants