Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .claude-plugin/skills/agent-cli-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ agent-cli dev new <branch-name> --from HEAD --agent --prompt-file path/to/prompt
This creates:
1. A new git worktree with its own branch
2. Runs project setup (installs dependencies)
3. Saves your prompt to `.claude/TASK.md` in the worktree (for reference)
3. Saves your prompt to a unique task file in `.claude/` in the worktree (for reference)
4. Opens a new terminal tab with an AI coding agent
5. Passes your prompt to the agent

Expand Down Expand Up @@ -144,11 +144,12 @@ agent-cli dev agent review-auth -m tmux --prompt-file .claude/review-tests.md

Key rules for same-worktree launches:
- Use `dev agent`, not `dev new`, after the worktree already exists
- Use `dev agent -a <agent>` to select a specific agent for an existing worktree; `--with-agent` remains a deprecated alias on this subcommand
- Use `-m tmux` for headless or scripted launching; it works even when not already inside tmux
- Each launch joins the same deterministic repo-scoped tmux session, so related agents stay grouped together
- Ask each agent to write to a unique report path such as `.claude/REPORT-security-<run-id>.md` or `.claude/REPORT-tests-<run-id>.md`
- If you rerun the same prompt repeatedly, include a timestamp or other run id in the report filename so later runs do not overwrite earlier ones
- Do not rely on `.claude/TASK.md` as per-agent state in shared worktrees; later launches overwrite it
- Each agent launch gets its own unique task file in `.claude/` (e.g., `TASK-{timestamp}-{hex}.md`), so parallel launches do not overwrite each other

### Prompt guidance for shared worktrees

Expand Down
2 changes: 1 addition & 1 deletion .claude-plugin/skills/agent-cli-dev/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ Write findings to .claude/REPORT-tests-$run_id.md:
- If you rerun the same prompt often, include a timestamp or run id in the filename so reports do not get replaced
- `-m tmux` works even when the caller is not already inside tmux
- All three agents land in the same deterministic tmux session for that repo
- `.claude/TASK.md` is shared state and may be overwritten by later launches, so keep prompt files outside that convention
- Each agent launch gets its own unique task file in `.claude/`, so parallel launches do not conflict

## Scenario 7: Parallel test validation

Expand Down
5 changes: 3 additions & 2 deletions .claude/skills/agent-cli-dev/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ agent-cli dev new <branch-name> --from HEAD --agent --prompt-file path/to/prompt
This creates:
1. A new git worktree with its own branch
2. Runs project setup (installs dependencies)
3. Saves your prompt to `.claude/TASK.md` in the worktree (for reference)
3. Saves your prompt to a unique task file in `.claude/` in the worktree (for reference)
4. Opens a new terminal tab with an AI coding agent
5. Passes your prompt to the agent

Expand Down Expand Up @@ -144,11 +144,12 @@ agent-cli dev agent review-auth -m tmux --prompt-file .claude/review-tests.md

Key rules for same-worktree launches:
- Use `dev agent`, not `dev new`, after the worktree already exists
- Use `dev agent -a <agent>` to select a specific agent for an existing worktree; `--with-agent` remains a deprecated alias on this subcommand
- Use `-m tmux` for headless or scripted launching; it works even when not already inside tmux
- Each launch joins the same deterministic repo-scoped tmux session, so related agents stay grouped together
- Ask each agent to write to a unique report path such as `.claude/REPORT-security-<run-id>.md` or `.claude/REPORT-tests-<run-id>.md`
- If you rerun the same prompt repeatedly, include a timestamp or other run id in the report filename so later runs do not overwrite earlier ones
- Do not rely on `.claude/TASK.md` as per-agent state in shared worktrees; later launches overwrite it
- Each agent launch gets its own unique task file in `.claude/` (e.g., `TASK-{timestamp}-{hex}.md`), so parallel launches do not overwrite each other

### Prompt guidance for shared worktrees

Expand Down
2 changes: 1 addition & 1 deletion .claude/skills/agent-cli-dev/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ Write findings to .claude/REPORT-tests-$run_id.md:
- If you rerun the same prompt often, include a timestamp or run id in the filename so reports do not get replaced
- `-m tmux` works even when the caller is not already inside tmux
- All three agents land in the same deterministic tmux session for that repo
- `.claude/TASK.md` is shared state and may be overwritten by later launches, so keep prompt files outside that convention
- Each agent launch gets its own unique task file in `.claude/`, so parallel launches do not conflict

