fix(do): reuse worktree across phases via DO_WORKTREE_DIR env var

Previously, each codeagent-wrapper --worktree call created a new worktree,
causing multiple worktrees per /do task (one per phase).

Changes:
- setup-do.py: create worktree at initialization, export DO_WORKTREE_DIR
- executor.go: check DO_WORKTREE_DIR first, reuse if set
- SKILL.md: update documentation for new behavior

Generated with SWE-Agent.ai

Co-Authored-By: SWE-Agent.ai <noreply@swe-agent.ai>
This commit is contained in:
cexll
2026-02-05 23:32:52 +08:00
parent 81fa6843d9
commit 5853539cab
3 changed files with 53 additions and 5 deletions

View File

@@ -941,8 +941,13 @@ func RunCodexTaskWithContext(parentCtx context.Context, taskSpec TaskSpec, backe
cfg.WorkDir = defaultWorkdir
}
// Handle worktree mode: create a new git worktree and update cfg.WorkDir
if taskSpec.Worktree {
// Handle worktree mode: check DO_WORKTREE_DIR env var first, then create if needed
if worktreeDir := os.Getenv("DO_WORKTREE_DIR"); worktreeDir != "" {
// Use existing worktree from /do setup
cfg.WorkDir = worktreeDir
logInfo(fmt.Sprintf("Using existing worktree from DO_WORKTREE_DIR: %s", worktreeDir))
} else if taskSpec.Worktree {
// Create new worktree (backward compatibility for standalone --worktree usage)
paths, err := createWorktreeFn(cfg.WorkDir)
if err != nil {
result.ExitCode = 1

View File

@@ -41,10 +41,15 @@ This creates `.claude/do.{task_id}.local.md` with:
## Worktree Mode
When `use_worktree: true` in state file, ALL `codeagent-wrapper` calls that modify code MUST include `--worktree`:
When `use_worktree: true` in state file:
- The worktree is created once during initialization (setup-do.py)
- The worktree path is stored in `worktree_dir` frontmatter field
- Environment variable `DO_WORKTREE_DIR` is exported for codeagent-wrapper to use
- ALL `codeagent-wrapper` calls that modify code MUST include `--worktree` flag
- codeagent-wrapper detects `DO_WORKTREE_DIR` and reuses the existing worktree instead of creating new ones
```bash
# With worktree mode enabled
# With worktree mode enabled - codeagent-wrapper will use DO_WORKTREE_DIR automatically
codeagent-wrapper --worktree --agent develop - . <<'EOF'
...
EOF
@@ -60,7 +65,7 @@ workdir: .
EOF
```
The `--worktree` flag tells codeagent-wrapper to create/use a worktree internally. Read-only agents (code-explorer, code-architect, code-reviewer) do NOT need `--worktree`.
The `--worktree` flag tells codeagent-wrapper to use worktree mode. When `DO_WORKTREE_DIR` is set, it reuses that directory; otherwise it creates a new worktree (backward compatibility). Read-only agents (code-explorer, code-architect, code-reviewer) do NOT need `--worktree`.
## Loop State Management

View File

@@ -2,6 +2,7 @@
import argparse
import os
import secrets
import subprocess
import sys
import time
@@ -20,6 +21,34 @@ def die(msg: str):
print(f"{msg}", file=sys.stderr)
sys.exit(1)
def create_worktree(project_dir: str, task_id: str) -> str:
"""Create a git worktree for the task. Returns the worktree directory path."""
# Get git root
result = subprocess.run(
["git", "-C", project_dir, "rev-parse", "--show-toplevel"],
capture_output=True,
text=True,
)
if result.returncode != 0:
die(f"Not a git repository: {project_dir}")
git_root = result.stdout.strip()
# Calculate paths
worktree_dir = os.path.join(git_root, ".worktrees", f"do-{task_id}")
branch_name = f"do/{task_id}"
# Create worktree with new branch
result = subprocess.run(
["git", "-C", git_root, "worktree", "add", "-b", branch_name, worktree_dir],
capture_output=True,
text=True,
)
if result.returncode != 0:
die(f"Failed to create worktree: {result.stderr}")
return worktree_dir
def main():
parser = argparse.ArgumentParser(
description="Creates (or overwrites) project state file: .claude/do.local.md"
@@ -50,6 +79,11 @@ def main():
os.makedirs(state_dir, exist_ok=True)
# Create worktree if requested (before writing state file)
worktree_dir = ""
if use_worktree:
worktree_dir = create_worktree(project_dir, task_id)
phase_name = phase_name_for(1)
content = f"""---
@@ -59,6 +93,7 @@ phase_name: "{phase_name}"
max_phases: {max_phases}
completion_promise: "{completion_promise}"
use_worktree: {str(use_worktree).lower()}
worktree_dir: "{worktree_dir}"
---
# do loop state
@@ -80,6 +115,9 @@ use_worktree: {str(use_worktree).lower()}
print(f"completion_promise: {completion_promise}")
print(f"use_worktree: {use_worktree}")
print(f"export DO_TASK_ID={task_id}")
if worktree_dir:
print(f"worktree_dir: {worktree_dir}")
print(f"export DO_WORKTREE_DIR={worktree_dir}")
if __name__ == "__main__":
main()