Skip to content

watch-dedup: "starts both the tsx source CLI and a normal built CLI child" fails intermittently, and leaves a full build in the package root when interrupted #567

Description

@KageBinary

ix-cli/src/cli/__tests__/watch-dedup.test.ts:99.

What was seen

Five failures on 2026-09-01, all of canonical watch refresh > starts both the tsx source CLI and a normal built CLI child:

  • once inside a full vitest run (1 failed / 1508 passed)
  • then four consecutive times running that file alone, in the same worktree, against clean main (793c56b) with local changes stashed

What could not be reproduced

In a fresh worktree at the same commit, the test passed 15/15 on every attempt — roughly twelve runs, including six while a full vitest run was executing concurrently to load the machine.

I could not capture the assertion message before the failing worktree was removed, which is the main gap here. If anyone hits this, please paste the failure output — the two expect(status, stderr) assertions carry the child's stderr and would name the cause immediately.

Hypotheses tested and ruled out

hypothesis result
Disk pressure (machine was at 97%, 1.6G free) No. The build the test performs is only 1.3M / 89 files.
Leftover .watch-child-runtime-* debris from an earlier interrupted run No. Recreated the debris (including a full dist inside it) and the test still passed 15/15.
The update notice leaking onto stdout and breaking the --version assertion No. With a fresh IX_HOME seeded exactly as the test seeds it, stdout is exactly 0.10.5 and nothing else.

Why this test is more exposed than most

The test body does real work in the environment rather than asserting on pure functions like the rest of the file:

  1. spawns src/cli/main.ts under tsx and asserts --version on stdout
  2. shells out to a full tsc -p tsconfig.build.json build and asserts build.status === 0 (:130-143)
  3. spawns the built CLI and asserts on --help

Any environmental problem in step 2 or 3 fails the test for a reason unrelated to what it is checking, which is whether the child process starts.

A separate, definite defect worth fixing either way

:109 creates its temp dir inside the package root:

const tempRoot = fs.mkdtempSync(path.join(packageRoot, ".watch-child-runtime-"));

Cleanup is in a finally, which does not run if the process is killed. An interrupted run therefore leaves a complete dist build sitting in the working tree — I hit exactly this and found .watch-child-runtime-ZLHEp7/ as an untracked directory afterwards. It is also not in .gitignore, so it shows up in git status and can be committed by accident.

Moving tempRoot to the OS tmpdir fixes both, and costs nothing — nothing in the test requires it to be under packageRoot (packageRoot is already passed explicitly as cwd to each spawnSync).

Found while verifying #566; not caused by it.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions