diff --git a/src/hooks/start-work/worktree-detector.test.ts b/src/hooks/start-work/worktree-detector.test.ts
new file mode 100644
index 000000000..b02d5af1b
--- /dev/null
+++ b/src/hooks/start-work/worktree-detector.test.ts
@@ -0,0 +1,79 @@
+///
+
+import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test"
+import * as childProcess from "node:child_process"
+import { detectWorktreePath } from "./worktree-detector"
+
+describe("detectWorktreePath", () => {
+ let execFileSyncSpy: ReturnType
+
+ beforeEach(() => {
+ execFileSyncSpy = spyOn(childProcess, "execFileSync").mockImplementation(
+ ((_file: string, _args: string[]) => "") as typeof childProcess.execFileSync,
+ )
+ })
+
+ afterEach(() => {
+ execFileSyncSpy.mockRestore()
+ })
+
+ describe("when directory is a valid git worktree", () => {
+ test("#given valid git dir #when detecting #then returns worktree root path", () => {
+ execFileSyncSpy.mockImplementation(
+ ((_file: string, _args: string[]) => "/home/user/my-repo\n") as typeof childProcess.execFileSync,
+ )
+
+ // when
+ const result = detectWorktreePath("/home/user/my-repo/src")
+
+ // then
+ expect(result).toBe("/home/user/my-repo")
+ })
+
+ test("#given git output with trailing newline #when detecting #then trims output", () => {
+ execFileSyncSpy.mockImplementation(
+ ((_file: string, _args: string[]) => "/projects/worktree-a\n\n") as typeof childProcess.execFileSync,
+ )
+
+ const result = detectWorktreePath("/projects/worktree-a")
+
+ expect(result).toBe("/projects/worktree-a")
+ })
+
+ test("#given valid dir #when detecting #then calls git rev-parse with cwd", () => {
+ execFileSyncSpy.mockImplementation(
+ ((_file: string, _args: string[]) => "/repo\n") as typeof childProcess.execFileSync,
+ )
+
+ detectWorktreePath("/repo/some/subdir")
+
+ expect(execFileSyncSpy).toHaveBeenCalledWith(
+ "git",
+ ["rev-parse", "--show-toplevel"],
+ expect.objectContaining({ cwd: "/repo/some/subdir" }),
+ )
+ })
+ })
+
+ describe("when directory is not a git worktree", () => {
+ test("#given non-git directory #when detecting #then returns null", () => {
+ execFileSyncSpy.mockImplementation((_file: string, _args: string[]) => {
+ throw new Error("not a git repository")
+ })
+
+ const result = detectWorktreePath("/tmp/not-a-repo")
+
+ expect(result).toBeNull()
+ })
+
+ test("#given non-existent directory #when detecting #then returns null", () => {
+ execFileSyncSpy.mockImplementation((_file: string, _args: string[]) => {
+ throw new Error("ENOENT: no such file or directory")
+ })
+
+ const result = detectWorktreePath("/nonexistent/path")
+
+ expect(result).toBeNull()
+ })
+ })
+})
diff --git a/src/hooks/start-work/worktree-detector.ts b/src/hooks/start-work/worktree-detector.ts
new file mode 100644
index 000000000..74c919593
--- /dev/null
+++ b/src/hooks/start-work/worktree-detector.ts
@@ -0,0 +1,14 @@
+import { execFileSync } from "node:child_process"
+
+export function detectWorktreePath(directory: string): string | null {
+ try {
+ return execFileSync("git", ["rev-parse", "--show-toplevel"], {
+ cwd: directory,
+ encoding: "utf-8",
+ timeout: 5000,
+ stdio: ["pipe", "pipe", "pipe"],
+ }).trim()
+ } catch {
+ return null
+ }
+}