Clamp ConPTY sizes to the 1x1 minimum that Windows accepts

CreatePseudoConsole and ResizePseudoConsole reject zero dimensions with
E_INVALIDARG, but we legitimately request them: the pty is sized after
the main view, and that view is zero-sized while hidden, e.g. in
full-screen mode with a side panel focused. Entering that mode while a
custom pager is configured therefore made StartPty fail (degrading to
unpaged output now that the fallback works), and resizing a live pty
from onResize would fail layout. The Unix pty accepts zero sizes, so
the clamp lives in the Windows implementation only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Stefan Haller
2026-07-23 17:31:26 +02:00
co-authored by Claude Fable 5
parent c217084c90
commit 02c8ba3073
2 changed files with 11 additions and 5 deletions
+11 -2
View File
@@ -36,7 +36,16 @@ func (p *winPty) Resize(cols, rows uint16) error {
// there is nothing left to resize.
return nil
}
return windows.ResizePseudoConsole(p.hpc, windows.Coord{X: int16(cols), Y: int16(rows)})
return windows.ResizePseudoConsole(p.hpc, clampPtySize(cols, rows))
}
// clampPtySize clamps a requested pty size to the minimum that ConPTY
// accepts: CreatePseudoConsole and ResizePseudoConsole reject zero
// dimensions with E_INVALIDARG, but callers legitimately request them — the
// pty is sized after the main view, which is zero-sized while hidden, e.g.
// in full-screen mode with a side panel focused.
func clampPtySize(cols, rows uint16) windows.Coord {
return windows.Coord{X: int16(max(cols, 1)), Y: int16(max(rows, 1))}
}
// closeHpc closes the pseudoconsole exactly once. Safe to call from multiple
@@ -140,7 +149,7 @@ func StartPty(cmd *exec.Cmd, cols, rows uint16) (sp StartedPty, err error) {
// CreatePseudoConsole dupes the handles it needs internally; we release
// our references to the child-side ends immediately after.
var hpc windows.Handle
size := windows.Coord{X: int16(cols), Y: int16(rows)}
size := clampPtySize(cols, rows)
if err = windows.CreatePseudoConsole(size, inRead, outWrite, 0, &hpc); err != nil {
_ = windows.CloseHandle(inRead)
_ = windows.CloseHandle(outWrite)
@@ -16,10 +16,7 @@ func TestStartPtyWithZeroSize(t *testing.T) {
// in place of handles to the attached pseudoconsole, so command output
// would bypass the pty and pollute the test log.
sp, err := StartPty(exec.Command("cmd", "/c", "exit 0"), 0, 0)
/* EXPECTED:
assert.NoError(t, err)
ACTUAL: */
assert.Error(t, err)
if err == nil {
_ = sp.Wait()