git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 2/4] worktree: add post-worktree-remove hook

From
Domen Kožar <domen@cachix.org>
Date
Aug 4, 2026, 18:14 UTC
Message-ID
<b9a1cf0c-1cf3-4c36-bf98-1923f9559b61@mtasv.net>
In-Reply-To
<20260804181358.532970-1-domen@cachix.org>

External tooling has no way to learn that a working tree is gone: "git worktree remove" deletes both the working tree and its administrative directory without running any hook.

Introduce a post-worktree-remove hook that runs after "git worktree remove" has deleted a working tree. It is given the former absolute path of the working tree and its identifier as arguments. The hook also runs when only the administrative entry is deleted because the working tree directory itself had already disappeared, since the worktree is deregistered either way.

Because the working tree no longer exists at that point, no special working directory or environment is set up; the hook runs wherever the command ran, like other post-command hooks.

The hook runs once deletion is underway even if parts of it fail, since there is no going back at that point, but it does not run when the removal is refused (locked or dirty working tree, failed validation). It cannot affect the outcome of the command other than its exit status being reflected in the exit status of "git worktree remove".

Signed-off-by: Domen Kožar <domen@cachix.org>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---
 Documentation/config/hook.adoc |  1 +
 Documentation/githooks.adoc    | 18 ++++++++++++++
 builtin/worktree.c             | 10 ++++++++
 t/t2403-worktree-move.sh       | 44 ++++++++++++++++++++++++++++++++++
 4 files changed, 73 insertions(+)
diff --git a/Documentation/config/hook.adoc b/Documentation/config/hook.adoc
index 81afb4a919..e013bc1e40 100644
--- a/Documentation/config/hook.adoc
+++ b/Documentation/config/hook.adoc
@@ -95,6 +95,7 @@ hook.jobs::
 `pre-commit`;;
 `post-checkout`;;
 `post-worktree-add`;;
+`post-worktree-remove`;;
 `push-to-checkout`;;
 `post-commit`;;
 	Access the working tree, index, or repository state.
diff --git a/Documentation/githooks.adoc b/Documentation/githooks.adoc
index 5a2955ee2f..9573b8c1f5 100644
--- a/Documentation/githooks.adoc
+++ b/Documentation/githooks.adoc
@@ -233,6 +233,24 @@ runs after the `post-checkout` hook, even if that hook fails.
 This hook can be used to set up per-worktree development environments
 or to register the new working tree with external tools.
 
+post-worktree-remove
+~~~~~~~~~~~~~~~~~~~~
+
+This hook is invoked by linkgit:git-worktree[1] after a working tree
+has been deleted by `git worktree remove`. The hook is given two
+parameters: the absolute path of the removed working tree and its
+identifier (the name of its former administrative directory in
+`$GIT_DIR/worktrees/`).
+
+The working tree no longer exists when the hook runs.
+
+This hook cannot affect the outcome of `git worktree remove`, other
+than that the hook's exit status becomes the exit status of the
+command.
+
+This hook can be used to tear down per-worktree development
+environments or to unregister the working tree from external tools.
+
 post-merge
 ~~~~~~~~~~
 
diff --git a/builtin/worktree.c b/builtin/worktree.c
index cc3299bca9..dc456fcac7 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -177,6 +177,14 @@ static int run_post_worktree_add_hook(const char *path)
 	return run_hooks_opt(the_repository, "post-worktree-add", &hook_opt);
 }
 
+static int run_post_worktree_remove_hook(const char *path, const char *id)
+{
+	struct run_hooks_opt hook_opt = RUN_HOOKS_OPT_INIT_FORCE_SERIAL;
+
+	strvec_pushl(&hook_opt.args, path, id, NULL);
+	return run_hooks_opt(the_repository, "post-worktree-remove", &hook_opt);
+}
+
 static void prune_worktree(const char *id, const char *reason)
 {
 	if (show_only || verbose)
@@ -1444,6 +1452,8 @@ static int remove_worktree(int ac, const char **av, const char *prefix,
 	ret |= delete_git_dir(wt->id);
 	delete_worktrees_dir_if_empty();
 
+	ret |= run_post_worktree_remove_hook(wt->path, wt->id);
+
 	free_worktrees(worktrees);
 	return ret;
 }
diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
index 0bb33e8b1b..b94f00e426 100755
--- a/t/t2403-worktree-move.sh
+++ b/t/t2403-worktree-move.sh
@@ -246,6 +246,50 @@ test_expect_success 'not remove a repo with initialized submodule' '
 	)
 '
 
