* fix(windows): retain native handles whose close is unconfirmed
CloseHandle failure no longer drops the numeric value. A still-open handle
stays owned and can be closed again; a value missing from the handle table
is dropped and never closed again; unreadable liveness blocks another close
until a probe says the value is open.
Co-authored-by: morluto <morluto@users.noreply.github.com>
* test(process): keep scratch until inherited pipes close
Lock the execFileOutput settlement contract: an inherited-pipe descendant
holds the caller's scratch root until it exits, and destroying the readers
is not process-family completion.
Co-authored-by: morluto <morluto@users.noreply.github.com>
* fix(windows): retry admitted file closes before runtime removal
A failed private-file close stays on the runtime that opened it. Runtime
removal closes that same wrapper before the tree walk, including when the
read that opened the file has already returned.
Co-authored-by: morluto <morluto@users.noreply.github.com>
* fix(windows): resume a close after part of the owner is released
A runtime whose directory handle is already released continues with its
parent handles instead of walking that invalid root. A process whose
settlement was already observed retries process close alone, without kill
or poll against the released job.
Co-authored-by: morluto <morluto@users.noreply.github.com>
* test(process): signal only an owned inherited-pipe descendant
Skip the descendant SIGKILL after pipe settlement. On an earlier failure,
signal only when the process command line still contains that test's path.
Co-authored-by: morluto <morluto@users.noreply.github.com>
* fix(windows): do not close a handle again after a failed close
An open liveness probe can observe a reused numeric value. After CloseHandle
fails, drop the value only when the handle table no longer contains it, and
do not call CloseHandle on that number again.
Co-authored-by: morluto <morluto@users.noreply.github.com>
* fix(windows): stop using a close-blocked pipe for I/O
A broken pipe whose CloseHandle fails stays owned, but its numeric value
may have been reused. Later polls and stdin writes treat that pipe as
ended instead of reading or writing the number.
Co-authored-by: morluto <morluto@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
Refs #4502, #4506, #4508, #4518, #4522, #4526.
Retain snapshot, endpoint, launch rollback and compiler cleanup owners. Join request finalization and cleanup cancellation. Preserve bounded compiler output and original byte counts through capture and Ghidra failures. Normalize execFile cancellation once at its producer.
Validation includes real macOS Ghidra TCP MCP success workflows, controlled Node compiler/process fault fixtures, focused regressions and static checks. Native Swift compiler faults, Windows execution and stdio transport were not verified. Broader startup primary classification in #4517 and native adapter propagation remain unfinished.
Use Android 8-compatible batched find/stat with NUL-framed names and raw symlink targets. Preserve malformed or invalid UTF-8 records as partial evidence with exact source bytes. List hidden direct children without synthetic navigation entries.
Share process byte capture with UTF-8 callers and screenshots, removing duplicate timeout and cancellation supervision. Verified 35 focused process, ADB, CLI and MCP tests; compatibility checked against AOSP Oreo sources. Live Android device/emulator verification was not run.
Node's execFile timeout contract differs by supported runtime: Node 22
and 24 accept any non-negative integer (their timers clamp anything
above 2^31-1 to 1 ms), while Node 26 caps it at Number.MAX_SAFE_INTEGER.
Mirroring either left stoppable and plain commands disagreeing on one
runtime. Validate a safe non-negative integer before spawning in both
modes, so every supported runtime gives the same answer.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since a stoppable command keeps its own deadline, execFile no longer
validated its timeout. A negative, NaN, fractional or infinite value
then started the command with no deadline (or one setTimeout coerced)
instead of failing with ERR_OUT_OF_RANGE as execFile does.
Apply execFile's unsigned-integer contract before spawning.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
execFileOutput forwarded stopSignal only together with an AbortSignal, so
a caller with just a timeout (the native UI helper's swiftc build) still
got Node's SIGTERM. When the timeout did send the stop signal, nothing
killed a process that ignored it, and a process that exited cleanly
after the signal resolved as a success.
A stoppable command now owns its timeout: it sends the stop signal,
kills the process after the grace period, and rejects with ETIMEDOUT
and killed: true.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cancelling the macOS process-ownership helper build left swiftc object files
in TMPDIR. Node's execFile sends SIGTERM on abort and rejects before the
child exits, and swift-driver runs each frontend job in its own process
group: on SIGTERM the driver exits while a job keeps writing, so REA removed
its root while objects were still being written outside and inside it.
Add an opt-in `stopSignal` to execFileOutput that stops the process with the
caller's signal on abort or timeout and settles only after it exits. Swift
helper builds stop swiftc with SIGINT, on which swift-driver stops its jobs
and deletes their objects, and point TMPDIR inside the root cleanup removes.
Fixes#1173
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>