Skip to content

runtime-host-local-target-activation's 50 ms settlement deadline races child process startup, flaky under CI load #4466

Description

@Astro-Han

a parent deadline terminates and waits for a target activator with a hung settlement in packages/cli/src/__tests__/runtime-host-local-target-activation.test.ts gives a 50 ms settlement budget to a child that must first boot Node and load an ESM module graph. Under CI load the child loses that race, and the test fails on an assertion about a file the child never finished writing.

Observed on the heavy lane of CI run 33487585852:

✖ a parent deadline terminates and waits for a target activator with a hung settlement
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  '' !== 'entered'

Why it races

The test writes a small activator script, spawns it, and gives the parent a 50 ms deadline:

{ settlementTimeoutMs: 50 }

The child's readRecord stub is what writes the file the assertion reads:

readRecord: () => {
  writeFileSync(readEnteredPath, 'entered');
  return new Promise(() => { setInterval(() => {}, 1_000); });
}

Reaching that line costs a process spawn, Node startup, and resolution of the runtime-host-installed-update-activator module graph. On an idle machine that fits inside 50 ms; on a runner executing three workspace suites concurrently it does not, and the parent SIGKILLs the child first.

The observed value is '' rather than ENOENT, which points at the kill landing between the file's creation and its write rather than before writeFileSync was entered at all — a narrower window, same cause.

Confirmed locally: 8 consecutive runs of this file alone all pass. The failure needs the contention.

Why it is worth more than a retry

A step failure ends the job, so everything after it is skipped. In run 33487585852 this test took down Run Runtime Host tests and all three forward-roll steps with it, which meant #4427 merged its own CI evidence only after a rerun. A flaky test this early in a serial job silently withdraws coverage from everything behind it.

Direction

The deadline is being asked to do two jobs: prove the parent gives up on a hung settlement, and outlast an unrelated process launch. Separating them keeps the assertion and drops the race — wait for read-entered to appear as the synchronisation point proving the child entered readRecord, and only then let the settlement deadline expire. The 50 ms budget can stay once it no longer has to cover startup.

Same class as #2221 and #4094: a fixed wall-clock budget in a CLI test that holds on a developer machine and not under CI contention.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingeffort/SUnder 100 readable lines

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions