Found while reviewing #893. Pre-existing on main, unrelated to that branch.
What happens
WatchBatchProcessor._relative_path (code_review_graph/incremental.py:2143) walks up to the nearest existing ancestor:
existing = candidate
while not existing.exists() and existing != lexical_root:
existing = existing.parent
Path.exists() propagates OSError for a path the OS cannot even stat. A component longer than NAME_MAX is the easy case:
>>> Path('/tmp/' + 'a'*300 + '/x.py').exists()
OSError: [Errno 63] File name too long
That escapes _relative_path, is caught by process()'s except BaseException, sets self.failure, and raise_if_failed() ends the watch loop with RuntimeError: watch update failed. The watcher exits; under the daemon it is restarted (with backoff), pays for a full initial update, and dies again the next time the same event arrives.
Why it matters
Any tool that writes a too-long name inside a watched repo — a build cache, a generated fixture, a test that constructs deep paths — takes the watcher down. collect_all_files already guards for exactly this at incremental.py:1054-1060 ("Skip paths that would exceed OS filename limits"), so the concern is acknowledged elsewhere in the codebase; the watch path just doesn't have it.
Suggested fix
Guard the exists() walk (and the realpath/resolve calls near it) so an unstattable path is dropped like any other irrelevant event rather than killing the loop. Reuse the length check from collect_all_files if that reads better than catching OSError.
Reproduction
macOS, Python 3.13. Any watched repo; write a file whose name exceeds 255 bytes in a watched directory and the loop exits with watch update failed.
Found while reviewing #893. Pre-existing on
main, unrelated to that branch.What happens
WatchBatchProcessor._relative_path(code_review_graph/incremental.py:2143) walks up to the nearest existing ancestor:Path.exists()propagatesOSErrorfor a path the OS cannot even stat. A component longer thanNAME_MAXis the easy case:That escapes
_relative_path, is caught byprocess()'sexcept BaseException, setsself.failure, andraise_if_failed()ends the watch loop withRuntimeError: watch update failed. The watcher exits; under the daemon it is restarted (with backoff), pays for a full initial update, and dies again the next time the same event arrives.Why it matters
Any tool that writes a too-long name inside a watched repo — a build cache, a generated fixture, a test that constructs deep paths — takes the watcher down.
collect_all_filesalready guards for exactly this atincremental.py:1054-1060("Skip paths that would exceed OS filename limits"), so the concern is acknowledged elsewhere in the codebase; the watch path just doesn't have it.Suggested fix
Guard the
exists()walk (and therealpath/resolvecalls near it) so an unstattable path is dropped like any other irrelevant event rather than killing the loop. Reuse the length check fromcollect_all_filesif that reads better than catchingOSError.Reproduction
macOS, Python 3.13. Any watched repo; write a file whose name exceeds 255 bytes in a watched directory and the loop exits with
watch update failed.