test(ssh): capture hung Windows client phase

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo-H
2026-07-17 00:07:14 -04:00
co-authored by Orca
parent 149a122b73
commit b24dda7448
7 changed files with 224 additions and 41 deletions
@@ -91,6 +91,7 @@ afterAll(() => {
describe('Windows SSH no-input launcher', () => {
it('keeps the artifact source scoped to the reviewed Win32 boundary', () => {
const processSource = readFileSync(join(sourceRoot, 'WindowsSshChildProcess.cs'), 'utf8')
const programSource = readFileSync(join(sourceRoot, 'OrcaSshNoInputLauncher.cs'), 'utf8')
const consoleSource = readFileSync(join(sourceRoot, 'WindowsPrivateConsoleInput.cs'), 'utf8')
const outputSource = readFileSync(join(sourceRoot, 'WindowsBoundedOutputFiles.cs'), 'utf8')
@@ -103,6 +104,9 @@ describe('Windows SSH no-input launcher', () => {
expect(processSource).toContain('OutputLimitPollMilliseconds = 50')
expect(processSource).toContain('JobSettlementTimeoutMilliseconds = 5000')
expect(processSource).toContain('TerminateJobObject')
expect(processSource).toContain('diagnosticTimeoutMilliseconds')
expect(programSource).toContain('--diagnostic-timeout-ms')
expect(programSource).toContain('diagnosticTimeoutMilliseconds > 60000')
expect(processSource).toContain('outputs.EnsureWithinLimits()')
expect(processSource).toContain('outputs.ReplayOutputs()')
expect(processSource).not.toContain('WaitForMultipleObjects')
@@ -195,6 +199,26 @@ describe('Windows SSH no-input launcher', () => {
expect(listCaptureFiles()).toEqual(capturesBefore)
})
itWindows(
'terminates the child job and replays bounded output at the diagnostic deadline',
{ timeout: 10_000 },
async () => {
const capturesBefore = listCaptureFiles()
const pidPath = join(fixtureRoot, 'timed-out-child.pid')
const result = runLauncher('hold', [pidPath], {}, 250)
const childPid = Number.parseInt(readFileSync(pidPath, 'utf8'), 10)
expect(result.status).toBe(1)
expect(result.stdout.toString('utf8')).toBe('ORCA-HOLD-STDOUT')
expect(result.stderr.toString('utf8')).toContain('ORCA-HOLD-STDERR')
expect(result.stderr.toString('utf8')).toContain(
'SSH child reached the 250 ms diagnostic timeout.'
)
await waitForProcessExit(childPid, 5_000)
expect(listCaptureFiles()).toEqual(capturesBefore)
}
)
itWindows('does not leak an unrelated inheritable parent handle into the child', () => {
const result = spawnSync(handleProbePath, [launcherPath], {
cwd: fixtureRoot,
@@ -231,15 +255,21 @@ describe('Windows SSH no-input launcher', () => {
)
})
function runLauncher(mode, args = [], options = {}) {
return spawnSync(launcherPath, [process.execPath, childFixturePath, mode, ...args], {
cwd: fixtureRoot,
encoding: null,
timeout: 10_000,
windowsHide: true,
maxBuffer: 32 * 1024 * 1024,
...options
})
function runLauncher(mode, args = [], options = {}, diagnosticTimeoutMs = null) {
const diagnosticArgs =
diagnosticTimeoutMs == null ? [] : ['--diagnostic-timeout-ms', String(diagnosticTimeoutMs)]
return spawnSync(
launcherPath,
[...diagnosticArgs, process.execPath, childFixturePath, mode, ...args],
{
cwd: fixtureRoot,
encoding: null,
timeout: 10_000,
windowsHide: true,
maxBuffer: 32 * 1024 * 1024,
...options
}
)
}
function listCaptureFiles() {
@@ -328,6 +358,8 @@ if (mode === 'arguments-and-stdin') {
}
} else if (mode === 'hold') {
require('node:fs').writeFileSync(process.argv[3], String(process.pid))
process.stdout.write('ORCA-HOLD-STDOUT')
process.stderr.write('ORCA-HOLD-STDERR')
setInterval(() => {}, 1000)
} else {
process.exit(99)
@@ -9,9 +9,9 @@ keeps commands, hashes, runner identities, timings, and failure details.
A checked box means the work has evidence in the detailed ledger. Design approval alone does not
complete a box.
Active checkpoint: **Milestone 6 / Work Package 5 bounded runtime transfer — disconnected private-
console plus bounded regular-output Windows diagnostic locally green; exact-head native proof
required, 2026-07-16, Codex implementation owner.**
Active checkpoint: **Milestone 6 / Work Package 5 bounded runtime transfer — bounded verbose
Win32-OpenSSH lifecycle capture locally green; exact-head native proof required, 2026-07-16, Codex
implementation owner.**
`E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-PORTABLE-FIXTURE-LOCAL-001` records the portable-fixture
correction locally green: the no-Orca-publication workflow oracle permits the one exact pinned
Microsoft fixture release URL while Orca release URLs, `gh release`, and `contents: write` remain
@@ -210,6 +210,21 @@ isolation pass. Seven native
launcher cases plus live/full-size behavior remain honest local skips. Commit/push only this
runner-temp diagnostic and require fresh exact-head Windows x64/ARM64 compilation, overflow,
cleanup, cancellation, live lifecycle, and fixture-teardown proof. SignPath remains deferred.
Exact head `149a122b735659b47d7718f409d14e1ed2f947c2`, run `29553325782`, and x64/ARM64 jobs
`87800290122`/`87800290144` prove 101 files/862 cases with 19 skips, including both new overflow and
capture-cleanup cases, plus unchanged reproducibility and full-size cache gates. The live probes
still time out after 8017.5611/8042.1902 ms with ended stdout, closed channel, no sentinel/process
exit, and zero stderr; both exact teardowns catch locked `UsrClass.dat`. All four non-Windows jobs
and both Linux supplements pass. `E-M6-WINDOWS-PRIVATE-CONSOLE-REGULAR-OUTPUT-CI-RED-001` therefore
falsifies output pipes as the remaining cause. Next add only an internal diagnostic deadline and
bounded replay of verbose OpenSSH output before choosing ConPTY or another client. Production,
Beta, fallback, tuple, publication, default, and signing paths remain untouched.
`E-M6-WINDOWS-VERBOSE-LIFECYCLE-CAPTURE-LOCAL-001` records that diagnostic locally green: an
optional 100-60,000 ms runner-only deadline terminates and settles the owned job before bounded
output replay; the live suite uses 6 seconds, `-vvv`, outer 8/10-second safety ceilings, and a 16 KiB
trace tail. Focused 119/119, all 285 release contracts, typecheck, full lint/reliability/max-lines,
formatting, diff, and protected-resolver isolation pass with honest native skips. Commit/push only
this artifact and require x64/ARM64 phase classification before selecting ConPTY or another client.
`E-M6-WINDOWS-SYSTEM-SSH-TREE-LIVE-AUDIT-001` fixes the loopback-only official Microsoft
server, fixture-owned non-admin account/ACL, exact host-key trust, Windows PowerShell 5.1,
serial/default and four-channel metrics, cancellation/collision/cleanup, and deterministic teardown
@@ -8,7 +8,7 @@ work; keep exact commands, runner identities, hashes, metrics, and residual gaps
Date created: 2026-07-14<br>
Last updated: 2026-07-16<br>
Current phase: Milestone 6 / Work Package 5 bounded runtime transfer — **Locally green / exact-head native proof required — 2026-07-16, Codex implementation owner: disconnected private-console plus bounded regular-output diagnostic**. `E-M6-WINDOWS-PRIVATE-CONSOLE-REGULAR-OUTPUT-LOCAL-001` records the one-variable runner-temp candidate locally green: proven private-console EOF, distinct bounded regular-file output handles, delete-on-close cleanup, bounded job settlement, and post-exit replay. Commit/push only this package and require x64/ARM64 legacy compilation, native contracts, live lifecycle, and exact teardown evidence. Every production/default SSH path remains disconnected. Real Apple/SignPath rehearsals remain deferred.<br>
Current phase: Milestone 6 / Work Package 5 bounded runtime transfer — **Locally green / bounded verbose client-lifecycle native proof required — 2026-07-16, Codex implementation owner**. `E-M6-WINDOWS-VERBOSE-LIFECYCLE-CAPTURE-LOCAL-001` records the runner-temp internal deadline, job settlement, bounded replay, and verbose trace-tail contracts locally green. Commit/push only this diagnostic and require both native architectures to identify the last completed client phase before selecting ConPTY or another client. Every production/default SSH path remains disconnected. Real Apple/SignPath rehearsals remain deferred.<br>
Session checkpoint: **PRIVATE CONSOLE EOF PROVEN / PRIVATE OUTPUT PIPES FALSIFIED / BOUNDED REGULAR OUTPUT ACTIVE — 2026-07-16, Codex implementation owner** — exact head `5841b12bc94ecd1027e507e4b741e09fcb0a093d`, run `29552101635`, and x64/ARM64 jobs `87796673326`/`87796673353` both pass 101 files/860 cases with 19 skips, legacy compilation, real-console qualification/EOF, argv/binary/exit/handle-exclusion/cancellation, unchanged full-runtime reproducibility, and full-size cache gates. Live diagnostics still time out after 8025.1553/8034.6943 milliseconds with ended stdout, closed channel, no sentinel/process exit, and zero stderr; both exact teardowns catch locked `UsrClass.dat`. All four non-Windows primary jobs and both Linux supplements pass. No product caller, full-size Windows transfer claim, upload, default/fallback, Beta, tuple publication, or signing behavior changed.<br>
Primary design: [SSH relay GitHub Release plan](./2026-07-14-ssh-relay-github-release-plan.html)<br>
Motivating issues: [#8450](https://github.com/stablyai/orca/issues/8450), [#1693](https://github.com/stablyai/orca/issues/1693)
@@ -19891,6 +19891,75 @@ config/scripts/ssh-relay-runtime-workflow.test.mjs` passes 119 runnable cases wi
Beta option, fallback, full-size Windows transfer claim, tuple, publication, default, signing, or
SignPath behavior is authorized.
### E-M6-WINDOWS-PRIVATE-CONSOLE-REGULAR-OUTPUT-CI-RED-001 — regular output files do not settle Win32-OpenSSH
- Date/owner/head/run: 2026-07-16, Codex implementation owner; exact pushed head
`149a122b735659b47d7718f409d14e1ed2f947c2`, pull-request artifact run `29553325782`, Windows x64
job `87800290122` on `windows-2022`, and Windows ARM64 job `87800290144` on `windows-11-arm`.
- Native contract proof: both architectures pass legacy .NET Framework compilation, the dedicated
console EOF probe, and 101 artifact-contract files with 862 runnable cases and 19 declared skips.
The two new cases raise the prior count from 860 to 862 and prove >16 MiB fail-closed output plus
successful/overflow delete-on-close cleanup; exact binary replay, argv, exit propagation,
unrelated-handle exclusion, job cancellation, and bounded settlement also pass. Both unchanged
runtimes pass build-twice identity, closure, smoke, and both full-size cache gates.
- X64 live result: `mode=private-console-regular-output-launcher-no-n`,
`elapsedMs=8017.5611`, `success=false`, `timedOut=true`, `sentinel=false`, `stdoutEnded=true`,
`processExit=null`, `channelClosed=true`, `closeCode=null`, and `stderrBytes=0`.
- ARM64 live result: the same lifecycle fields with `elapsedMs=8042.190199999999`. Both official
server logs prove accepted public-key authentication, remote command-session start, and remote
session close before the client requires bounded external termination. The full-size Windows
transfer remains correctly unexecuted after the failed diagnostic.
- Teardown result: x64 cannot unload fixture class hive
`S-1-5-21-4137868374-391603265-4009536220-1003_Classes`; ARM64 independently cannot unload
`S-1-5-21-1882319117-3219095328-2125279949-1001_Classes`. Both catch the locked
`C:\Users\orca_ssh_fixture\AppData\Local\Microsoft\Windows\UsrClass.dat` and retained profile
directory instead of silently leaking fixture state.
- Preserved evidence: Linux x64/ARM64 jobs `87800290119`/`87800290139`, Darwin x64/ARM64 jobs
`87800290123`/`87800290135`, and Linux oldest-userland supplements `87800855219`/`87800855211`
all pass at the same exact head.
- Interpretation/next bound: private pipes, overlapped pipes, zero-length regular stdin, proven
console EOF, and now distinct bounded regular-file stdout/stderr all reproduce the live hang.
Do not select a production mechanism from speculation. Add only a runner-temp internal deadline
that terminates and settles the owned job before replaying bounded output, invoke the exact client
with verbose diagnostics, and record the last completed OpenSSH phase on both architectures.
ConPTY and alternate-client qualification remain subsequent evidence decisions. No product,
Beta, fallback, tuple, publication, default, signing, or SignPath behavior is authorized.
### E-M6-WINDOWS-VERBOSE-LIFECYCLE-CAPTURE-LOCAL-001 — internal timeout and bounded trace capture are locally green
- Date/owner/base/runner: 2026-07-16, Codex implementation owner; uncommitted artifact-only package
atop exact regular-output RED head `149a122b735659b47d7718f409d14e1ed2f947c2` on Apple arm64,
macOS 26.2 build 25C56, Node 26.5.0, and pnpm 10.24.0.
- Diagnostic boundary: an optional runner-only `--diagnostic-timeout-ms` accepts 100 through 60,000
ms and leaves ordinary launcher calls unbounded as before. At deadline the launcher terminates
and waits for its owned job, replays the same bounded regular-file stdout/stderr, then returns a
diagnostic failure. The live purpose suite uses 6,000 ms, retains the existing 8,000/10,000 ms
external safety ceilings, inserts `-vvv` before the exact OpenSSH destination separator, and logs
only a 16 KiB stderr tail plus lifecycle fields. Production argument construction is unchanged.
- Native contract design: the new Windows case writes stdout/stderr plus its PID, holds forever,
and requires internal timeout failure, exact replay, child-job termination, and unchanged capture-
file set. Existing console EOF, argv, binary output, overflow, cleanup, handle exclusion, exit,
and external-cancellation cases remain. The new native case is honestly skipped locally.
- Syntax/focused evidence: both launcher scripts pass `node --check`.
`/usr/bin/time -l pnpm exec vitest run --config config/vitest.config.ts --maxWorkers=1
src/main/ssh/ssh-connection.test.ts src/main/ssh/ssh-system-fallback.test.ts
src/main/ssh/ssh-system-no-input-windows-openssh.test.ts
src/main/ssh/ssh-relay-runtime-windows-system-ssh-openssh-full-size.test.ts
config/scripts/ssh-relay-runtime-windows-no-input-launcher.test.mjs
config/scripts/ssh-relay-runtime-workflow.test.mjs` passes 119 runnable cases with ten declared
native/live skips in 2.69 seconds real; maximum RSS is 171,212,800 bytes and peak footprint is
97,179,456 bytes.
- Release/static evidence: the unquoted release-contract command passes 51 files and 285 runnable
cases with eight declared Windows skips in 12.19 seconds. `/usr/bin/time -l pnpm typecheck`
passes in 2.57 seconds real with 1,258,979,328-byte maximum RSS and 97,163,024-byte peak
footprint. `/usr/bin/time -l pnpm lint` passes in 21.90 seconds real with the 26 existing
warnings, all 41 reliability gates, and the 355-entry max-lines ratchet; maximum RSS is
2,039,169,024 bytes and peak footprint is 97,195,840 bytes.
- Residual boundary: local source contracts do not prove legacy compilation, internal timeout/job
settlement, output replay, or useful OpenSSH verbose content on Windows. Commit/push this isolated
package and require exact-head x64/ARM64 evidence. Do not select ConPTY, another client, a product
caller, Beta, fallback, tuple, publication, default, signing, or SignPath behavior from local proof.
## Accepted Gaps
No product gap is accepted merely because it appears in this list. Each entry requires explicit
@@ -19949,11 +20018,10 @@ The project is not complete until every applicable item below is checked with ev
## Next Required Action
Commit and push only the private-console plus bounded regular-output diagnostic recorded by
`E-M6-WINDOWS-PRIVATE-CONSOLE-REGULAR-OUTPUT-LOCAL-001`, then require exact-head Windows x64 and
ARM64 legacy-compiler, console qualification/EOF, bounded binary output/overflow/cleanup,
argv/exit/handle-exclusion/cancellation, live diagnostic, and exact profile/directory teardown
evidence. Do not
Commit and push only the bounded internal-timeout and verbose-output capture recorded by
`E-M6-WINDOWS-VERBOSE-LIFECYCLE-CAPTURE-LOCAL-001`, then require exact-head Windows x64 and
ARM64 legacy-compiler, launcher-timeout settlement, verbose-client phase classification, and exact
profile/directory teardown evidence. Do not
change the production Windows handle or check the broad Milestone 6 Windows system-SSH item until
both architectures select the same console-independent strategy. Do not add an
Electron/startup/product importer, per-target mode wiring, fallback, tuple
@@ -1670,12 +1670,15 @@
child while retaining private output pipes, exact handle inheritance, job
ownership, and bounded cancellation. Native x64/arm64 prove the console handle
and zero-byte cooked EOF but the live client still hangs, falsifying stdin alone.
The next one-variable diagnostic retains that console stdin and replaces only
The next one-variable diagnostic retained that console stdin and replaced only
child stdout/stderr pipes with unique delete-on-close regular files under an
explicit per-stream byte ceiling, bounded polling/cancellation, incremental
post-exit replay, and exact cleanup. Build and live-test it on native x64/arm64
before considering ConPTY, another SSH client, packaging, signing, or a product
caller. POSIX retains ignored
post-exit replay, and exact cleanup. Native x64/arm64 prove those contracts, but
the live client still hangs with the same lifecycle tuple, falsifying redirected
output pipes as the remaining cause. Before selecting ConPTY or another SSH
client, the runner-only launcher must boundedly terminate its own diagnostic
child and replay an exact verbose OpenSSH trace to classify the stuck client
phase. Do not package, sign, or add a product caller. POSIX retains ignored
stdin plus <code>-n</code>; fixture-owned Windows session processes settle
before profile hives are boundedly unloaded and native profile deletion begins;
<code>MaxSessions=1</code>; interrupted upload; stale lock
@@ -7,21 +7,40 @@ internal static class OrcaSshNoInputLauncher
{
try
{
if (args.Length < 1)
int executableIndex = 0;
int diagnosticTimeoutMilliseconds = 0;
if (args.Length >= 2 && args[0] == "--diagnostic-timeout-ms")
{
Console.Error.WriteLine("Usage: orca-ssh-no-input.exe <ssh.exe> [arguments...]");
if (!Int32.TryParse(args[1], out diagnosticTimeoutMilliseconds) ||
diagnosticTimeoutMilliseconds < 100 ||
diagnosticTimeoutMilliseconds > 60000)
{
Console.Error.WriteLine("--diagnostic-timeout-ms requires 100 through 60000.");
return 2;
}
executableIndex = 2;
}
if (args.Length <= executableIndex)
{
Console.Error.WriteLine(
"Usage: orca-ssh-no-input.exe [--diagnostic-timeout-ms <milliseconds>] <ssh.exe> [arguments...]"
);
return 2;
}
string executablePath = Path.GetFullPath(args[0]);
string executablePath = Path.GetFullPath(args[executableIndex]);
if (!File.Exists(executablePath))
{
Console.Error.WriteLine("Unable to locate the SSH executable at \"{0}\".", executablePath);
return 2;
}
string[] childArgs = new string[args.Length - 1];
Array.Copy(args, 1, childArgs, 0, childArgs.Length);
return WindowsSshChildProcess.Run(executablePath, childArgs);
string[] childArgs = new string[args.Length - executableIndex - 1];
Array.Copy(args, executableIndex + 1, childArgs, 0, childArgs.Length);
return WindowsSshChildProcess.Run(
executablePath,
childArgs,
diagnosticTimeoutMilliseconds
);
}
catch (Exception error)
{
@@ -1,5 +1,6 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
@@ -18,7 +19,11 @@ internal static class WindowsSshChildProcess
private const uint OutputLimitPollMilliseconds = 50;
private const uint JobSettlementTimeoutMilliseconds = 5000;
internal static int Run(string executablePath, string[] args)
internal static int Run(
string executablePath,
string[] args,
int diagnosticTimeoutMilliseconds
)
{
Stream launcherStdout = Console.OpenStandardOutput();
Stream launcherStderr = Console.OpenStandardError();
@@ -70,7 +75,22 @@ internal static class WindowsSshChildProcess
throw LastWin32("ResumeThread failed.");
}
WaitForChildWithinOutputLimits(process.Process, outputs);
bool exited = WaitForChildWithinOutputLimits(
process.Process,
outputs,
diagnosticTimeoutMilliseconds
);
if (!exited)
{
// Why: the runner must recover verbose bytes without relying on an external
// kill that also destroys the delete-on-close diagnostic captures.
TerminateAndSettleJob(ref job);
outputs.ReplayOutputs();
throw new TimeoutException(
"SSH child reached the " + diagnosticTimeoutMilliseconds +
" ms diagnostic timeout."
);
}
uint exitCode;
if (!GetExitCodeProcess(process.Process, out exitCode))
{
@@ -177,18 +197,33 @@ internal static class WindowsSshChildProcess
}
}
private static void WaitForChildWithinOutputLimits(
private static bool WaitForChildWithinOutputLimits(
IntPtr process,
WindowsBoundedOutputFiles outputs
WindowsBoundedOutputFiles outputs,
int diagnosticTimeoutMilliseconds
)
{
Stopwatch elapsed = Stopwatch.StartNew();
while (true)
{
uint waitResult = WaitForSingleObject(process, OutputLimitPollMilliseconds);
uint waitMilliseconds = OutputLimitPollMilliseconds;
if (diagnosticTimeoutMilliseconds > 0)
{
long remaining = diagnosticTimeoutMilliseconds - elapsed.ElapsedMilliseconds;
if (remaining <= 0)
{
return false;
}
if (remaining < waitMilliseconds)
{
waitMilliseconds = (uint)remaining;
}
}
uint waitResult = WaitForSingleObject(process, waitMilliseconds);
if (waitResult == WaitObject0)
{
outputs.EnsureWithinLimits();
return;
return true;
}
if (waitResult == WaitFailed)
{
@@ -7,7 +7,7 @@ import type { SshTarget } from '../../shared/ssh-types'
import { buildSshArgs } from './system-ssh-args'
import { findSystemSsh } from './system-ssh-binary'
type DiagnosticMode = 'private-console-regular-output-launcher-no-n'
type DiagnosticMode = 'private-console-regular-output-verbose-timeout'
type DiagnosticResult = {
mode: DiagnosticMode
@@ -19,6 +19,7 @@ type DiagnosticResult = {
channelClosed: boolean
closeCode: number | null | 'not-observed'
stderrBytes: number
stderrTail: string
elapsedMs: number
}
@@ -48,19 +49,26 @@ async function runDiagnostic(): Promise<DiagnosticResult> {
if (!sshPath) {
throw new Error('Native Windows OpenSSH client is unavailable')
}
const mode: DiagnosticMode = 'private-console-regular-output-launcher-no-n'
const mode: DiagnosticMode = 'private-console-regular-output-verbose-timeout'
const startedAt = performance.now()
// Why: Win32-OpenSSH still hangs with proven console EOF plus piped output, so this diagnostic
// isolates stdout/stderr as bounded regular files without changing production transport.
const sshArgs = buildSshArgs(createTarget())
const destinationSeparator = sshArgs.indexOf('--')
if (destinationSeparator < 0) {
throw new Error('Windows OpenSSH diagnostic arguments lost the destination separator')
}
sshArgs.splice(destinationSeparator, 0, '-vvv')
const child = spawn(
launcherPath as string,
[sshPath, ...buildSshArgs(createTarget()), 'echo ORCA-SYSTEM-SSH-OK'],
['--diagnostic-timeout-ms', '6000', sshPath, ...sshArgs, 'echo ORCA-SYSTEM-SSH-OK'],
{ stdio: ['ignore', 'pipe', 'pipe'], windowsHide: true }
)
return new Promise((resolve, reject) => {
let stdout = ''
let stderrBytes = 0
let stderrTail = ''
let stdoutEnded = false
let processExit: number | null | 'not-observed' = 'not-observed'
let channelClosed = false
@@ -91,6 +99,7 @@ async function runDiagnostic(): Promise<DiagnosticResult> {
channelClosed,
closeCode,
stderrBytes,
stderrTail,
elapsedMs: performance.now() - startedAt
})
}
@@ -115,6 +124,8 @@ async function runDiagnostic(): Promise<DiagnosticResult> {
})
child.stderr?.on('data', (chunk: Buffer) => {
stderrBytes += chunk.length
// Why: retain only the diagnostic tail needed to classify the stuck client phase.
stderrTail = (stderrTail + chunk.toString('utf8')).slice(-16 * 1024)
})
child.on('exit', (code) => {
processExit = code
@@ -138,16 +149,16 @@ async function runDiagnostic(): Promise<DiagnosticResult> {
describe.skipIf(!hasLiveInput)('Windows OpenSSH no-input child-handle diagnostic', () => {
it(
'qualifies private-console EOF plus regular-file output without the Windows null-input option',
'captures the verbose client phase after a bounded private-console diagnostic timeout',
{ timeout: 15_000 },
async () => {
expect(process.platform).toBe('win32')
const privateConsoleRegularOutputLauncherNoN = await runDiagnostic()
const privateConsoleRegularOutputVerboseTimeout = await runDiagnostic()
console.log(
`ssh_windows_no_input_handle_diagnostic=${JSON.stringify({ privateConsoleRegularOutputLauncherNoN })}`
`ssh_windows_no_input_handle_diagnostic=${JSON.stringify({ privateConsoleRegularOutputVerboseTimeout })}`
)
expect(privateConsoleRegularOutputLauncherNoN.success).toBe(true)
expect(privateConsoleRegularOutputVerboseTimeout.success).toBe(true)
}
)
})