Skip to content

agent-memory-sync: document macOS fs.watch arming race, drop retry machinery (f876dff6) - #110

Merged
LanNguyenSi merged 2 commits into
masterfrom
fix/f876dff6-watch-trigger-stall
Aug 17, 2026
Merged

agent-memory-sync: document macOS fs.watch arming race, drop retry machinery (f876dff6)#110
LanNguyenSi merged 2 commits into
masterfrom
fix/f876dff6-watch-trigger-stall

Conversation

@LanNguyenSi

Copy link
Copy Markdown
Owner

Summary

Closes task f876dff6 (qw10-fu-01, watch trigger FS-event stall) as a knowledge deliverable, per operator decision: root cause documented, no behavior change (one test-helper stdio hardening), no retry machinery.

  • Root cause CONFIRMED with isolated repro (implementer + independent reviewer, outside the test harness): on macOS, Node's fs.watch() (which chokidar 4.x uses directly: no fsevents dependency, no polling default) permanently loses a write issued within ~1ms of the watch arming: 0/10 events at 0ms delay, 10/10 at >=1ms, idle and under 12-worker load. Matches open Node issue Not possible to know when fs.watch has started on macOS nodejs/node#52601. The other suspects were falsified: no atomic-rename writes in this suite, no fsevents in play, and parent-side stderr-pipe read delay measured at p99 9ms / max 11ms over 600 samples under load, which cannot blow a 90s budget.
  • The motivating symptom (30-40% stall under the documented 10-worker load scenario, measured 2026-08-16 in the eb798875 review) did NOT reproduce on current master during verification: base ran 5/5 green (3 idle + 2 under the documented load scenario, interleaved matched control). A retry-based test-side workaround was built, reviewed, found to introduce a deterministic regression (exit listener registered after a long await; Node never replays 'exit': fast ticks then hang to the 90s deadline), and was deliberately dropped rather than repaired, since it had no measurable benefit to justify its complexity.
  • What ships: the corrected ready-event comment in src/commands/watch.ts (comment-only, verified zero non-comment lines changed), a compact root-cause header in tests/helpers/watch-process.ts (mechanism + threshold + non-repro measurement + refuted stderr-pipe theory + the exit-listener ordering constraint as a warning for future edits), spawnWatch child stdout switched from an unread pipe to 'ignore' (an over-64KB stdout child would otherwise present as exactly this phantom-stall class), and a factual CHANGELOG note.

Verification

  • Typecheck clean; 4 watch-spawning integration files 33/33; unit suite 160/160; full coverage gate green in 2 of 3 runs; the single failure carries the exact pre-existing stall signature this task documents (present at merge base, out of scope to paper over with retries).
  • Reverted files verified 0-line-diff against base 47e2d60; applyTriggerWithRetry/deleteRetrySafe grep 0 repo-wide.

Task: f876dff6 (agent-memory). Orchestrator lane, batch 11; investigation history preserved in the branch commits and .ai run log.

Lan Nguyen Si added 2 commits August 16, 2026 23:06
…n test triggers (partial)

Root-caused the pre-existing watch-trigger stall (agent-tasks f876dff6): a
bare fs.watch() on macOS can silently and permanently miss a write issued
immediately after the watch reports armed (nodejs/node#52601), independent
of chokidar and independent of CPU load - proven via an isolated repro
bypassing both. Fix lives on the test side since no deadline size can
recover a lost event: applyTriggerWithRetry re-applies a trigger edit until
a progress signal confirms it landed, budgeted well under the unmodified
90s inactivity deadline. deleteRetrySafe makes the two delete-based
triggers safe to retry.

This measurably fixes the proven mechanism, but an interleaved
matched-control run under the full documented load scenario still failed
repeatedly on two specific tests on both the fixed and unmodified arms.
One failure's raw output showed evidence of a second, different mechanism
this change does not fix: the parent test-runner's own delayed stderr-pipe
read under heavy contention, which can make an already-completed tick look
like a stall. Left open with instrumentation notes in
tests/helpers/watch-process.ts for a follow-up.
…e docs only (f876dff6)

Review found the retry helpers added in 2cb9173 (applyTriggerWithRetry,
deleteRetrySafe) introduce a deterministic regression for no measurable
benefit, and that the stall symptom they targeted does not currently
reproduce against the merge base. Revert tests/helpers/watch-process.ts,
tests/helpers/cli.ts, tests/integration/watch-mirror-delete.test.ts and
tests/integration/watch-restore.test.ts to base (47e2d60); drop
tests/unit/watch-process-trigger-retry.test.ts.

Keep only: the corrected ready-event comment in src/commands/watch.ts,
tightened to the reviewer-verified measurement (macOS fs.watch() can
permanently lose a write <1ms after arming; 0/10 at 0ms vs 10/10 at
>=1ms, idle and under load; nodejs/node#52601); a compact root-cause
header in tests/helpers/watch-process.ts covering that mechanism, the
2026-08-16/17 non-repro measurement, the ruled-out stderr-read-delay
theory, and the exit-listener ordering constraint; and a one-line
hardening (spawnWatch's child stdout is now "ignore" instead of an
unread "pipe", which could otherwise back up past 64KB and look like an
unexplained stall). CHANGELOG entry rewritten to match: no "fixed"
claim, no behavior change beyond the stdout hardening.
@LanNguyenSi LanNguyenSi added review:tests-pass merge-approval gate prerequisite review:checklist-complete merge-approval gate prerequisite review:comments-resolved merge-approval gate prerequisite review:scope-matches-task merge-approval gate prerequisite review:evidence-logged merge-approval gate prerequisite labels Aug 17, 2026
@LanNguyenSi
LanNguyenSi merged commit cd0a0d2 into master Aug 17, 2026
5 of 7 checks passed
@LanNguyenSi
LanNguyenSi deleted the fix/f876dff6-watch-trigger-stall branch August 17, 2026 04:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review:checklist-complete merge-approval gate prerequisite review:comments-resolved merge-approval gate prerequisite review:evidence-logged merge-approval gate prerequisite review:scope-matches-task merge-approval gate prerequisite review:tests-pass merge-approval gate prerequisite

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant