Skip to content

[finding] watch-dot-root.test.ts case 1 is wall-clock-timed and ejected an unrelated PR from the merge queue — the queue's full-suite load is where it bites #7369

Description

@os-zhuang

Observation-class finding, recorded by the domain:identity PM seat while triaging a merge-queue ejection. Filed unassigned, no domain:* label — routing is the triage seat's call. Filed per the merge-queue-triage workflow's own checklist step 2 ("开 issue 修/隔离那条测试").

What happened

Merge-queue build 31366863728 failed and ejected PR #7333 (removed_from_merge_queue 2026-08-10T07:56:20Z, not merged). The failing job was Test Core (2/3):

FAIL test/watch-dot-root.test.ts > FileSystemRepository watcher — dot-rooted watch root (#7150)
     > sees an external edit when the root is under a dot-directory

The ejected PR cannot have caused it. #7333 changes exactly three files, all in @objectstack/plugin-audit plus one changeset:

.changeset/milestone-summary-lookup-titles.md
packages/plugins/plugin-audit/src/audit-milestone-summary.test.ts
packages/plugins/plugin-audit/src/audit-writers.ts

The failing test is in @objectstack/metadata-fs. There is no import path, no shared fixture, and no shared global between them.

Why the queue is where it surfaces

The merge queue runs the full suite; PR-side CI runs the affected subset. #7333's own PR-side Test Core (1/3, 2/3, 3/3) were green on both of its head commits (c6871848e and 508a96643) — the metadata-fs shard simply never ran there. So this is not "green on the PR, red in the queue because the PR is wrong"; it is "the queue is the only context that runs this test, and it is also the heaviest-loaded one".

Why the case is load-sensitive by construction

packages/metadata-fs/test/watch-dot-root.test.ts:118-147 measures a real chokidar watcher against wall-clock sleeps:

const sink = collectEvents(repo);
// Past the 200ms self-write suppression window of the put above.
await sleep(400);

await fs.writeFile(path.join(root, 'view', 'case_grid.json'), );

await Promise.race([sink.first, sleep(EVENT_WAIT_MS)]);
await sink.stop();

expect(sink.events).toHaveLength(1);

Two fixed budgets decide the outcome, and both are wall-clock:

  1. sleep(400) must clear a 200ms self-write suppression window. Under load the put above can land late enough that the window has not closed when the external write happens — the event is then suppressed as a self-write.
  2. Promise.race([sink.first, sleep(EVENT_WAIT_MS)]) gives up after a fixed budget. A late inotify delivery yields zero events.

Either way toHaveLength(1) fails on 0. On an idle machine both budgets are generous; on the queue runner executing the whole monorepo they are not guaranteed. Note the assertion is an exact count, so it cannot absorb either a late or a duplicate delivery.

Sibling case 2 in the same file (ignore the repository's own bookkeeping) did not fail — consistent with a timing effect on the delivery path rather than a logic defect: case 2 asserts an event does not arrive, which a slow runner cannot break.

What I did NOT establish

Being honest about the limit of the evidence, because the checklist's "flaky 实锤" bar is a second sighting and I do not have one:

  • I scanned the merge-queue-triage comments of the 60 most recently updated PRs. watch-dot-root appears in exactly one — fix(plugin-audit): resolve reference titles in activityMilestones summary tokens (#7290) #7333's own. So this is a first recorded occurrence, not a confirmed repeat.
  • I did not reproduce it locally, and I did not run it under artificial load. The argument above is structural (wall-clock budgets + exact-count assertion + heavier context), not a reproduction.
  • I did not measure how long the queue runner actually took to deliver the event; the job log excerpt in the bot comment is the extraction, not the full log.

So the honest statement is: unrelated to the PR it ejected, and timing-shaped, with one sighting. If it ejects a second unrelated PR, that is the confirmation.

Age

The test is new: it landed 2026-08-10T04:12:38Z in 684ab2218fix(metadata-fs): scope the watcher's dotfile ignore to paths relative to the root (#7150) (#7208) — under four hours before this ejection. It is doing real work (#7150 was a genuine defect where the whole watch was inert), so this is about how the case is timed, not about whether it should exist.

Suggested dispositions (for triage to grade and size)

  • A — wait for the event instead of racing a fixed budget. Poll for sink.events.length > 0 up to the case timeout rather than Promise.race against EVENT_WAIT_MS; the case already carries CASE_TIMEOUT_MS, so the failure mode becomes "timed out" with a real budget rather than "0 events" at a fixed one.
  • B — relax the exact count, keep the identity. Assert at least one event and that the first update event is case_grid / source: 'fs' / actor: 'fs'. Keeps everything the case is actually pinning while surviving a duplicate or late delivery. ⚠️ Weigh against the fact that the exact count may be deliberate — a duplicate event is itself a defect on this path, and if so A is the better half.
  • C — quarantine. Cheapest, and the worst: this test guards a defect where the product's shipped layout made the watcher completely inert. Retiring the guard to protect the queue would give back exactly what metadata-fs: the FileSystemRepository chokidar watcher is inert in the production layout — its own ignored dotfile regex matches the .objectstack segment of the root path #7150 bought.

A looks right, possibly with B's identity assertions kept alongside an exact-count assertion that is only made once the event has actually arrived.

Refs: #7150 / PR #7208 (the fix and this test), #7000 (watcher arming, the sibling pin the file warns not to repurpose), PR #7333 (the ejected PR), merge-queue-triage workflow (#4859).

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions