Skip to content

Commit 4b756c6

Browse files
committed
feat(dev): fix usability bugs - dot worktree, unique task files, --agent/-a primary
- dev agent . resolves to current worktree via find_worktree_by_name - write_prompt_to_worktree uses unique TASK-{ts}-{hex}.md filenames - _create_prompt_wrapper_script uses mkstemp for unique script paths - dev agent: --agent/-a is primary flag, --with-agent deprecated alias - Updated help text, docs, skill documentation - 7 new tests covering all fixes + race condition regression
1 parent 7905b81 commit 4b756c6

File tree

13 files changed

+378
-39
lines changed

13 files changed

+378
-39
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: 31 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ def new(
327327
typer.Option(
328328
"--prompt",
329329
"-p",
330-
help="Initial task for the AI agent. Saved to .claude/TASK.md. Implies --agent. Example: --prompt='Fix the login bug'",
330+
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'",
331331
),
332332
] = None,
333333
prompt_file: Annotated[
@@ -710,7 +710,9 @@ def status_cmd( # noqa: PLR0915
710710
def remove(
711711
name: Annotated[
712712
str,
713-
typer.Argument(help="Worktree to remove. Can be branch name or directory name"),
713+
typer.Argument(
714+
help="Worktree to remove. Can be branch name, directory name, or '.' for current"
715+
),
714716
],
715717
force: Annotated[
716718
bool,
@@ -774,7 +776,9 @@ def remove(
774776
def path_cmd(
775777
name: Annotated[
776778
str,
777-
typer.Argument(help="Worktree to get path for. Can be branch name or directory name"),
779+
typer.Argument(
780+
help="Worktree to get path for. Can be branch name, directory name, or '.' for current"
781+
),
778782
],
779783
) -> None:
780784
"""Print the absolute path to a dev environment.
@@ -796,7 +800,9 @@ def path_cmd(
796800
def open_editor(
797801
name: Annotated[
798802
str,
799-
typer.Argument(help="Worktree to open. Can be branch name or directory name"),
803+
typer.Argument(
804+
help="Worktree to open. Can be branch name, directory name, or '.' for current"
805+
),
800806
],
801807
editor_name: Annotated[
802808
str | None,
@@ -845,7 +851,7 @@ def start_agent(
845851
name: Annotated[
846852
str,
847853
typer.Argument(
848-
help="Worktree to start the agent in. Can be branch name or directory name",
854+
help="Worktree to start the agent in. Can be branch name, directory name, or '.' for current",
849855
),
850856
],
851857
agent_name: Annotated[
@@ -856,6 +862,14 @@ def start_agent(
856862
help="Which agent: claude, codex, gemini, aider, copilot, cn, opencode, cursor-agent. Auto-detects if omitted",
857863
),
858864
] = None,
865+
agent_name_deprecated: Annotated[
866+
str | None,
867+
typer.Option(
868+
"--with-agent",
869+
hidden=True,
870+
help="[Deprecated: use --agent/-a] Which agent to start",
871+
),
872+
] = None,
859873
agent_args: Annotated[
860874
list[str] | None,
861875
typer.Option(
@@ -868,7 +882,7 @@ def start_agent(
868882
typer.Option(
869883
"--prompt",
870884
"-p",
871-
help="Initial task for the agent. Saved to .claude/TASK.md. Example: --prompt='Add unit tests for auth'",
885+
help="Initial task for the agent. Saved to a unique file in .claude/ to avoid conflicts. Example: --prompt='Add unit tests for auth'",
872886
),
873887
] = None,
874888
prompt_file: Annotated[
@@ -902,6 +916,11 @@ def start_agent(
902916
- `dev agent my-feature -a claude` — Start Claude specifically
903917
- `dev agent my-feature -p "Continue the auth refactor"` — Start with a task
904918
"""
919+
# Handle deprecated --with-agent alias
920+
if agent_name_deprecated is not None:
921+
warn("--with-agent is deprecated for 'dev agent', use --agent/-a instead")
922+
agent_name = agent_name or agent_name_deprecated
923+
905924
# Handle prompt-file option (takes precedence over --prompt)
906925
if prompt_file is not None:
907926
prompt = prompt_file.read_text().strip()
@@ -912,18 +931,9 @@ def start_agent(
912931
if wt is None:
913932
error(f"Worktree not found: {name}")
914933

915-
if agent_name:
916-
agent = coding_agents.get_agent(agent_name)
917-
if agent is None:
918-
error(f"Agent not found: {agent_name}")
919-
else:
920-
agent = coding_agents.detect_current_agent()
921-
if agent is None:
922-
available = coding_agents.get_available_agents()
923-
if not available:
924-
error("No AI coding agents available")
925-
agent = available[0]
926-
934+
agent = resolve_agent(use_agent=True, agent_name=agent_name, default_agent=None)
935+
if agent is None:
936+
error(f"Agent not found: {agent_name}" if agent_name else "No AI coding agents available")
927937
if not agent.is_available():
928938
error(f"{agent.name} is not installed. Install from: {agent.install_url}")
929939

@@ -1155,7 +1165,9 @@ def _doctor_check_git() -> None:
11551165
def run_cmd(
11561166
name: Annotated[
11571167
str,
1158-
typer.Argument(help="Worktree to run command in. Can be branch name or directory name"),
1168+
typer.Argument(
1169+
help="Worktree to run command in. Can be branch name, directory name, or '.' for current"
1170+
),
11591171
],
11601172
command: Annotated[
11611173
list[str],

agent_cli/dev/launch.py

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

190190

191191
def write_prompt_to_worktree(worktree_path: Path, prompt: str) -> Path:
192-
"""Write the prompt to .claude/TASK.md in the worktree.
192+
"""Write the prompt to a unique file in .claude/ in the worktree.
193193
194-
This makes the task description available to the spawned agent
195-
and provides a record of what was requested.
194+
Uses a timestamp and random suffix to avoid overwrites when multiple
195+
agents are launched in parallel on the same worktree.
196196
"""
197+
import time # noqa: PLC0415
198+
197199
claude_dir = worktree_path / ".claude"
198200
claude_dir.mkdir(parents=True, exist_ok=True)
199-
task_file = claude_dir / "TASK.md"
201+
timestamp = int(time.time())
202+
suffix = os.urandom(2).hex()
203+
task_file = claude_dir / f"TASK-{timestamp}-{suffix}.md"
200204
task_file.write_text(prompt + "\n")
201205
return task_file
202206

@@ -222,8 +226,6 @@ def _create_prompt_wrapper_script(
222226
env: dict[str, str] | None = None,
223227
) -> Path:
224228
"""Create a wrapper script that reads prompt from file to avoid shell quoting issues."""
225-
script_path = Path(tempfile.gettempdir()) / f"agent-cli-{worktree_path.name}.sh"
226-
227229
# Build the agent command without the prompt
228230
exe = agent.get_executable()
229231
if exe is None:
@@ -243,7 +245,13 @@ def _create_prompt_wrapper_script(
243245
# Reads prompt from file to avoid shell parsing issues with special characters
244246
{env_prefix}exec {agent_cmd} "$(cat {shlex.quote(str(task_file_rel))})"
245247
"""
246-
script_path.write_text(script_content)
248+
fd, script_path_str = tempfile.mkstemp(
249+
prefix=f"agent-cli-{worktree_path.name}-",
250+
suffix=".sh",
251+
)
252+
os.write(fd, script_content.encode())
253+
os.close(fd)
254+
script_path = Path(script_path_str)
247255
script_path.chmod(0o755)
248256
return script_path
249257

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: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,12 +285,33 @@ 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+
for wt in worktrees:
292+
try:
293+
cwd.relative_to(wt.path.resolve())
294+
return wt
295+
except ValueError:
296+
continue
297+
# Fallback: return main worktree
298+
return next((wt for wt in worktrees if wt.is_main), None)
299+
300+
288301
def find_worktree_by_name(
289302
name: str,
290303
repo_path: Path | None = None,
291304
) -> WorktreeInfo | None:
292-
"""Find a worktree by branch name or directory name."""
305+
"""Find a worktree by branch name or directory name.
306+
307+
Use '.' to match the worktree containing the current working directory,
308+
or the main worktree if the CWD is not inside any worktree.
309+
"""
293310
worktrees = list_worktrees(repo_path)
311+
312+
if name == ".":
313+
return _find_worktree_for_cwd(worktrees)
314+
294315
sanitized = sanitize_branch_name(name)
295316

296317
for wt in worktrees:

docs/commands/dev.md

Lines changed: 2 additions & 2 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
| `--verbose, -v` | `false` | Stream output from setup commands instead of hiding it |
@@ -292,7 +292,7 @@ agent-cli dev agent NAME [--agent/-a AGENT] [--agent-args ARGS] [--prompt/-p PRO
292292
|--------|---------|-------------|
293293
| `--agent, -a` | - | Which agent: claude, codex, gemini, aider, copilot, cn, opencode, cursor-agent. Auto-detects if omitted |
294294
| `--agent-args` | - | Extra CLI args for the agent. Example: --agent-args='--dangerously-skip-permissions' |
295-
| `--prompt, -p` | - | Initial task for the agent. Saved to .claude/TASK.md. Example: --prompt='Add unit tests for auth' |
295+
| `--prompt, -p` | - | Initial task for the agent. Saved to a unique file in .claude/ to avoid conflicts. Example: --prompt='Add unit tests for auth' |
296296
| `--prompt-file, -P` | - | Read the agent prompt from a file instead of command line |
297297
| `--multiplexer, -m` | - | Launch the agent in a specific multiplexer instead of the current terminal. Currently supported: tmux |
298298

0 commit comments

Comments
 (0)