(slug: string)
| 900 | * Falls back to hook-based creation if not in a git repository. |
| 901 | */ |
| 902 | export async function createAgentWorktree(slug: string): Promise<{ |
| 903 | worktreePath: string |
| 904 | worktreeBranch?: string |
| 905 | headCommit?: string |
| 906 | gitRoot?: string |
| 907 | hookBased?: boolean |
| 908 | }> { |
| 909 | validateWorktreeSlug(slug) |
| 910 | |
| 911 | // Try hook-based worktree creation first (allows user-configured VCS) |
| 912 | if (hasWorktreeCreateHook()) { |
| 913 | const hookResult = await executeWorktreeCreateHook(slug) |
| 914 | logForDebugging( |
| 915 | `Created hook-based agent worktree at: ${hookResult.worktreePath}`, |
| 916 | ) |
| 917 | |
| 918 | return { worktreePath: hookResult.worktreePath, hookBased: true } |
| 919 | } |
| 920 | |
| 921 | // Fall back to git worktree |
| 922 | // findCanonicalGitRoot (not findGitRoot) so agent worktrees always land in |
| 923 | // the main repo's .claude/worktrees/ even when spawned from inside a session |
| 924 | // worktree — otherwise they nest at <worktree>/.claude/worktrees/ and the |
| 925 | // periodic cleanup (which scans the canonical root) never finds them. |
| 926 | const gitRoot = findCanonicalGitRoot(getCwd()) |
| 927 | if (!gitRoot) { |
| 928 | throw new Error( |
| 929 | 'Cannot create agent worktree: not in a git repository and no WorktreeCreate hooks are configured. ' + |
| 930 | 'Configure WorktreeCreate/WorktreeRemove hooks in settings.json to use worktree isolation with other VCS systems.', |
| 931 | ) |
| 932 | } |
| 933 | |
| 934 | const { worktreePath, worktreeBranch, headCommit, existed } = |
| 935 | await getOrCreateWorktree(gitRoot, slug) |
| 936 | |
| 937 | if (!existed) { |
| 938 | logForDebugging( |
| 939 | `Created agent worktree at: ${worktreePath} on branch: ${worktreeBranch}`, |
| 940 | ) |
| 941 | await performPostCreationSetup(gitRoot, worktreePath) |
| 942 | } else { |
| 943 | // Bump mtime so the periodic stale-worktree cleanup doesn't consider this |
| 944 | // worktree stale — the fast-resume path is read-only and leaves the original |
| 945 | // creation-time mtime intact, which can be past the 30-day cutoff. |
| 946 | const now = new Date() |
| 947 | await utimes(worktreePath, now, now) |
| 948 | logForDebugging(`Resuming existing agent worktree at: ${worktreePath}`) |
| 949 | } |
| 950 | |
| 951 | return { worktreePath, worktreeBranch, headCommit, gitRoot } |
| 952 | } |
| 953 | |
| 954 | /** |
| 955 | * Remove a worktree created by createAgentWorktree. |
no test coverage detected