Lessons — dev-server watcher & tick narrowing
Before quarantining ANY flake in this area, apply the three rules (zudo-test-wisdom → Deflaking Recipe → "The three rules"):
- "Flaky" is not a diagnosis — it is an admission you have not diagnosed it yet. Prove it first: diff the product's INPUT on a failing run vs a passing run. Identical inputs → test bug. Different inputs → product bug, stop and fix the product.
- "It's OK, it's just flaky" is never acceptable — every accepted flake devalues red, until a real bug that fails 30% of the time is indistinguishable from noise.
- If a test seems irreducibly probabilistic, you are asserting the wrong thing — assert the invariant, not a sampled point value.
The entry below is the case that produced those rules: this repo's quarantine pipeline was one step away from burying a live macOS product bug behind an immaculate paper trail.
2026-07-13 — a "flaky test" was a real macOS product bug (issue #1581 / PR #1582)
What we set out to do
Triage e2e_out_of_root_edit_narrows_rerender_and_discovers_new_entry, which failed
~2-3 of every 4 runs on macOS and passed deterministically on ubuntu CI. The issue
proposed two possibilities and demanded one be ruled in: (1) test-harness timing, or
(2) a genuine macOS product gap.
Approach we tried first
The issue's own "Recommended remediation" — written by an agent correctly following
root CLAUDE.md's flaky: quarantine pipeline — said: tag the test
#[ignore = "flaky: <url>"], add a crates/CLAUDE.md manifest row, add it
to exam.yml's quarantine-heavy filterset, investigate later.
Following that would have buried a real bug behind a paper trail that looked responsible. Quarantine suspends PRODUCT coverage, and the product was the thing that was broken.
Why it went wrong (root cause)
Two independent structural facts, neither visible from the test:
- macOS FSEvents nondeterministically varies the SHAPE of the input. An in-place
edit of an EXISTING content file is sometimes delivered as
ChangeKind::Createdinstead ofModified(zfb_watcher::merge_kindkeeps a pendingCreatedover a coalescedModified). The orchestrator's strictmodified_only_contentgate requires the tick be exclusively in-placeModifiedcontent edits, so a coalescedCreatedsetsfan_out_safe = false→compute_tick_narrowingreturnsOff→ FULL FAN-OUT: every route re-rendered and re-stamped on disk. - The guard that was supposed to absorb (1) was DEAD CODE. #1058 already added a
Created→Modifiednormalization for exactly this FSEvents artifact — but keyed it ongraph.consumers_of(path)being non-empty. No collection entry has aDepKind::Contentedge on a cold boot:seed_boot_module_edgeswrites onlyDepKind::Moduleedges, and the dev server's ONLYDepKind::Contentwriter ismake_discovery_hook, which fires only for newly-CREATED files. So the normalization could never fire for a pre-existing entry.
The bug is perf-only (bytes still end up correct), which is why it survived: no
test asserted narrowing on disk until this one did, under ZFB_DEV_EAGER=1.
It is not out-of-root-specific — in-root collections lose narrowing on macOS too.
What worked instead
A session-live KnownContentEntries registry on GranularityPolicy (same
shared-interior-mutability shape as the existing RawImportInvalidation), seeded at
boot from the collection MEMBERSHIP walk and extended by discovery. The normalization
consults it; #1058's graph check stays as a secondary source (a warm persisted graph
CAN restore Content edges).
Scoped to the normalization ONLY. See the PageSelection::All trap below for why.
Watch for next time
If a dev-server test fails intermittently on macOS but never on ubuntu, suspect the PRODUCT, not the test. Run with
ZFB_DEV_TIMING=1and diff the tick line between a failing and a passing run.orchestrator.rs's own comment names the smoking gun: "anarrowing=falseline whose kinds include aCreatedfor an already-known content file." This takes minutes and is the first thing to do — before any quarantine.[zfb-timing] tick(): kinds=[alpha.mdx:Created] eager_hint=true fan_out_safe=falseIf you are about to narrow
dirty_pages, STOP. An unknown content path trips the planner'sPageSelection::Allsentinel, and that over-broad fallback is currently the ONLY thing re-rendering AGGREGATE pages (a post index listing every entry, tag pages, pagination) on a content edit. Narrowing it without first deriving authoritative aggregate/tag/pagination provenance silently under-renders them — trading a perf bug for a correctness bug. There is NO aggregate-page regression test in this repo. Write one FIRST. Tracked in issue #1583.graph.add_node()is not a shortcut for "make the graph know this path." It makesconsumers_ofreturnSome(vec![])(known-but-unused). Check what the planner does with an empty consumer set before relying on it.If you add a guard/normalization keyed on a registry, assert the registry is actually POPULATED in the scenario you are guarding. #1058's guard was correct in shape and dead in practice for two releases. A guard that can never fire is worse than no guard: it reads as coverage.
The watcher can report a created DIRECTORY whose children never surface as individual events. Never register the watcher's raw event paths as your source of truth for collection membership — re-walk. (Caught by codex review; would have reintroduced the full fan-out one directory-create later.)
A
Removedmust purge registry state BEFORE anyCreatednormalization reads it. The watcher can batch a removed directory and aCreatedbeneath it into ONE tick, so purging afterwards is too late and a genuine delete→recreate would skip discovery.#[cfg(feature = "embed_v8")]sitting between a doc comment and itsfnis a footgun. Insert a new function into that gap and the gate silently transfers to your function, leaving the original ungated. It still compiles with default features — only the no-v8 lane catches it.pnpm b4pushdoes NOT run the no-v8 lane (it is aB4PUSH_FULL=1step). A green b4push is NOT sufficient before pushing anything that touches anembed_v8cfg boundary. Runcargo check --no-default-features -p zfb --testsby hand, or it costs a CI round-trip.
Would-skip-if-redoing
- Reading the FSEvents/
notifyinternals and theorising about directory-granularity event coalescing. The issue's hypothesis (candidate-set broadening) was wrong, and ~an hour went into reasoning about it from source.ZFB_DEV_TIMING=1— instrumentation that already existed, documented in a comment right next to the bug — answered it in one run. Instrument before theorising. - The initial theory that the handshake's
__warmup-N.mdxfiles were the whole story. They were a real second defect, but ablation proved they were not the primary cause.
2026-07 — a SECOND dynamic-watch registry: css_mirror_roots (epic #1799, issues #1801/#1802/#1805)
The watcher API surface grew a second dynamic-registration channel alongside the
file-parent watch_additional_files/dynamic_dependency_paths() pair this file
already covers: Watcher::sync_recursive_dir_watches (zfb-watcher, #1801) plus a
css_mirror_roots registry on RawImportInvalidation/GranularityPolicy, exposed via
css_mirror_root_paths() (zfb-build, #1802). Both channels are reconciled from the
SAME orchestrator.rs function, register_dynamic_dependency_watches, and both reuse
the SAME watch-extra registered: ZFB_DEV_TIMING signal — but they are watching for
structurally different things:
watch_additional_files/dynamic_dependency_paths()— non-recursive, file-parent watches for out-of-root?raw/worker/plain-module import targets discovered by the browser pipeline (#1678/#1710/#1711).sync_recursive_dir_watches/css_mirror_root_paths()— recursive-directory watches forzfb_build::SiblingMirrorPlanmirror roots (tsconfig/plugin alias claims, computed bybuild_default_css_payload_with_source_planon EVERY CSS-triggering tick, regardless of whether Tailwind is enabled or anything actually imports the alias target).
Watch for next time: a sibling directory can be claimed by BOTH channels
simultaneously (e.g. this repo's dev_sibling_watch_1678_e2e.rs fixture's sub/shared:
it is both a ?raw/worker/plain-module import target AND carries a tsconfig alias
pointing at itself). If you write a confirm-e2e for ONE of the two channels reusing a
sibling directory already covered by the OTHER, the test proves nothing — it will keep
passing even with the channel under test fully reverted, because the other channel
already keeps the directory watched. e2e_dev_sibling_tailwind_utility_class_refreshes_served_css
(issue #1805) sidesteps this by using a fixture whose sibling is reached ONLY through
the tsconfig alias — no import touches it — verified by actually reverting
sync_recursive_dir_watches's call site and confirming the test times out on
wait_for_watch_extra before restoring it. When adding a new dynamic-watch confirm-test,
check whether your chosen sibling path is already claimed by a channel you are not
trying to test.