diff --git a/src/ClaudeDo.Data/PromptFiles.cs b/src/ClaudeDo.Data/PromptFiles.cs index 402e8940..18b3d154 100644 --- a/src/ClaudeDo.Data/PromptFiles.cs +++ b/src/ClaudeDo.Data/PromptFiles.cs @@ -470,10 +470,12 @@ public static class PromptFiles Then, for each surviving task: - Idle or Failed → update_task_status(id, "Queued"). For a Failed task ask first whether to reset_failed_task and re-queue it, or skip it. - Queued → leave it; it is already waiting for a slot. - - Running or WaitingForChildren → leave it; only poll. + - Running or WaitingForChildren → leave it; the wait below covers it. - WaitingForReview → leave it; it goes straight to Phase 4. - Call wait_for_task_change with the ids of every task still Queued or Running (timeoutSeconds up to 170) instead of sleeping and polling get_task yourself. It returns as soon as any of them leaves Queued/Running — WaitingForReview on success, Failed on error — or reports timedOut if none did. Report progress as tasks land, then call it again with whatever ids are still Queued/Running until none remain. + Then wait with wait_for_task_change instead of sleeping and polling get_task yourself. Pass the ids of every task not yet in WaitingForReview or a terminal status — Queued, Running and WaitingForChildren alike — and set treatWaitingForChildrenAsBusy=true. Without that flag a task with children returns the moment it goes Running → WaitingForChildren, while its children are still working, and you would walk into Phase 4 with unfinished work. Use timeoutSeconds 900: the server clamps there anyway, and ClaudeDo's launchers already raise MCP_TOOL_TIMEOUT above it, so one long wait costs one turn where six short ones cost six. + + It returns as soon as a task reaches WaitingForReview or fails, or reports timedOut if none did. Report progress as tasks land, then call it again with whatever ids are still outstanding until none remain. ## Phase 4 — Review and merge Before merging anything, call preview_merge_set with every surviving task's id (the same targetBranch you are about to merge into). It tells you, per task, whether a clean merge-tree preview is even possible (status/conflictFiles/changedFileCount/behind) and which files more than one of the tasks changed (overlaps). Read the overlaps: a file two tasks both touch is where a same-branch collision could happen. This is a HINT, not proof — it only catches same-file overlap, not a cross-file break (e.g. one task deletes a symbol another task's file still references), and a clean preview never guarantees the result compiles or passes tests. Use it to decide merge order and to know which pairs to look at extra carefully in step 1 below; it does not replace reading the diffs. diff --git a/tests/ClaudeDo.Data.Tests/PromptFilesTests.cs b/tests/ClaudeDo.Data.Tests/PromptFilesTests.cs index 43b8f26f..8cf050de 100644 --- a/tests/ClaudeDo.Data.Tests/PromptFilesTests.cs +++ b/tests/ClaudeDo.Data.Tests/PromptFilesTests.cs @@ -143,6 +143,27 @@ public class PromptFilesTests Assert.DoesNotContain("run_task_now(", d); // single override slot — must not batch-start } + [Fact] + public void DefaultFor_merge_helper_execute_waits_with_the_real_server_side_cap() + { + // TaskWaitMcpTools.MaxTimeoutSeconds is 900 and every ClaudeDo launcher sets + // MCP_TOOL_TIMEOUT=930000ms. The prompt used to say 170 -- a leftover from the retired + // 200000ms era -- which burned ~5x the turns on re-waiting. + var d = PromptFiles.DefaultFor(PromptKind.MergeHelperExecute); + Assert.Contains("timeoutSeconds 900", d); + Assert.DoesNotContain("170", d); + } + + [Fact] + public void DefaultFor_merge_helper_execute_waits_through_waiting_for_children() + { + // Without the flag, a parent with children reports "changed" as soon as it reaches + // WaitingForChildren, so the handler would advance to review/merge while children run. + var d = PromptFiles.DefaultFor(PromptKind.MergeHelperExecute); + Assert.Contains("treatWaitingForChildrenAsBusy=true", d); + Assert.Contains("WaitingForChildren", d); + } + [Fact] public void DefaultFor_merge_helper_execute_keeps_the_shared_checkout_git_guard() {