Skip to content

Commit ee894c9

Browse files
authored
feat(dev): fix dot-worktree resolution, unique task files, and --agent/-a UX (#458)
1 parent 8d1d32f commit ee894c9

File tree

13 files changed

+422
-28
lines changed

13 files changed

+422
-28
lines changed

.claude-plugin/skills/agent-cli-dev/SKILL.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ agent-cli dev new <branch-name> --from HEAD --agent --prompt-file path/to/prompt
5656
This creates:
5757
1. A new git worktree with its own branch
5858
2. Runs project setup (installs dependencies)
59-
3. Saves your prompt to `.claude/TASK.md` in the worktree (for reference)
59+
3. Saves your prompt to a unique task file in `.claude/` in the worktree (for reference)
6060
4. Opens a new terminal tab with an AI coding agent
6161
5. Passes your prompt to the agent
6262

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

145145
Key rules for same-worktree launches:
146146
- Use `dev agent`, not `dev new`, after the worktree already exists
147+
- Use `dev agent -a <agent>` to select a specific agent for an existing worktree; `--with-agent` remains a deprecated alias on this subcommand
147148
- Use `-m tmux` for headless or scripted launching; it works even when not already inside tmux
148149
- Each launch joins the same deterministic repo-scoped tmux session, so related agents stay grouped together
149150
- 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`
150151
- 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
151-
- Do not rely on `.claude/TASK.md` as per-agent state in shared worktrees; later launches overwrite it
152+
- 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
152153

153154
### Prompt guidance for shared worktrees
154155

.claude-plugin/skills/agent-cli-dev/examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ Write findings to .claude/REPORT-tests-$run_id.md:
585585
- If you rerun the same prompt often, include a timestamp or run id in the filename so reports do not get replaced
586586
- `-m tmux` works even when the caller is not already inside tmux
587587
- All three agents land in the same deterministic tmux session for that repo
588-
- `.claude/TASK.md` is shared state and may be overwritten by later launches, so keep prompt files outside that convention
588+
- Each agent launch gets its own unique task file in `.claude/`, so parallel launches do not conflict
589589
590590
## Scenario 7: Parallel test validation
591591

.claude/skills/agent-cli-dev/SKILL.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ agent-cli dev new <branch-name> --from HEAD --agent --prompt-file path/to/prompt
5656
This creates:
5757
1. A new git worktree with its own branch
5858
2. Runs project setup (installs dependencies)
59-
3. Saves your prompt to `.claude/TASK.md` in the worktree (for reference)
59+
3. Saves your prompt to a unique task file in `.claude/` in the worktree (for reference)
6060
4. Opens a new terminal tab with an AI coding agent
6161
5. Passes your prompt to the agent
6262

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

145145
Key rules for same-worktree launches:
146146
- Use `dev agent`, not `dev new`, after the worktree already exists
147+
- Use `dev agent -a <agent>` to select a specific agent for an existing worktree; `--with-agent` remains a deprecated alias on this subcommand
147148
- Use `-m tmux` for headless or scripted launching; it works even when not already inside tmux
148149
- Each launch joins the same deterministic repo-scoped tmux session, so related agents stay grouped together
149150
- 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`
150151
- 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
151-
- Do not rely on `.claude/TASK.md` as per-agent state in shared worktrees; later launches overwrite it
152+
- 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
152153

153154
### Prompt guidance for shared worktrees
154155

.claude/skills/agent-cli-dev/examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ Write findings to .claude/REPORT-tests-$run_id.md:
585585
- If you rerun the same prompt often, include a timestamp or run id in the filename so reports do not get replaced
586586
- `-m tmux` works even when the caller is not already inside tmux
587587
- All three agents land in the same deterministic tmux session for that repo
588-
- `.claude/TASK.md` is shared state and may be overwritten by later launches, so keep prompt files outside that convention
588+
- Each agent launch gets its own unique task file in `.claude/`, so parallel launches do not conflict
589589
590590
## Scenario 7: Parallel test validation
591591

agent_cli/dev/cli.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ def new(
373373
typer.Option(
374374
"--prompt",
375375
"-p",
376-
help="Initial task for the AI agent. Saved to .claude/TASK.md. Implies --agent. Example: --prompt='Fix the login bug'",
376+
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'",
377377
),
378378
] = None,
379379
prompt_file: Annotated[
@@ -776,7 +776,9 @@ def status_cmd( # noqa: PLR0915
776776
def remove(
777777
name: Annotated[
778778
str,
779-
typer.Argument(help="Worktree to remove. Can be branch name or directory name"),
779+
typer.Argument(
780+
help="Worktree to remove. Can be branch name, directory name, or '.' for current"
781+
),
780782
],
781783
force: Annotated[
782784
bool,
@@ -840,7 +842,9 @@ def remove(
840842
def path_cmd(
841843
name: Annotated[
842844
str,
843-
typer.Argument(help="Worktree to get path for. Can be branch name or directory name"),
845+
typer.Argument(
846+
help="Worktree to get path for. Can be branch name, directory name, or '.' for current"
847+
),
844848
],
845849
) -> None:
846850
"""Print the absolute path to a dev environment.
@@ -862,7 +866,9 @@ def path_cmd(
862866
def open_editor(
863867
name: Annotated[
864868
str,
865-
typer.Argument(help="Worktree to open. Can be branch name or directory name"),
869+
typer.Argument(
870+
help="Worktree to open. Can be branch name, directory name, or '.' for current"
871+
),
866872
],
867873
editor_name: Annotated[
868874
str | None,
@@ -911,7 +917,7 @@ def start_agent(
911917
name: Annotated[
912918
str,
913919
typer.Argument(
914-
help="Worktree to start the agent in. Can be branch name or directory name",
920+
help="Worktree to start the agent in. Can be branch name, directory name, or '.' for current",
915921
),
916922
],
917923
agent_name: Annotated[
@@ -922,6 +928,14 @@ def start_agent(
922928
help="Which agent: claude, codex, gemini, aider, copilot, cn, opencode, cursor-agent. Auto-detects if omitted",
923929
),
924930
] = None,
931+
agent_name_deprecated: Annotated[
932+
str | None,
933+
typer.Option(
934+
"--with-agent",
935+
hidden=True,
936+
help="[Deprecated: use --agent/-a] Which agent to start",
937+
),
938+
] = None,
925939
agent_args: Annotated[
926940
list[str] | None,
927941
typer.Option(
@@ -934,7 +948,7 @@ def start_agent(
934948
typer.Option(
935949
"--prompt",
936950
"-p",
937-
help="Initial task for the agent. Saved to .claude/TASK.md. Example: --prompt='Add unit tests for auth'",
951+
help="Initial task for the agent. Saved to a unique file in .claude/ to avoid conflicts. Example: --prompt='Add unit tests for auth'",
938952
),
939953
] = None,
940954
prompt_file: Annotated[
@@ -975,6 +989,11 @@ def start_agent(
975989
- `dev agent my-feature -a claude` — Start Claude specifically
976990
- `dev agent my-feature -p "Continue the auth refactor"` — Start with a task
977991
"""
992+
# Handle deprecated --with-agent alias
993+
if agent_name_deprecated is not None:
994+
warn("--with-agent is deprecated for 'dev agent', use --agent/-a instead")
995+
agent_name = agent_name or agent_name_deprecated
996+
978997
prompt = _resolve_prompt_text(prompt, prompt_file=prompt_file)
979998

980999
repo_root = _ensure_git_repo()
@@ -1233,7 +1252,9 @@ def _doctor_check_git() -> None:
12331252
def run_cmd(
12341253
name: Annotated[
12351254
str,
1236-
typer.Argument(help="Worktree to run command in. Can be branch name or directory name"),
1255+
typer.Argument(
1256+
help="Worktree to run command in. Can be branch name, directory name, or '.' for current"
1257+
),
12371258
],
12381259
command: Annotated[
12391260
list[str],

agent_cli/dev/launch.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,18 @@ def launch_editor(path: Path, editor: Editor) -> None:
168168

169169

170170
def write_prompt_to_worktree(worktree_path: Path, prompt: str) -> Path:
171-
"""Write the prompt to .claude/TASK.md in the worktree.
171+
"""Write the prompt to a unique file in .claude/ in the worktree.
172172
173-
This makes the task description available to the spawned agent
174-
and provides a record of what was requested.
173+
Uses a timestamp and random suffix to avoid overwrites when multiple
174+
agents are launched in parallel on the same worktree.
175175
"""
176+
import time # noqa: PLC0415
177+
176178
claude_dir = worktree_path / ".claude"
177179
claude_dir.mkdir(parents=True, exist_ok=True)
178-
task_file = claude_dir / "TASK.md"
180+
timestamp = int(time.time())
181+
suffix = os.urandom(2).hex()
182+
task_file = claude_dir / f"TASK-{timestamp}-{suffix}.md"
179183
task_file.write_text(prompt + "\n")
180184
return task_file
181185

@@ -201,8 +205,6 @@ def _create_prompt_wrapper_script(
201205
env: dict[str, str] | None = None,
202206
) -> Path:
203207
"""Create a wrapper script that reads prompt from file to avoid shell quoting issues."""
204-
script_path = Path(tempfile.gettempdir()) / f"agent-cli-{worktree_path.name}.sh"
205-
206208
# Build the agent command without the prompt
207209
exe = agent.get_executable()
208210
if exe is None:
@@ -222,7 +224,13 @@ def _create_prompt_wrapper_script(
222224
# Reads prompt from file to avoid shell parsing issues with special characters
223225
{env_prefix}exec {agent_cmd} "$(cat {shlex.quote(str(task_file_rel))})"
224226
"""
225-
script_path.write_text(script_content)
227+
fd, script_path_str = tempfile.mkstemp(
228+
prefix=f"agent-cli-{worktree_path.name}-",
229+
suffix=".sh",
230+
)
231+
os.write(fd, script_content.encode())
232+
os.close(fd)
233+
script_path = Path(script_path_str)
226234
script_path.chmod(0o755)
227235
return script_path
228236

agent_cli/dev/skill/SKILL.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ agent-cli dev new <branch-name> --from HEAD --agent --prompt-file path/to/prompt
5656
This creates:
5757
1. A new git worktree with its own branch
5858
2. Runs project setup (installs dependencies)
59-
3. Saves your prompt to `.claude/TASK.md` in the worktree (for reference)
59+
3. Saves your prompt to a unique task file in `.claude/` in the worktree (for reference)
6060
4. Opens a new terminal tab with an AI coding agent
6161
5. Passes your prompt to the agent
6262

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

145145
Key rules for same-worktree launches:
146146
- Use `dev agent`, not `dev new`, after the worktree already exists
147+
- Use `dev agent -a <agent>` to select a specific agent for an existing worktree; `--with-agent` remains a deprecated alias on this subcommand
147148
- Use `-m tmux` for headless or scripted launching; it works even when not already inside tmux
148149
- Each launch joins the same deterministic repo-scoped tmux session, so related agents stay grouped together
149150
- 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`
150151
- 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
151-
- Do not rely on `.claude/TASK.md` as per-agent state in shared worktrees; later launches overwrite it
152+
- 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
152153

153154
### Prompt guidance for shared worktrees
154155

agent_cli/dev/skill/examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ Write findings to .claude/REPORT-tests-$run_id.md:
585585
- If you rerun the same prompt often, include a timestamp or run id in the filename so reports do not get replaced
586586
- `-m tmux` works even when the caller is not already inside tmux
587587
- All three agents land in the same deterministic tmux session for that repo
588-
- `.claude/TASK.md` is shared state and may be overwritten by later launches, so keep prompt files outside that convention
588+
- Each agent launch gets its own unique task file in `.claude/`, so parallel launches do not conflict
589589
590590
## Scenario 7: Parallel test validation
591591

agent_cli/dev/worktree.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,12 +285,39 @@ def resolve_worktree_base_dir(repo_root: Path) -> Path:
285285
return repo_root.parent / f"{repo_root.name}-worktrees"
286286

287287

288+
def _find_worktree_for_cwd(worktrees: list[WorktreeInfo]) -> WorktreeInfo | None:
289+
"""Find the worktree containing the current working directory."""
290+
cwd = Path.cwd().resolve()
291+
# Prefer the deepest matching path so nested layouts like .worktrees/<name>
292+
# resolve to the actual worktree instead of the main repo.
293+
for wt in sorted(
294+
worktrees,
295+
key=lambda worktree: len(worktree.path.resolve().parts),
296+
reverse=True,
297+
):
298+
try:
299+
cwd.relative_to(wt.path.resolve())
300+
return wt
301+
except ValueError:
302+
continue
303+
# Fallback: return main worktree
304+
return next((wt for wt in worktrees if wt.is_main), None)
305+
306+
288307
def find_worktree_by_name(
289308
name: str,
290309
repo_path: Path | None = None,
291310
) -> WorktreeInfo | None:
292-
"""Find a worktree by branch name or directory name."""
311+
"""Find a worktree by branch name or directory name.
312+
313+
Use '.' to match the worktree containing the current working directory,
314+
or the main worktree if the CWD is not inside any worktree.
315+
"""
293316
worktrees = list_worktrees(repo_path)
317+
318+
if name == ".":
319+
return _find_worktree_for_cwd(worktrees)
320+
294321
sanitized = sanitize_branch_name(name)
295322

296323
for wt in worktrees:

docs/commands/dev.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ agent-cli dev new [BRANCH] [OPTIONS]
8484
| `--branch-name-timeout` | `20.0` | Timeout in seconds for AI branch naming command |
8585
| `--direnv/--no-direnv` | - | Generate .envrc based on project type and run 'direnv allow'. Auto-enabled if direnv is installed |
8686
| `--agent-args` | - | Extra CLI args for the agent. Can be repeated. Example: --agent-args='--dangerously-skip-permissions' |
87-
| `--prompt, -p` | - | Initial task for the AI agent. Saved to .claude/TASK.md. Implies --agent. Example: --prompt='Fix the login bug' |
87+
| `--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' |
8888
| `--prompt-file, -P` | - | Read the agent prompt from a file. Useful for long prompts to avoid shell quoting. Implies --agent |
8989
| `--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 |
9090
| `--hooks/--no-hooks` | `true` | Run built-in agent preparation (like Codex auto-trust) and configured pre-launch hooks before starting the agent |
@@ -292,8 +292,9 @@ agent-cli dev agent NAME [--agent/-a AGENT] [--agent-args ARGS] [--prompt/-p PRO
292292
| Option | Default | Description |
293293
|--------|---------|-------------|
294294
| `--agent, -a` | - | Which agent: claude, codex, gemini, aider, copilot, cn, opencode, cursor-agent. Auto-detects if omitted |
295+
| `--with-agent` | - | [Deprecated: use --agent/-a] Which agent to start |
295296
| `--agent-args` | - | Extra CLI args for the agent. Example: --agent-args='--dangerously-skip-permissions' |
296-
| `--prompt, -p` | - | Initial task for the agent. Saved to .claude/TASK.md. Example: --prompt='Add unit tests for auth' |
297+
| `--prompt, -p` | - | Initial task for the agent. Saved to a unique file in .claude/ to avoid conflicts. Example: --prompt='Add unit tests for auth' |
297298
| `--prompt-file, -P` | - | Read the agent prompt from a file instead of command line |
298299
| `--multiplexer, -m` | - | Launch the agent in a specific multiplexer instead of the current terminal. Currently supported: tmux |
299300
| `--hooks/--no-hooks` | `true` | Run built-in agent preparation (like Codex auto-trust) and configured pre-launch hooks before starting the agent |
@@ -775,7 +776,7 @@ for section in 1 2 3 4; do
775776
done
776777
```
777778

778-
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.
779+
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.
779780

780781
## Shell Integration
781782

0 commit comments

Comments
 (0)