Skip to content

Commit 4d2d7f9

Browse files
committed
test(cli): select the serve child by argv in the start signal pin
On a cold tsx transform cache the loader's `esbuild --service` process is a second child of the CLI parent and exits a moment after it. Counting every child read that as an orphaned server on the file's first boot. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
1 parent 586d7ec commit 4d2d7f9

1 file changed

Lines changed: 22 additions & 9 deletions

File tree

‎packages/cli/test/start-signal-forwarding.e2e.test.ts‎

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,17 +109,30 @@ function argvFor(command: Command, artifact: string, port: number): string[] {
109109
: ['dev', '--artifact', artifact, '--port', String(port)];
110110
}
111111

112-
/** Every pid whose parent is `ppid`, read from the process table. */
113-
function childPidsOf(ppid: number): number[] {
114-
const table = execFileSync('ps', ['-A', '-o', 'pid=,ppid='], {
112+
/**
113+
* The pids of `ppid`'s `serve` children, read from the process table — a child
114+
* whose argv carries the `serve` command token.
115+
*
116+
* ⚠️ Selected by argv, not "every child", and MEASURED to need it: on a cold
117+
* tsx transform cache the loader runs an `esbuild --service` process as a
118+
* second child of the CLI parent. It exits on its own a moment AFTER the parent
119+
* (alive at the parent's exit, gone 2 s later, 2 of 2 runs with
120+
* `TSX_DISABLE_CACHE=1`), so counting it reads as an orphaned server on the
121+
* file's first boot and on no other. The ruling's sentence is about the
122+
* `serve` child, and so is this probe. `-ww` keeps BSD `ps` from cutting the
123+
* argv at a terminal width.
124+
*/
125+
function serveChildPidsOf(ppid: number): number[] {
126+
const table = execFileSync('ps', ['-A', '-ww', '-o', 'pid=,ppid=,args='], {
115127
encoding: 'utf8',
116128
env: childEnv(),
117129
});
118130
return table
119131
.split('\n')
120-
.map((line) => line.trim().split(/\s+/).map(Number))
121-
.filter(([pid, parent]) => Number.isInteger(pid) && parent === ppid)
122-
.map(([pid]) => pid);
132+
.map((line) => line.trim().split(/\s+/))
133+
.filter(([pid, parent, ...args]) =>
134+
Number.isInteger(Number(pid)) && Number(parent) === ppid && args.includes('serve'))
135+
.map(([pid]) => Number(pid));
123136
}
124137

125138
/** Does `pid` name a live process? (`EPERM` = alive, owned by someone else.) */
@@ -225,9 +238,9 @@ async function signalParent(command: Command, signal: NodeJS.Signals): Promise<v
225238
const parentPid = parent.pid!;
226239

227240
// ── Positive controls, on the same boot, before the signal ─────────────
228-
const children = childPidsOf(parentPid);
229-
expect(children.length, `os ${command}: the probe sees no child of the parent.\n${output()}`)
230-
.toBeGreaterThan(0);
241+
const children = serveChildPidsOf(parentPid);
242+
expect(children, `os ${command}: the probe does not see exactly one serve child.\n${output()}`)
243+
.toHaveLength(1);
231244
expect(children.every(isAlive), `os ${command}: a child reads dead before the signal`).toBe(true);
232245
expect(portIsFree(port), `os ${command}: port ${port} reads free while the server is up`).toBe(false);
233246

0 commit comments

Comments
 (0)