+test_expect_success '"remove" invokes post-worktree-remove hook' '
+	test_hook post-worktree-remove <<-\EOF &&
+	echo $* >hook.actual
+	EOF
+	git worktree add --detach wt-hooked &&
+	git worktree remove wt-hooked &&
+	echo $(pwd)/wt-hooked wt-hooked >hook.expect &&
+	test_cmp hook.expect hook.actual
+'
+
+test_expect_success '"remove" of missing worktree invokes post-worktree-remove hook' '
+	test_when_finished "rm -rf wt-moved-away" &&
+	test_hook post-worktree-remove <<-\EOF &&
+	echo $* >hook.actual
+	EOF
+	rm -f hook.actual &&
+	git worktree add --detach wt-elsewhere &&
+	mv wt-elsewhere wt-moved-away &&
+	git worktree remove wt-elsewhere &&
+	echo $(pwd)/wt-elsewhere wt-elsewhere >hook.expect &&
+	test_cmp hook.expect hook.actual
+'
+
+test_expect_success 'refused "remove" does not invoke post-worktree-remove hook' '
+	git worktree add --detach wt-kept &&
+	test_when_finished "git worktree remove --force --force wt-kept || :" &&
+	test_hook post-worktree-remove <<-\EOF &&
+	>hook.ran
+	EOF
+	git worktree lock wt-kept &&
+	test_must_fail git worktree remove wt-kept &&
+	test_path_is_missing hook.ran
+'
+
+test_expect_success 'failing post-worktree-remove hook fails "remove", worktree is gone' '
+	test_hook post-worktree-remove <<-\EOF &&
+	exit 1
+	EOF
+	git worktree add --detach wt-doomed &&
+	test_must_fail git worktree remove wt-doomed &&
+	test_path_is_missing wt-doomed &&
+	test_path_is_missing .git/worktrees/wt-doomed
+'
+
 test_expect_success 'move worktree with absolute path to relative path' '
 	test_config worktree.useRelativePaths false &&
 	git worktree add ./absolute &&
-- 
2.54.0
Previous: Domen KožarNext: Domen Kožar
Message 17 of 21 in “worktree: add post-worktree-add and post-worktree-remove hooks”
  1. 0/3 worktree: add post-worktree-add and post-worktree-remove hooksDomen Kožar, Jul 9, 2026
  2. Phillip WoodJul 10, 2026
  3. 0/4 worktree: add lifecycle hooksDomen Kožar, Aug 4, 2026
  4. Caleb WhiteAug 4, 2026
  5. Junio C HamanoAug 4, 2026
  6. Domen KožarAug 30, 2026
  7. Domen KožarSep 7, 2026
  8. Kristoffer HaugsbakkSep 7, 2026
  9. Domen KožarSep 7, 2026
  10. Maciej CiemborowiczOct 3, 2026
  11. 0/2 worktree: add post-worktree lifecycle hookDomen Kožar, Oct 4, 2026
  12. Junio C HamanoOct 5, 2026
  13. Phillip WoodJul 13, 2026
  14. 1/4 worktree: add post-worktree-add hookDomen Kožar, Aug 4, 2026
  15. Caleb WhiteAug 4, 2026
  16. 4/4 worktree: add post-worktree-move hookDomen Kožar, Aug 4, 2026
  17. 2/4 worktree: add post-worktree-remove hookDomen Kožar, Aug 4, 2026
  18. 3/4 worktree: run post-worktree-remove hook when pruningDomen Kožar, Aug 4, 2026
  19. 1/2 worktree: add post-worktree lifecycle hookDomen Kožar, Oct 4, 2026
  20. Phillip WoodOct 6, 2026
  21. 2/2 worktree: notify post-worktree hook when pruningDomen Kožar, Oct 4, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.