Leith.Pty 0.1.0-ci.17
Leith.Pty
Leith.Pty is a native pseudo-terminal (PTY) library for .NET.
It provides one managed API over:
- Unix PTYs through a small native shim (
forkpty, plus aposix_spawnhelper on Darwin). - Windows ConPTY through P/Invoke.
- Two explicit I/O execution modes:
DedicatedandScalable.
Both I/O modes expose the same PtySession API and preserve the same process-exit,
output-drain, EOF, cancellation, resize, and completion semantics. The difference is
how the library spends resources while waiting for PTY I/O and process lifecycle
events.
See Docs/IO-MODES.md for the full execution-mode contract and Docs/STATUS.md for the current platform validation state.
Quick start
using Leith.Pty;
var startInfo = OperatingSystem.IsWindows()
? new PtyStartInfo
{
FileName = "cmd.exe",
Arguments = ["/d", "/s", "/c", "echo hello & exit /b 42"],
Columns = 80,
Rows = 24,
}
: new PtyStartInfo
{
FileName = "/bin/sh",
Arguments = ["-c", "echo hello; exit 42"],
Columns = 80,
Rows = 24,
};
using var session = Pty.Start(startInfo);
var buffer = new byte[4096];
while (true)
{
int read = await session.Output.ReadAsync(buffer, CancellationToken.None);
if (read == 0)
break;
Console.Write(System.Text.Encoding.UTF8.GetString(buffer, 0, read));
}
PtyExitStatus exit = await session.WaitForProcessExitAsync();
await session.WaitForCompletionAsync();
Output.ReadAsync(...) returns 0 only after the child has exited, the required
quiet-drain window has elapsed, and buffered output has been delivered.
I/O modes
PtyIoMode controls the resource topology used by a session. It does not change
the programming model or PTY semantics.
| Workload | Recommended mode | Why |
|---|---|---|
| Desktop terminal emulator | Dedicated |
Small PTY population; prioritize per-session isolation and throughput |
| IDE integrated terminals | Dedicated |
Usually a small or moderate number of interactive sessions |
| One or a few output-heavy PTYs | Dedicated |
Prefer the direct per-session path |
| Browser or web terminal service | Scalable |
Many concurrent or mostly-idle sessions |
| Agent or automation host | Scalable |
Avoid one blocked PTY-owned waiter per live session |
| CI or PTY test farm | Scalable |
Session density is part of the architecture |
| Unsure | Dedicated |
It is the default and the safer general-purpose choice |
var dedicated = Pty.Start(new PtyStartInfo
{
FileName = "/bin/bash",
IoMode = PtyIoMode.Dedicated,
});
var scalable = Pty.Start(new PtyStartInfo
{
FileName = "/bin/bash",
IoMode = PtyIoMode.Scalable,
});
There is no Auto mode. A caller that requests Scalable either gets the
scalable backend or receives PlatformNotSupportedException; Leith.Pty does not
silently fall back to Dedicated.
See Docs/IO-MODES.md for platform details and the resource-scaling contract.
Windows ConPTY host
On Windows, Leith.Pty can use either the ConPTY host bundled with the library or the system ConPTY implementation.
The default is:
WindowsConsoleHost = PtyWindowsConsoleHost.Bundled
The available policies are:
| Value | Behavior |
|---|---|
Bundled |
Prefer the bundled ConPTY host. If it cannot be used, fall back to the system host and record the reason. |
BundledOnly |
Require the bundled host. If it cannot be used, throw PlatformNotSupportedException. |
System |
Always use the system kernel32!CreatePseudoConsole / conhost.exe path. |
The bundled Windows assets are pinned to a tested Microsoft Terminal upstream build:
Microsoft Terminal upstream build @ fda72a0
source commit: fda72a070905570cd44e022658c7b9d1ee89322a
For each Windows RID, the package keeps the matching artifact set together:
conpty.dll
OpenConsole.exe
OpenConsoleProxy.dll
The exact provenance and SHA-256 hashes are committed in src/Leith.Pty/conpty/PROVENANCE.json.
Inspecting the selected host
Every Windows session exposes the resolved host information through
PtySession.ConsoleHost. The property is null on non-Windows platforms.
using var session = Pty.Start(new PtyStartInfo
{
FileName = "cmd.exe",
WindowsConsoleHost = PtyWindowsConsoleHost.Bundled,
});
PtyConsoleHostInfo? host = session.ConsoleHost;
Console.WriteLine(host?.RequestedMode);
Console.WriteLine(host?.EffectiveHost);
Console.WriteLine(host?.ConPtyDllVersion);
Console.WriteLine(host?.ExpectedHostPath);
Console.WriteLine(host?.FallbackReason);
PtyConsoleHostInfo exposes:
RequestedModeEnvironmentOverrideEffectiveHostConPtyDllPathConPtyDllVersionExpectedHostPathExpectedHostVersionFallbackReason
Process-wide override
The LEITH_PTY_WINDOWS_CONSOLE_HOST environment variable overrides
PtyStartInfo.WindowsConsoleHost for the process:
LEITH_PTY_WINDOWS_CONSOLE_HOST=bundled
LEITH_PTY_WINDOWS_CONSOLE_HOST=bundled-only
LEITH_PTY_WINDOWS_CONSOLE_HOST=system
An unknown value is treated as a configuration error and throws
InvalidOperationException.
For diagnostics, LEITH_PTY_CONPTY_DLL=<path-to-conpty.dll> can point host
resolution at an explicit bundled ConPTY directory.
Lifecycle and completion
Leith.Pty deliberately separates process exit from PTY completion.
WaitForProcessExitAsync
child process exit status is available
Output.ReadAsync(...) == 0
transport EOF has been reached after the required drain
WaitForCompletionAsync
process exit, transport drain, and output delivery are complete
This distinction matters when a child exits while the PTY still contains unread output.
PtySession.Output permits one active primitive reader across synchronous and
asynchronous reads. Overlapping primitive reads fail with
InvalidOperationException.
Public API
| Type / member | Purpose |
|---|---|
PtyStartInfo |
Spawn configuration: executable, arguments, working directory, environment overlay, geometry, terminal name, raw mode, quiet-drain window, I/O mode, and Windows console-host policy |
PtyIoMode |
Dedicated or Scalable; Dedicated is the default |
PtyWindowsConsoleHost |
Windows host policy: Bundled, BundledOnly, or System |
Pty.Start(PtyStartInfo) |
Start a PTY session without waiting for process exit |
PtySession.Input / Output |
Duplex terminal streams |
PtySession.ProcessId |
Child process ID |
PtySession.MasterFd |
Unix master file descriptor; -1 on Windows |
PtySession.ConsoleHost |
Windows ConPTY host diagnostics; null off Windows |
PtySession.Resize(...) |
Resize the terminal window |
PtySession.Kill(PtySignal) |
Send a Unix signal to the process group; on Windows, terminate the job object |
PtySession.Stop(PtySignal) |
Mark cancellation and then terminate the session |
PtySession.SendEof() |
Send terminal EOF using the platform-specific mechanism |
PtySession.GetForegroundProcessName() |
Return the foreground process name on Unix or the root child process name on Windows |
PtySession.WaitForExit(timeout) |
Synchronous process wait |
PtySession.WaitForProcessExitAsync(...) |
Wait until child exit status is available |
PtySession.WaitForCompletionAsync(...) |
Wait for exit, drain, and final output delivery |
PtyExitStatus |
Exit code or terminating signal |
PtyEnvironment.MergeForSpawn |
Merge the parent environment with a caller-provided overlay |
PtyEnvironment.DefaultTerm |
Unix fallback terminal name: xterm-256color |
Environment overlay
PtyStartInfo.Environment is an overlay on the parent process environment:
- a
nullvalue leaves the inherited variable unchanged; - an empty string removes the variable;
- any other string sets or replaces it.
On Unix, if TerminalName is not set and the inherited environment has no
TERM, Leith.Pty uses xterm-256color.
On Windows, Leith.Pty does not synthesize TERM by default.
Geometry validation
Spawn and resize geometry is validated against the public
PtyStartInfo.Min* / Max* limits. Invalid values throw
ArgumentOutOfRangeException; values are not silently clamped.
Platform status
Leith.Pty contains production Dedicated and Scalable backends for its
supported platform families.
| Platform | Implementation | Current validation |
|---|---|---|
| Linux x64 (glibc) | forkpty; Dedicated blocking read path; Scalable epoll + pidfd |
Core contract, output-read, lifecycle, and bulk-drain coverage |
| Linux arm64 (glibc) | Same Linux backend | Conformance coverage on arm64 |
| Linux musl x64 / arm64 | Same Linux backend with packaged musl native shims | Conformance and package-consumer coverage |
| Windows x64 | ConPTY + job object; Dedicated overlapped path; Scalable IOCP path; bundled ConPTY by default | Core contract, dual-mode, host-selection, and bundled-host coverage |
| Windows arm64 | Same Windows backend | Conformance coverage; see platform matrix for the exact host/version used by each recorded run |
| macOS arm64 | posix_spawn helper; Dedicated and shared-kqueue paths |
Full-suite and dual-mode coverage |
| macOS x64 | Same Darwin backend | Conformance coverage under Rosetta 2 |
| FreeBSD x64 | forkpty; Dedicated FIONREAD path; Scalable shared kqueue path |
Contract and dual-mode coverage |
The detailed evidence index, including the exact hosts and limitations of each run, is Docs/baseline/PLATFORM-MATRIX.md.
Build and test
dotnet build Term.Pty.slnx -c Release
dotnet test tests/Leith.Pty.Tests/Leith.Pty.Tests.csproj -c Release
dotnet test tests/Leith.Pty.PackageSmoke/Leith.Pty.PackageSmoke.csproj -c Release
GitHub Actions (self-hosted buildx64, Linux x64) builds the same solution and publishes
Leith.Pty NuGet packages to BaGetter (https://bageter.buchmiet.co.uk/v3/index.json) on main — no sibling repo checkouts.
See Docs/CI.md.
Unix native assets
The Unix native shim is built by Leith.Pty.csproj. Depending on the platform,
the output includes:
libleithpty_unix.so
libleithpty_unix.dylib
leithpty_spawn_helper
Package assets are placed under runtimes/<RID>/native/.
After loading the Unix shim, the managed library verifies
leithpty_abi_version(). The private C-to-C# ABI is documented in
Docs/NATIVE-ABI.md.
Building from source with bundled ConPTY
When building the repository from source, MSBuild uses
scripts/Fetch-BundledConPty.ps1 to populate the
pinned Windows artifact cache and verify its hashes against
src/Leith.Pty/conpty/PROVENANCE.json.
The cache layout is:
artifacts/conpty/fda72a0/
├── win-x64/
│ ├── conpty.dll
│ ├── OpenConsole.exe
│ └── OpenConsoleProxy.dll
└── win-arm64/
├── conpty.dll
├── OpenConsole.exe
└── OpenConsoleProxy.dll
The staged copies under src/Leith.Pty/runtimes/win-*/native/ are generated
files and are not committed.
If the verified cache has already been populated for an offline build, automatic fetching can be skipped with:
dotnet build Term.Pty.slnx -c Release /p:SkipFetchBundledConPty=true
A source build needs PowerShell (pwsh or Windows PowerShell) unless the cache
has already been populated.
NuGet packages contain the Windows runtime assets under the standard
runtimes/win-<arch>/native/ layout, so package consumers do not need to run
the fetch script.
Documentation
| Document | Contents |
|---|---|
| Docs/IO-MODES.md | Public Dedicated / Scalable contract and mode-selection guidance |
| Docs/STATUS.md | Current implementation and validation status |
| Docs/baseline/PLATFORM-MATRIX.md | Platform-by-platform validation index |
| Docs/Leith.Term.Presentation-Integration.md | Integration contract for the terminal presentation layer |
| Docs/NATIVE-ABI.md | Private Unix C-to-C# ABI and packaging contract |
| Docs/TEST-CORPUS.md | Test corpus and validation protocol |
| src/Leith.Pty/conpty/PROVENANCE.json | Exact provenance and hashes for bundled Windows ConPTY assets |
| Docs/baseline/bundled-conpty/BC10-bundle-main-20536.md | Detailed validation record for the currently pinned bundled ConPTY build |
Historical implementation plans and research notes remain in Docs/ and
research/, but the files above are the best entry points for current users.
License
MIT — see LICENSE.
Third-party attributions are in THIRD-PARTY-NOTICES.md.
Showing the top 20 packages that depend on Leith.Pty.
| Packages | Downloads |
|---|---|
|
Leith.Term.Presentation.Transport.Local
Local PTY transport for Leith.Term.Presentation.
|
0 |
.NET 10.0
- No dependencies.
| Version | Downloads | Last updated |
|---|---|---|
| 19.0.0 | 5 | 09/28/2026 |
| 0.1.0-ci.17 | 0 | 09/28/2026 |
| 0.1.0-ci.16 | 0 | 09/28/2026 |