Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit c7021f5

Browse files
committed
fix: correct queue advancement off-by-one and child mode tracking in advanceSubtaskQueue
- Fix off-by-one: dispatch subtaskQueue[currentIndex] instead of subtaskQueue[nextIndex], preventing the first queued item from being skipped - Fix completedMode: fetch child history to get the child actual mode instead of incorrectly using the parent historyItem.mode - Update tests to reflect corrected queue semantics (subtaskQueueIndex represents the next item to dispatch, not the currently running item)
1 parent 35f04e6 commit c7021f5

2 files changed

Lines changed: 32 additions & 24 deletions

File tree

src/__tests__/sequential-fan-out.spec.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ describe("advanceSubtaskQueue", () => {
6464
getCurrentTask: vi.fn().mockReturnValue({ taskId: "child-1" }),
6565
removeClineFromStack: vi.fn().mockResolvedValue(undefined),
6666
getTaskWithId: vi.fn().mockResolvedValue({
67-
historyItem: makeHistoryItem({ id: "child-1", status: "active" }),
67+
historyItem: makeHistoryItem({ id: "child-1", mode: "code", status: "active" }),
6868
}),
6969
updateTaskHistory: vi.fn().mockResolvedValue(undefined),
7070
handleModeSwitch: vi.fn().mockResolvedValue(undefined),
@@ -73,6 +73,8 @@ describe("advanceSubtaskQueue", () => {
7373
log: vi.fn(),
7474
}
7575

76+
// Queue items represent ADDITIONAL subtasks after the initial child.
77+
// subtaskQueueIndex=0 means queue[0] is the next to dispatch.
7678
const subtaskQueue: SubtaskQueueItem[] = [
7779
{ mode: "code", message: "Step 1" },
7880
{ mode: "debug", message: "Step 2" },
@@ -88,7 +90,7 @@ describe("advanceSubtaskQueue", () => {
8890
const result = await (ClineProvider.prototype as any).advanceSubtaskQueue.call(provider, {
8991
parentTaskId: "parent-1",
9092
childTaskId: "child-1",
91-
completionResultSummary: "Step 1 done",
93+
completionResultSummary: "Initial task done",
9294
historyItem,
9395
})
9496

@@ -102,11 +104,11 @@ describe("advanceSubtaskQueue", () => {
102104
expect.objectContaining({ id: "child-1", status: "completed" }),
103105
)
104106

105-
// Should have switched mode to next subtask's mode
106-
expect(provider.handleModeSwitch).toHaveBeenCalledWith("debug")
107+
// Should have switched mode to queue[0]'s mode (the next item to dispatch)
108+
expect(provider.handleModeSwitch).toHaveBeenCalledWith("code")
107109

108-
// Should have created the next child with the queued message
109-
expect(provider.createTask).toHaveBeenCalledWith("Step 2", undefined, undefined, {
110+
// Should have created the next child with queue[0]'s message
111+
expect(provider.createTask).toHaveBeenCalledWith("Step 1", undefined, undefined, {
110112
initialTodos: [],
111113
initialStatus: "active",
112114
startTask: false,
@@ -115,12 +117,13 @@ describe("advanceSubtaskQueue", () => {
115117
// Should have started the next child
116118
expect(mockChild.start).toHaveBeenCalled()
117119

118-
// Should have updated parent with advanced queue index
120+
// Should have updated parent with advanced queue index (0 -> 1)
121+
// completedMode comes from child's history (mode: "code")
119122
expect(provider.updateTaskHistory).toHaveBeenCalledWith(
120123
expect.objectContaining({
121124
id: "parent-1",
122125
subtaskQueueIndex: 1,
123-
subtaskResults: [{ taskId: "child-1", mode: "unknown", summary: "Step 1 done" }],
126+
subtaskResults: [{ taskId: "child-1", mode: "code", summary: "Initial task done" }],
124127
awaitingChildId: "child-2",
125128
delegatedToId: "child-2",
126129
}),
@@ -131,7 +134,7 @@ describe("advanceSubtaskQueue", () => {
131134
RooCodeEventName.TaskDelegationCompleted,
132135
"parent-1",
133136
"child-1",
134-
"Step 1 done",
137+
"Initial task done",
135138
)
136139
expect(emitSpy).toHaveBeenCalledWith(RooCodeEventName.TaskDelegated, "parent-1", "child-2")
137140
})
@@ -141,7 +144,7 @@ describe("advanceSubtaskQueue", () => {
141144
getCurrentTask: vi.fn().mockReturnValue({ taskId: "child-2" }),
142145
removeClineFromStack: vi.fn().mockResolvedValue(undefined),
143146
getTaskWithId: vi.fn().mockResolvedValue({
144-
historyItem: makeHistoryItem({ id: "child-2", status: "active" }),
147+
historyItem: makeHistoryItem({ id: "child-2", mode: "code", status: "active" }),
145148
}),
146149
updateTaskHistory: vi.fn().mockResolvedValue(undefined),
147150
handleModeSwitch: vi.fn(),
@@ -151,11 +154,13 @@ describe("advanceSubtaskQueue", () => {
151154
formatAggregatedQueueResults: (ClineProvider.prototype as any).formatAggregatedQueueResults,
152155
}
153156

157+
// Queue has 1 item, subtaskQueueIndex=1 means queue[0] was already dispatched.
158+
// Now that child completes and the queue is exhausted.
154159
const subtaskQueue: SubtaskQueueItem[] = [{ mode: "code", message: "Step 1" }]
155160

156161
const historyItem = makeHistoryItem({
157162
subtaskQueue,
158-
subtaskQueueIndex: 0,
163+
subtaskQueueIndex: 1,
159164
subtaskResults: [{ taskId: "child-1", mode: "code", summary: "Step 1 done" }],
160165
childIds: ["child-1", "child-2"],
161166
})

src/core/webview/ClineProvider.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3150,25 +3150,22 @@ export class ClineProvider
31503150
return { handled: false, aggregatedSummary: completionResultSummary }
31513151
}
31523152

3153+
// currentIndex is the next queue item to dispatch (0-based).
3154+
// When the initial child (from mode/message params) completes, currentIndex is 0,
3155+
// meaning queue[0] should be dispatched first.
31533156
const currentIndex = subtaskQueueIndex ?? 0
3154-
const nextIndex = currentIndex + 1
3155-
3156-
// Record this child's result
3157-
const completedMode = historyItem.mode ?? "unknown"
3158-
const updatedResults = [
3159-
...(subtaskResults ?? []),
3160-
{ taskId: childTaskId, mode: completedMode, summary: completionResultSummary },
3161-
]
31623157

31633158
// Close current child if still open
31643159
const current = this.getCurrentTask()
31653160
if (current?.taskId === childTaskId) {
31663161
await this.removeClineFromStack()
31673162
}
31683163

3169-
// Mark child as completed
3164+
// Fetch child history to get the child's actual mode and mark it completed
3165+
let completedMode = "unknown"
31703166
try {
31713167
const { historyItem: childHistory } = await this.getTaskWithId(childTaskId)
3168+
completedMode = childHistory.mode ?? "unknown"
31723169
await this.updateTaskHistory({ ...childHistory, status: "completed" })
31733170
} catch (err) {
31743171
this.log(
@@ -3178,18 +3175,24 @@ export class ClineProvider
31783175
)
31793176
}
31803177

3178+
// Record this child's result using the child's actual mode
3179+
const updatedResults = [
3180+
...(subtaskResults ?? []),
3181+
{ taskId: childTaskId, mode: completedMode, summary: completionResultSummary },
3182+
]
3183+
31813184
// Emit completion event for the finished child
31823185
try {
31833186
this.emit(RooCodeEventName.TaskDelegationCompleted, parentTaskId, childTaskId, completionResultSummary)
31843187
} catch {
31853188
// non-fatal
31863189
}
31873190

3188-
if (nextIndex <= subtaskQueue.length - 1) {
3191+
if (currentIndex < subtaskQueue.length) {
31893192
// More subtasks in queue — start the next one
3190-
const nextSubtask = subtaskQueue[nextIndex]
3193+
const nextSubtask = subtaskQueue[currentIndex]
31913194
this.log(
3192-
`[advanceSubtaskQueue] Auto-advancing queue: subtask ${nextIndex + 1}/${subtaskQueue.length} (mode: ${nextSubtask.mode})`,
3195+
`[advanceSubtaskQueue] Auto-advancing queue: subtask ${currentIndex + 1}/${subtaskQueue.length} (mode: ${nextSubtask.mode})`,
31933196
)
31943197

31953198
// Switch mode
@@ -3219,7 +3222,7 @@ export class ClineProvider
32193222
awaitingChildId: nextChild.taskId,
32203223
childIds,
32213224
subtaskQueue,
3222-
subtaskQueueIndex: nextIndex,
3225+
subtaskQueueIndex: currentIndex + 1,
32233226
subtaskResults: updatedResults,
32243227
})
32253228

0 commit comments

Comments
 (0)