Skip to content

mise test:init: Dev server child processes (tsx watch) not terminated after test completion #667

@2chanhaeng

Description

@2chanhaeng

Summary

When running mise test:init, the dev server processes spawned during hydration tests (specifically tsx watch and its child processes like dotenvx) are not fully terminated after each test case completes. Over the course of a full test run (560+ cases), orphaned processes accumulate and gradually slow down the system.

Problem

The current implementation in packages/init/src/test/server.ts uses @david/dax to spawn the dev server process. When the test completes, it calls CommandChild.kill("SIGKILL") to terminate the process. However, this only kills the direct child process (npm/pnpm), not the entire process tree.

The typical process tree for frameworks using tsx watch (bare-bones, express, hono, elysia) looks like:

dax shell → npm/pnpm → sh → dotenvx → tsx watch → node app

Killing only the top-level dax process leaves dotenvx, tsx watch, and the node application running as orphans.

Root Cause

@david/dax executes commands through its own JS-based shell (deno_task_shell), which does not expose process PIDs or support process group management. The CommandChild.kill() method sends a signal through dax's internal KillSignal abstraction, which cannot propagate to the full OS process tree.

This is a known limitation — see dsherret/dax#351, where the maintainer confirmed that PID exposure is not straightforward due to dax's architecture.

Solution

Replace @david/dax process spawning with node:child_process.spawn() using detached: true to create a new process group. On cleanup, use process.kill(-child.pid, 'SIGKILL') to terminate the entire process group, ensuring all descendant processes (including tsx watch, dotenvx, etc.) are killed.

Key changes in packages/init/src/test/server.ts:

  • Replace @david/dax $ spawn with child_process.spawn({ detached: true })
  • Use Readable.toWeb() to convert Node.js streams to Web ReadableStream (for .tee() compatibility)
  • Kill entire process group via process.kill(-pid, 'SIGKILL') in cleanup

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions