Subject: [PATCH] fsmonitor: fix two bugs in watchman hook retry path

From: "Paul Tarjan via GitGitGadget" <[email-masked]>
Date: Mon, 26 Jan 2026 01:33:22 +0000

From: Claude <[email-masked]>

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 <[email-masked]>
---
    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