What happened?
Harness.invoke() and listModels() document timeoutMs as killing the harness after the deadline, but the shared runner sends one SIGTERM to the direct child and waits indefinitely for close. A child that ignores the signal keeps the promise pending. If the child exits but has spawned descendants, those descendants remain alive.
This means the timeout is neither bounded nor recursive.
How to reproduce
Using a custom harness backed by node:
- Run
process.on("SIGTERM", () => {}); setTimeout(() => process.exit(0), 500) with timeoutMs: 30. invoke() returned after 531 ms.
- Run a child that spawns a 60 second grandchild, then waits.
invoke() returned with timedOut: true, but the grandchild was still alive and needed an explicit SIGKILL.
Anything else?
Terminate the owned process tree, allow a documented grace period after SIGTERM, then escalate to a hard kill. Add regression tests for ignored termination signals and surviving descendants on each supported platform.
What happened?
Harness.invoke()andlistModels()documenttimeoutMsas killing the harness after the deadline, but the shared runner sends oneSIGTERMto the direct child and waits indefinitely forclose. A child that ignores the signal keeps the promise pending. If the child exits but has spawned descendants, those descendants remain alive.This means the timeout is neither bounded nor recursive.
How to reproduce
Using a custom harness backed by
node:process.on("SIGTERM", () => {}); setTimeout(() => process.exit(0), 500)withtimeoutMs: 30.invoke()returned after 531 ms.invoke()returned withtimedOut: true, but the grandchild was still alive and needed an explicitSIGKILL.Anything else?
Terminate the owned process tree, allow a documented grace period after
SIGTERM, then escalate to a hard kill. Add regression tests for ignored termination signals and surviving descendants on each supported platform.