fix: ActivateWorktree uses plan project directory instead of workspace

FindRepoRoot was called on ai.Workspace (picoclaw home), but git init
is done in the plan's project directory. This caused "workspace is not
a git repository" and silently skipped worktree creation.

Now accepts projectDir parameter and falls back to workspace if empty.
Call sites pass GetPlanWorkDir() so worktrees are created inside the
actual project repository.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
dj-oyu 2026-02-24 18:04:16 +09:00
parent ca04d7c139
commit 1e5926ee35
2 changed files with 14 additions and 7 deletions

View file

@ -206,16 +206,21 @@ func resolvePlanFallbacks(agentCfg *config.AgentConfig, defaults *config.AgentDe
} }
// ActivateWorktree creates a worktree for a session. // ActivateWorktree creates a worktree for a session.
// Path: <workspace>/.picoclaw/worktrees/<branch-basename>/ // projectDir is the git repository to create the worktree in.
func (ai *AgentInstance) ActivateWorktree(sessionKey, taskName string) (*git.WorktreeInfo, error) { // If empty, falls back to ai.Workspace.
repoRoot := git.FindRepoRoot(ai.Workspace) // Path: <projectDir>/.picoclaw/worktrees/<branch-basename>/
func (ai *AgentInstance) ActivateWorktree(sessionKey, taskName, projectDir string) (*git.WorktreeInfo, error) {
if projectDir == "" {
projectDir = ai.Workspace
}
repoRoot := git.FindRepoRoot(projectDir)
if repoRoot == "" { if repoRoot == "" {
return nil, fmt.Errorf("workspace is not a git repository") return nil, fmt.Errorf("directory is not a git repository: %s", projectDir)
} }
branchName := git.SanitizeBranchName(taskName) branchName := git.SanitizeBranchName(taskName)
baseName := git.BranchBaseName(branchName) baseName := git.BranchBaseName(branchName)
wtPath := filepath.Join(ai.Workspace, ".picoclaw", "worktrees", baseName) wtPath := filepath.Join(repoRoot, ".picoclaw", "worktrees", baseName)
wt, err := git.CreateWorktree(repoRoot, wtPath, branchName) wt, err := git.CreateWorktree(repoRoot, wtPath, branchName)
if err != nil { if err != nil {

View file

@ -307,7 +307,8 @@ func (al *AgentLoop) Run(ctx context.Context) error {
if taskName == "" { if taskName == "" {
taskName = "plan-execution" taskName = "plan-execution"
} }
if wt, err := agent.ActivateWorktree(msg.SessionKey, taskName); err != nil { planDir := agent.ContextBuilder.GetPlanWorkDir()
if wt, err := agent.ActivateWorktree(msg.SessionKey, taskName, planDir); err != nil {
logger.WarnCF("agent", "Worktree activation skipped", map[string]any{"error": err.Error()}) logger.WarnCF("agent", "Worktree activation skipped", map[string]any{"error": err.Error()})
} else { } else {
logger.InfoCF("agent", "Worktree activated", map[string]any{"branch": wt.Branch}) logger.InfoCF("agent", "Worktree activated", map[string]any{"branch": wt.Branch})
@ -2111,7 +2112,8 @@ func (al *AgentLoop) runLLMIteration(
// Heartbeat lazy worktree: create worktree on first write-tool call // Heartbeat lazy worktree: create worktree on first write-tool call
if opts.Background && isWriteTool(tc.Name) && !agent.IsInWorktree(opts.SessionKey) { if opts.Background && isWriteTool(tc.Name) && !agent.IsInWorktree(opts.SessionKey) {
taskName := "heartbeat-" + time.Now().Format("20060102") taskName := "heartbeat-" + time.Now().Format("20060102")
if wt, err := agent.ActivateWorktree(opts.SessionKey, taskName); err == nil { hbDir := agent.ContextBuilder.GetPlanWorkDir()
if wt, err := agent.ActivateWorktree(opts.SessionKey, taskName, hbDir); err == nil {
logger.InfoCF("agent", "Heartbeat worktree created", map[string]any{"branch": wt.Branch}) logger.InfoCF("agent", "Heartbeat worktree created", map[string]any{"branch": wt.Branch})
} }
} }