bug(workflow): structured plan children cannot set cwd — parallel writes rejected in multi-repo workspaces
- Dominant language
- Rust
- Stars
- 41k
- Forks
- 3.6k
- Avg merge
- 13h 59m
- Merged PRs (30d)
- 299
Description
## Description
Running a structured workflow plan (`goal` + `children`) with `risk: writes` from a workspace that is not itself a git checkout, but contains several repositories, rejects every child before dispatch:
```
dispatch failed for s1-daw-assist: spawn rejected: Failed to validate input:
Multiple git repositories found under /home/f2. Specify cwd to disambiguate:
/home/f2/qmk_firmware, /home/f2/awakened-poe-trade-git, /home/f2/Kokoro-82M, /home/f2/open-skills
parallel(): every slot failed (5 of 5); no work survived this fan-out
```
The remedy the error names — "Specify cwd" — cannot be carried out from a structured plan: plan children have no `cwd` field. The script surface gained that field with #4889 (the fix for #4818), and the docs now state that `cwd` "is required when a workflow is launched from a multi-repository workspace and the child needs shell or file access" — but the structured plan, described in the same document as the "preferred agent path", has no way to supply it.
For this workspace shape the preferred path therefore cannot run parallel writes at all; the operator has to fall back to the script form (`task({ cwd })`) or to direct agent calls.
## Steps to reproduce
1. Use a workspace whose root is not a git repository but contains several nested ones — a home directory holding clones is the ordinary case.
2. Call the workflow tool with a structured plan: `risk: writes`, two or more children in one parallel phase, each with `file_scope`.
3. Every child is rejected before it starts with the "Multiple git repositories found under …" validation error; nothing runs.
## Expected behavior
Any one of these would resolve it:
- plan children accept `cwd` (repository-relative, as `task()` does), so the ambiguity is resolvable from the plan itself;
- or, if the plan path genuinely cannot express it, the refusal names what actually helps there — set `[workflow] max_parallel_writes_without_worktree` above the number of parallel writers, or use the script form;
- and a `cwd` key supplied in a plan child must not be dropped silently.
## Actual behavior
`StructuredPlanChild` (`crates/tui/src/tools/workflow/mod.rs:2649-2671`) accepts `id`, `label`, `prompt` (alias `description`), `agent_type` (alias `type`), `role`, `profile`, `model`, `mode`, `file_scope` — no `cwd`, and the struct does not use `deny_unknown_fields`, so a `cwd` key in a child is ignored in silence rather than rejected.
The worktree requirement comes from the default `max_parallel_writes_without_worktree = 0` (`crates/config/src/lib.rs:2527-2529`; the field's own doc comment: "0 forces worktree isolation for parallel writes", from #4120), so parallel write children are isolated and the worktree resolver must pick a repository. Its discovery walks the parent workspace and fails on ambiguity — `crates/tui/src/tools/subagent/worktree.rs:168-171`:
```rust
return Err(ToolError::invalid_input(format!(
"Multiple git repositories found under {}. Specify cwd to disambiguate: {repos}",
dir.display()
)));
```
The documented requirement lives on the script surface only: `docs/WORKFLOW_AUTHORING.md:99-101` and `docs/AUTOMATIC_WORKFLOWS.md:73-76` ("a child that needs shell or file access must set `cwd` to the repository-relative directory it should use").
## Impact
- The "preferred agent path" is unusable for parallel writes in any multi-repo parent directory: 5 of 5 children rejected in ~32 ms, and the work had to move to direct agent calls.
- The error sends the operator after a field the plan schema does not have — the same class of unreachable remedy that #6123 fixed for write-scope contention.
- The silent drop of `cwd` is worse than a refusal: an operator who adds the key believes the ambiguity is resolved.
Workaround, verified locally: raise `[workflow] max_parallel_writes_without_worktree` to at least the number of parallel writers (`codewhale config get workflow` then reports the effective value), or use the script form's `task({ cwd })`.
## Environment
- OS: Arch Linux
- codewhale version: 0.9.13 (dev)
- Install method: source build
- Terminal app: tmux (`TERM=tmux-256color`)
- Shell: fish
- Workspace: a home directory containing four git repositories, no repository at the root
## Logs, screenshots, or recordings
The dispatch rejection quoted above; run id `workflow_0b1ca0d0`, failed in 32 ms with `parallel(): every slot failed (5 of 5); no work survived this fan-out`.
Related: #4818 and its fix PR #4889 (`cwd` added to `task()`; the structured plan was not covered), #4120 (parallel write children default to worktree isolation), #6123 (unreachable remedy text in a sibling refusal).
Contributor guide
Research direction
Start with StructuredPlanChild in crates/tui/src/tools/workflow/mod.rs and compare its fields with task({ cwd }). Reproduce the multi-repository validation failure, then inspect crates/tui/src/tools/subagent/worktree.rs and the workflow documentation at docs/WORKFLOW_AUTHORING.md and docs/AUTOMATIC_WORKFLOWS.md. Done means the plan can resolve repository ambiguity or its refusal accurately names a usable workaround, without silently dropping cwd.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- cli, tooling
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 58/100