## Scenario 7: Parallel test validation

Expand Down
35 changes: 28 additions & 7 deletions agent_cli/dev/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def new(
typer.Option(
"--prompt",
"-p",
help="Initial task for the AI agent. Saved to .claude/TASK.md. Implies --agent. Example: --prompt='Fix the login bug'",
help="Initial task for the AI agent. Saved to a unique file in .claude/ to avoid conflicts. Implies --agent. Example: --prompt='Fix the login bug'",
),
] = None,
prompt_file: Annotated[
Expand Down Expand Up @@ -776,7 +776,9 @@ def status_cmd( # noqa: PLR0915
def remove(
name: Annotated[
str,
typer.Argument(help="Worktree to remove. Can be branch name or directory name"),
typer.Argument(
help="Worktree to remove. Can be branch name, directory name, or '.' for current"
),
],
force: Annotated[
bool,
Expand Down Expand Up @@ -840,7 +842,9 @@ def remove(
def path_cmd(
name: Annotated[
str,
typer.Argument(help="Worktree to get path for. Can be branch name or directory name"),
typer.Argument(
help="Worktree to get path for. Can be branch name, directory name, or '.' for current"
),
],
) -> None:
"""Print the absolute path to a dev environment.
Expand All @@ -862,7 +866,9 @@ def path_cmd(
def open_editor(
name: Annotated[
str,
typer.Argument(help="Worktree to open. Can be branch name or directory name"),
typer.Argument(
help="Worktree to open. Can be branch name, directory name, or '.' for current"
),
],
editor_name: Annotated[
str | None,
Expand Down Expand Up @@ -911,7 +917,7 @@ def start_agent(
name: Annotated[
str,
typer.Argument(
help="Worktree to start the agent in. Can be branch name or directory name",
help="Worktree to start the agent in. Can be branch name, directory name, or '.' for current",
),
],
agent_name: Annotated[
Expand All @@ -922,6 +928,14 @@ def start_agent(
help="Which agent: claude, codex, gemini, aider, copilot, cn, opencode, cursor-agent. Auto-detects if omitted",
),
] = None,
agent_name_deprecated: Annotated[
str | None,
typer.Option(
"--with-agent",
hidden=True,
help="[Deprecated: use --agent/-a] Which agent to start",
),
] = None,
agent_args: Annotated[
list[str] | None,
typer.Option(
Expand All @@ -934,7 +948,7 @@ def start_agent(
typer.Option(
"--prompt",
"-p",
help="Initial task for the agent. Saved to .claude/TASK.md. Example: --prompt='Add unit tests for auth'",
help="Initial task for the agent. Saved to a unique file in .claude/ to avoid conflicts. Example: --prompt='Add unit tests for auth'",
),
] = None,
prompt_file: Annotated[
Expand Down Expand Up @@ -975,6 +989,11 @@ def start_agent(
- `dev agent my-feature -a claude` — Start Claude specifically
- `dev agent my-feature -p "Continue the auth refactor"` — Start with a task
"""
# Handle deprecated --with-agent alias
if agent_name_deprecated is not None:
warn("--with-agent is deprecated for 'dev agent', use --agent/-a instead")
agent_name = agent_name or agent_name_deprecated

prompt = _resolve_prompt_text(prompt, prompt_file=prompt_file)

repo_root = _ensure_git_repo()
Expand Down Expand Up @@ -1233,7 +1252,9 @@ def _doctor_check_git() -> None:
def run_cmd(
name: Annotated[
str,
typer.Argument(help="Worktree to run command in. Can be branch name or directory name"),
typer.Argument(
help="Worktree to run command in. Can be branch name, directory name, or '.' for current"
),
],
command: Annotated[
list[str],
Expand Down
22 changes: 15 additions & 7 deletions agent_cli/dev/launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,18 @@ def launch_editor(path: Path, editor: Editor) -> None:


def write_prompt_to_worktree(worktree_path: Path, prompt: str) -> Path:
"""Write the prompt to .claude/TASK.md in the worktree.
"""Write the prompt to a unique file in .claude/ in the worktree.

This makes the task description available to the spawned agent
and provides a record of what was requested.
Uses a timestamp and random suffix to avoid overwrites when multiple
agents are launched in parallel on the same worktree.
"""
import time # noqa: PLC0415

claude_dir = worktree_path / ".claude"
claude_dir.mkdir(parents=True, exist_ok=True)
task_file = claude_dir / "TASK.md"
timestamp = int(time.time())
suffix = os.urandom(2).hex()
task_file = claude_dir / f"TASK-{timestamp}-{suffix}.md"
task_file.write_text(prompt + "\n")
return task_file

Expand All @@ -201,8 +205,6 @@ def _create_prompt_wrapper_script(
env: dict[str, str] | None = None,
) -> Path:
"""Create a wrapper script that reads prompt from file to avoid shell quoting issues."""
script_path = Path(tempfile.gettempdir()) / f"agent-cli-{worktree_path.name}.sh"

# Build the agent command without the prompt
exe = agent.get_executable()
if exe is None:
Expand All @@ -222,7 +224,13 @@ def _create_prompt_wrapper_script(
# Reads prompt from file to avoid shell parsing issues with special characters
{env_prefix}exec {agent_cmd} "$(cat {shlex.quote(str(task_file_rel))})"
"""
script_path.write_text(script_content)
fd, script_path_str = tempfile.mkstemp(
prefix=f"agent-cli-{worktree_path.name}-",
suffix=".sh",
)
os.write(fd, script_content.encode())
os.close(fd)
script_path = Path(script_path_str)
script_path.chmod(0o755)
return script_path

Expand Down
5 changes: 3 additions & 2 deletions agent_cli/dev/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ agent-cli dev new <branch-name> --from HEAD --agent --prompt-file path/to/prompt
This creates:
1. A new git worktree with its own branch
2. Runs project setup (installs dependencies)
3. Saves your prompt to `.claude/TASK.md` in the worktree (for reference)
3. Saves your prompt to a unique task file in `.claude/` in the worktree (for reference)
4. Opens a new terminal tab with an AI coding agent
5. Passes your prompt to the agent

Expand Down Expand Up @@ -144,11 +144,12 @@ agent-cli dev agent review-auth -m tmux --prompt-file .claude/review-tests.md

Key rules for same-worktree launches:
- Use `dev agent`, not `dev new`, after the worktree already exists
- Use `dev agent -a <agent>` to select a specific agent for an existing worktree; `--with-agent` remains a deprecated alias on this subcommand
- Use `-m tmux` for headless or scripted launching; it works even when not already inside tmux
- Each launch joins the same deterministic repo-scoped tmux session, so related agents stay grouped together
- Ask each agent to write to a unique report path such as `.claude/REPORT-security-<run-id>.md` or `.claude/REPORT-tests-<run-id>.md`
- If you rerun the same prompt repeatedly, include a timestamp or other run id in the report filename so later runs do not overwrite earlier ones
- Do not rely on `.claude/TASK.md` as per-agent state in shared worktrees; later launches overwrite it
- Each agent launch gets its own unique task file in `.claude/` (e.g., `TASK-{timestamp}-{hex}.md`), so parallel launches do not overwrite each other

### Prompt guidance for shared worktrees

Expand Down
2 changes: 1 addition & 1 deletion agent_cli/dev/skill/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ Write findings to .claude/REPORT-tests-$run_id.md:
- If you rerun the same prompt often, include a timestamp or run id in the filename so reports do not get replaced
- `-m tmux` works even when the caller is not already inside tmux
- All three agents land in the same deterministic tmux session for that repo
- `.claude/TASK.md` is shared state and may be overwritten by later launches, so keep prompt files outside that convention
- Each agent launch gets its own unique task file in `.claude/`, so parallel launches do not conflict

## Scenario 7: Parallel test validation

Expand Down
29 changes: 28 additions & 1 deletion agent_cli/dev/worktree.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,12 +285,39 @@ def resolve_worktree_base_dir(repo_root: Path) -> Path:
return repo_root.parent / f"{repo_root.name}-worktrees"


def _find_worktree_for_cwd(worktrees: list[WorktreeInfo]) -> WorktreeInfo | None:
"""Find the worktree containing the current working directory."""
cwd = Path.cwd().resolve()
# Prefer the deepest matching path so nested layouts like .worktrees/<name>
# resolve to the actual worktree instead of the main repo.
for wt in sorted(
worktrees,
key=lambda worktree: len(worktree.path.resolve().parts),
reverse=True,
):
try:
cwd.relative_to(wt.path.resolve())
return wt
except ValueError:
continue
# Fallback: return main worktree
return next((wt for wt in worktrees if wt.is_main), None)


def find_worktree_by_name(
name: str,
repo_path: Path | None = None,
) -> WorktreeInfo | None:
"""Find a worktree by branch name or directory name."""
"""Find a worktree by branch name or directory name.

Use '.' to match the worktree containing the current working directory,
or the main worktree if the CWD is not inside any worktree.
"""
worktrees = list_worktrees(repo_path)

if name == ".":
return _find_worktree_for_cwd(worktrees)

sanitized = sanitize_branch_name(name)

for wt in worktrees:
Expand Down
7 changes: 4 additions & 3 deletions docs/commands/dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ agent-cli dev new [BRANCH] [OPTIONS]
| `--branch-name-timeout` | `20.0` | Timeout in seconds for AI branch naming command |
| `--direnv/--no-direnv` | - | Generate .envrc based on project type and run 'direnv allow'. Auto-enabled if direnv is installed |
| `--agent-args` | - | Extra CLI args for the agent. Can be repeated. Example: --agent-args='--dangerously-skip-permissions' |
| `--prompt, -p` | - | Initial task for the AI agent. Saved to .claude/TASK.md. Implies --agent. Example: --prompt='Fix the login bug' |
| `--prompt, -p` | - | Initial task for the AI agent. Saved to a unique file in .claude/ to avoid conflicts. Implies --agent. Example: --prompt='Fix the login bug' |
| `--prompt-file, -P` | - | Read the agent prompt from a file. Useful for long prompts to avoid shell quoting. Implies --agent |
| `--multiplexer, -m` | - | Launch the agent in a specific multiplexer. Currently supported: tmux. When started outside tmux, creates or reuses a detached session and reports the pane handle |
| `--hooks/--no-hooks` | `true` | Run built-in agent preparation (like Codex auto-trust) and configured pre-launch hooks before starting the agent |
Expand Down Expand Up @@ -292,8 +292,9 @@ agent-cli dev agent NAME [--agent/-a AGENT] [--agent-args ARGS] [--prompt/-p PRO
| Option | Default | Description |
|--------|---------|-------------|
| `--agent, -a` | - | Which agent: claude, codex, gemini, aider, copilot, cn, opencode, cursor-agent. Auto-detects if omitted |
| `--with-agent` | - | [Deprecated: use --agent/-a] Which agent to start |
| `--agent-args` | - | Extra CLI args for the agent. Example: --agent-args='--dangerously-skip-permissions' |
| `--prompt, -p` | - | Initial task for the agent. Saved to .claude/TASK.md. Example: --prompt='Add unit tests for auth' |
| `--prompt, -p` | - | Initial task for the agent. Saved to a unique file in .claude/ to avoid conflicts. Example: --prompt='Add unit tests for auth' |
| `--prompt-file, -P` | - | Read the agent prompt from a file instead of command line |
| `--multiplexer, -m` | - | Launch the agent in a specific multiplexer instead of the current terminal. Currently supported: tmux |
| `--hooks/--no-hooks` | `true` | Run built-in agent preparation (like Codex auto-trust) and configured pre-launch hooks before starting the agent |
Expand Down Expand Up @@ -775,7 +776,7 @@ for section in 1 2 3 4; do
done
```

If multiple agents share one worktree, do not have them all write to `.claude/REPORT.md` because they will overwrite each other. Instead, assign unique report paths such as `.claude/REPORT-security-<run-id>.md` and `.claude/REPORT-tests-<run-id>.md`. If you rerun the same prompt repeatedly, use a timestamp or other run id so later runs do not replace earlier results. The same applies to `.claude/TASK.md`: it reflects the most recent launch, not stable per-agent state.
If multiple agents share one worktree, do not have them all write to `.claude/REPORT.md` because they will overwrite each other. Instead, assign unique report paths such as `.claude/REPORT-security-<run-id>.md` and `.claude/REPORT-tests-<run-id>.md`. If you rerun the same prompt repeatedly, use a timestamp or other run id so later runs do not replace earlier results. Each agent launch also gets its own `.claude/TASK-{timestamp}-{hex}.md` file, so prompt files no longer overwrite each other.

## Shell Integration

Expand Down
Loading