fs/watch debounce only applies to the first event batch
Summary
I found a debounce bug in the app-server fs/watch notification path. The local debounce wrapper in app-server/src/fs_watch.rs stores the next deadline in next_allowance and initializes it only once. After the first event batch is emitted, later batches reuse that stale Instant, so subsequent filesystem changes can be emitted immediately instead of being debounced.
The upstream repository currently only allows collaborators to open pull requests, so I pushed a fix to my fork instead:
- Branch: https://github.com/chr1sc2y/codex/tree/codex/fix-fs-watch-debounce
- Compare: https://github.com/openai/codex/compare/main...chr1sc2y:codex/fix-fs-watch-debounce
- Commit: https://github.com/chr1sc2y/codex/commit/1f14b949e057b015f9f94ea91decc2757c48b1ae
Impact
The first burst of file watcher events is coalesced correctly, but later bursts may bypass the intended debounce window. In the app-server path this affects fs/changed notifications, where the debounce interval is currently 200ms.
In practical terms, after the first notification burst, clients can receive noisier and less stable fs/changed notifications than intended.
Fix in the fork
The forked branch:
- moves the debouncing wrapper into
codex-file-watcherasDebouncedWatchReceiver - resets the debounce deadline for each event batch
- uses deterministic path ordering via
BTreeSet - flushes pending paths if the watcher channel closes during a debounce window
- updates app-server
fs/watchto use the shared debounce receiver
Regression coverage
I added a unit test with a 50ms debounce interval that verifies the second event batch is still debounced:
- send
a - wait 25ms
- send
b - receive one batch
[a, b] - send
c - assert
recv()does not return within 25ms - send
d - receive one batch
[c, d]
Before the fix, step 6 can fail because the stale deadline is already in the past.
I also added coverage for flushing pending debounced paths when the sender closes.
Validation
Ran locally:
just fmt
PATH="$HOME/.cargo/bin:$PATH" just test -p codex-file-watcher
PATH="$HOME/.cargo/bin:$PATH" just test -p codex-app-server fs_watch
Results:
codex-file-watcher: 21 tests passedcodex-app-server fs_watch: 8 tests passed, 777 skipped- the
bench-smokestep run byjust testalso passed
One unrelated existing warning appeared from codex-exec-server/src/client.rs about an unused variable.