From: Koji Nakamaru Date: Sat, 28 Feb 2026 17:02:11 GMT Subject: Re: [PATCH] fsmonitor: fix two bugs in watchman hook retry path Message-ID: In-Reply-To: On Mon, Jan 26, 2026 at 10:33 AM Paul Tarjan via GitGitGadget wrote: > > From: Claude > > The is_work_tree_watched() function in fsmonitor-watchman.sample has > two bugs: > > 1. Wrong variable in error check: After calling watchman_clock(), the > result is stored in $o, but the code checks $output->{error} instead > of $o->{error}. This means errors from the clock command are silently > ignored. > > 2. Double output violates protocol: When the retry path triggers (the > directory wasn't initially watched), output_result() is called with > the "/" flag, then launch_watchman() is called recursively which > calls output_result() again. This outputs two clock tokens to stdout, > but git's fsmonitor v2 protocol expects exactly one response. > > Fix #1 by checking $o->{error} after watchman_clock(). > > Fix #2 by removing the recursive launch_watchman() call. The "/" > "everything is dirty" flag already tells git to do a full scan, and > git will call the hook again on the next invocation with a valid clock > token. > > Apply the same fixes to the test helper scripts in t/t7519/. > > Signed-off-by: Paul Tarjan > --- > fsmonitor-watchman: fix variable reference and remove redundant code > > The is_work_tree_watched() function in fsmonitor-watchman.sample has two > bugs: > > 1. Wrong variable in error check: After calling watchman_clock(), the > result is stored in $o, but the code checks $output->{error} instead > of $o->{error}. This means errors from the clock command are > silently ignored. > > 2. Double output violates protocol: When the retry path triggers (the > directory wasn't initially watched), output_result() is called with > the "/" flag, then launch_watchman() is called recursively which > calls output_result() again. This outputs two clock tokens to > stdout, but git's fsmonitor v2 protocol expects exactly one > response. > > Fix #1 by checking $o->{error} after watchman_clock(). > > Fix #2 by removing the recursive launch_watchman() call. The "/" > "everything is dirty" flag already tells git to do a full scan, and git > will call the hook again on the next invocation with a valid clock > token. > > Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2180%2Fptarjan%2Fclaude%2Ffix-watchman-query-bug-sfbIw-v1 > Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2180/ptarjan/claude/fix-watchman-query-bug-sfbIw-v1 > Pull-Request: https://github.com/git/git/pull/2180 > > t/t7519/fsmonitor-watchman | 1 - > t/t7519/fsmonitor-watchman-v2 | 5 +---- > templates/hooks/fsmonitor-watchman.sample | 5 +---- > 3 files changed, 2 insertions(+), 9 deletions(-) > > diff --git a/t/t7519/fsmonitor-watchman b/t/t7519/fsmonitor-watchman > index 264b9daf83..907bd1533c 100755 > --- a/t/t7519/fsmonitor-watchman > +++ b/t/t7519/fsmonitor-watchman > @@ -109,7 +109,6 @@ sub launch_watchman { > close $fh; > > print "/\0"; > - eval { launch_watchman() }; > exit 0; > } > > diff --git a/t/t7519/fsmonitor-watchman-v2 b/t/t7519/fsmonitor-watchman-v2 > index 14ed0aa42d..2c59120c3d 100755 > --- a/t/t7519/fsmonitor-watchman-v2 > +++ b/t/t7519/fsmonitor-watchman-v2 > @@ -141,15 +141,12 @@ sub is_work_tree_watched { > # Watchman query just to get it over with now so we won't pay > # the cost in git to look up each individual file. > my $o = watchman_clock(); > - $error = $output->{error}; > + $error = $o->{error}; > > die "Watchman: $error.\n" . > "Falling back to scanning...\n" if $error; > > output_result($o->{clock}, ("/")); > - $last_update_token = $o->{clock}; > - > - eval { launch_watchman() }; > return 0; > } > > diff --git a/templates/hooks/fsmonitor-watchman.sample b/templates/hooks/fsmonitor-watchman.sample > index 23e856f5de..21c81b6804 100755 > --- a/templates/hooks/fsmonitor-watchman.sample > +++ b/templates/hooks/fsmonitor-watchman.sample > @@ -142,15 +142,12 @@ sub is_work_tree_watched { > # Watchman query just to get it over with now so we won't pay > # the cost in git to look up each individual file. > my $o = watchman_clock(); > - $error = $output->{error}; > + $error = $o->{error}; > > die "Watchman: $error.\n" . > "Falling back to scanning...\n" if $error; > > output_result($o->{clock}, ("/")); > - $last_update_token = $o->{clock}; > - > - eval { launch_watchman() }; > return 0; > } > > > base-commit: 68cb7f9e92a5d8e9824f5b52ac3d0a9d8f653dbe > -- > gitgitgadget > Although I don't have much experience with the Watchman backend, the fixes look correct to me. One suggestion: it seems we can make the code even cleaner by removing $retry and its associated logic, as they appear to be no longer necessary after these changes. -- Koji Nakamaru