Skip to content

feat(TaskScheduler): fan-out — parent stays active while child runs - #1046

Draft
edelauna wants to merge 5 commits into
mainfrom
issue/369
Draft

feat(TaskScheduler): fan-out — parent stays active while child runs#1046
edelauna wants to merge 5 commits into
mainfrom
issue/369

Conversation

@edelauna

@edelauna edelauna commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Related GitHub Issue

Closes: #

Description

Test Procedure

Pre-Submission Checklist

  • Issue Linked: This PR is linked to an approved GitHub Issue (see "Related GitHub Issue" above).
  • Scope: My changes are focused on the linked issue (one major feature/fix per PR).
  • Self-Review: I have performed a thorough self-review of my code.
  • Testing: New and/or updated tests have been added to cover my changes (if applicable).
  • Visual Snapshot (UI changes only): If a user would notice this change at a glance (layout, theme tokens, brand elements, empty/error states), I've added or updated a *.visual.tsx snapshot in webview-ui/. See webview-ui/AGENTS.md → "When a UI change needs a snapshot".
  • Documentation Impact: I have considered if my changes require documentation updates (see "Documentation Updates" section below).
  • Contribution Guidelines: I have read and agree to the Contributor Guidelines.

Visual Snapshots

Videos (interaction / animation only)

Documentation Updates

Additional Notes

Get in Touch

Summary by CodeRabbit

  • New Features

    • Added fan-out task delegation coverage including xprofile variants.
    • Added configurable task scheduler max concurrency via the extension API.
    • Added a “currently running tasks” view.
    • Added queued, timeout-guarded provider profile/mode-switch handling and fan-out-safe state updates.
  • Bug Fixes

    • Prevented task delegation from leaking shared mode/profile/prompt/rate-limit settings between concurrent tasks.
    • Improved rollback correctness by restoring the parent or releasing reserved permits appropriately.
    • Enhanced stack eviction to support targeting a specific task.
  • Documentation

    • Added formal task-delegation specification, invariants, and validation guidance.
  • Tests

    • Expanded concurrency/delegation E2E and state-machine test coverage.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Task delegation now supports concurrent parent and child execution with reserved scheduler permits, task-local API configuration, serialized profile mutation, rollback handling, and configurable concurrency. Unit, state-machine, formal-model, and VS Code end-to-end tests cover the updated behavior.

Changes

Concurrent delegation fan-out

Layer / File(s) Summary
Scheduler reservations and API configuration
packages/types/src/api.ts, src/utils/TaskSemaphore.ts, src/core/task/TaskScheduler.ts, src/extension/api.ts, src/core/webview/ClineProvider.ts, src/core/task/__tests__/delegation-concurrent.spec.ts
Adds non-queuing permit reservation, reserved task execution, scheduler concurrency configuration, and mode API configuration application.
Task-local execution configuration
src/core/task/Task.ts, src/core/task/__tests__/Task.spec.ts, src/eslint-suppressions.json
Uses each task’s own mode and API configuration for prompts, context handling, and rate limiting.
Profile mutation and fan-out mode switching
src/core/webview/ClineProvider.ts, src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts, src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts
Serializes profile mutations and prevents fan-out mode changes from rebuilding or mutating the active parent task.
Concurrent delegation and rollback
src/core/webview/ClineProvider.ts, src/core/task/__tests__/delegation-concurrent.spec.ts, src/__tests__/provider-delegation.spec.ts, src/__tests__/helpers/provider-stub.ts
Preserves the parent during fan-out, starts children with reserved permits, supports task-specific eviction, and centralizes rollback behavior.
Delegation state-machine validation
src/core/task/__tests__/delegation-state-machine.spec.ts, docs/formal/task-delegation/*
Adds executable and TLA+ models for delegation phases, permit accounting, profile isolation, focus, history, and rollback invariants.
End-to-end fan-out scenarios
apps/vscode-e2e/src/fixtures/subtasks.ts, apps/vscode-e2e/src/suite/subtasks.test.ts
Adds concurrent and cross-profile fixtures and E2E coverage for parent follow-ups, child execution, and model-specific requests.

Estimated code review effort: 5 (Critical) | ~90+ minutes

Possibly related issues

Possibly related PRs

Suggested labels: awaiting-review

Suggested reviewers: navedmerchant, taltas

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is only the template and lacks the required issue link, implementation summary, and test procedure. Fill in the linked issue number, a brief how/why summary, concrete test steps, and any relevant notes or checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and matches the main fan-out concurrency change in the PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue/369

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

apps/vscode-e2e/src/suite/subtasks.test.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/__tests__/helpers/provider-stub.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.

src/__tests__/provider-delegation.spec.ts

ESLint skipped: the ESLint configuration for this file references a package that is not available in the sandbox.

  • 4 others

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.82143% with 17 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/core/webview/ClineProvider.ts 87.95% 2 Missing and 8 partials ⚠️
src/extension/api.ts 0.00% 4 Missing ⚠️
src/core/task/Task.ts 83.33% 0 Missing and 2 partials ⚠️
src/__tests__/helpers/provider-stub.ts 80.00% 0 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/core/webview/ClineProvider.ts (2)

1531-1601: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

targetTask=null doesn't protect the parent's API config, only its _taskMode.

The doc for the new targetTask param promises that passing null will "skip the per-task mutation and only apply the global mode/API-config side effects... without stomping the still-running parent's own _taskMode." But the task/targetTask gate only wraps the _taskMode + task-history mutation (the if (task) {...} block). The subsequent activateProviderProfile({ name: profile.name }) call (further down in this same function, when the new mode has a saved API config) is unconditional and internally rebuilds whichever task this.getCurrentTask() returns via updateTaskApiHandlerIfNeeded.

In the fan-out delegation path (delegateParentAndOpenChild), handleModeSwitch(requestedMode, null) runs before the child exists and before the parent is removed from the registry — so getCurrentTask() at that point is still the parent. If the child's requested mode has a different saved/sticky API config than the parent's, this silently rebuilds and swaps the running parent's apiConfiguration/API handler to the child's mode's provider settings mid-task, potentially rerouting the parent's next request to an unintended provider/model/key.

Fix direction: thread an explicit target (or "skip current-task rebuild") flag through activateProviderProfile/updateTaskApiHandlerIfNeeded instead of relying on getCurrentTask().

🐛 Sketch of one possible fix
-			if (hasActualSettings) {
-				await this.activateProviderProfile({ name: profile.name })
-			} else {
+			if (hasActualSettings) {
+				await this.activateProviderProfile({ name: profile.name }, { skipCurrentTaskApiRebuild: targetTask === null })
+			} else {
 				// The task will continue with the current/default configuration.
 			}

and in activateProviderProfile/updateTaskApiHandlerIfNeeded, skip the getCurrentTask() rebuild when that flag is set.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 1531 - 1601, Update
handleModeSwitch and the activateProviderProfile/updateTaskApiHandlerIfNeeded
flow so passing targetTask=null also skips rebuilding the current task’s API
configuration and handler. Thread an explicit skip-current-task-rebuild or
target-task control through these methods, ensuring delegation fan-out applies
global mode/config side effects without using getCurrentTask() to mutate the
still-running parent.

3666-3703: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Reserved permit (and parent) can be lost if createTask() throws.

childReservedRelease is captured at line 3627, but the only cleanup that releases it (fan-out) or restores the parent (non-fan-out) lives in the catch wrapping atomicReadAndUpdate starting at line 3703 — which only runs for errors after createTask() returns. The createTask() call itself (3679-3683) is not wrapped in try/catch, yet it has real unguarded throw paths (OrganizationAllowListViolationError, Task constructor validation, addClineToStack()/getState() failures).

If it throws:

  • Fan-out case: childReservedRelease is never released — a permanent concurrency-permit leak for the life of this TaskScheduler instance.
  • Non-fan-out case: the parent was already evicted in step 3 and is never restored — the user is left with no active task.
🐛 Proposed fix
-		const child = await this.createTask(message, undefined, parent as any, {
-			initialTodos,
-			initialStatus: "active",
-			startTask: false,
-		})
+		let child: Task
+		try {
+			child = await this.createTask(message, undefined, parent as any, {
+				initialTodos,
+				initialStatus: "active",
+				startTask: false,
+			})
+		} catch (err) {
+			// createTask() can throw before any cleanup below runs — release the
+			// reserved permit (fan-out) so it doesn't leak, and let the caller see the error.
+			childReservedRelease?.()
+			throw err
+		}

Note: the non-fan-out parent-restore case still needs equivalent handling here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 3666 - 3703, Wrap the
createTask call in the same failure-handling flow as atomicReadAndUpdate so any
exception during child creation triggers cleanup. In the catch path, invoke
childReservedRelease for fan-out and restore the evicted parent for non-fan-out,
while preserving existing post-creation rollback behavior and avoiding duplicate
cleanup.
🧹 Nitpick comments (1)
src/core/task/__tests__/delegation-concurrent.spec.ts (1)

136-163: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Fragile microtask-count synchronization instead of polling a deterministic signal.

Both tests occupy scheduler permits with never-resolving run() functions and then assume sem.acquire() has settled after a fixed number of await Promise.resolve() calls (2 ticks at Lines 149-150; 1 tick at Line 207, then 2 more at Lines 219-220). This hardcodes an implementation detail (exact microtask depth of sem.acquire()); if TaskSemaphore's internal promise chaining ever grows by one hop, these tests could start failing/flaking without a clear signal why. The atomic-reservation test at Lines 270-273 already demonstrates a more robust alternative: polling scheduler.available until it reflects the expected state.

♻️ Suggested pattern for deterministic settling
-		void scheduler.schedule(makeParent({ taskId: "occupant-1" }), () => new Promise<void>(() => {}))
-		void scheduler.schedule(makeParent({ taskId: "occupant-2" }), () => new Promise<void>(() => {}))
-		// Let both schedule() calls' internal sem.acquire() microtasks settle so
-		// both permits are actually held before we check availability.
-		await Promise.resolve()
-		await Promise.resolve()
+		void scheduler.schedule(makeParent({ taskId: "occupant-1" }), () => new Promise<void>(() => {}))
+		void scheduler.schedule(makeParent({ taskId: "occupant-2" }), () => new Promise<void>(() => {}))
+		// Poll the deterministic signal instead of assuming a fixed microtask depth.
+		while (scheduler.available > 0) {
+			await Promise.resolve()
+		}

Also applies to: 185-227

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/task/__tests__/delegation-concurrent.spec.ts` around lines 136 -
163, Replace the fixed Promise.resolve() microtask waits in the permit-occupancy
tests around callDelegate and the related concurrency cases with polling of
scheduler.available until it reaches the expected exhausted state, matching the
deterministic approach used by the atomic-reservation test. Keep the
never-resolving occupancy tasks and assertions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/core/webview/ClineProvider.ts`:
- Around line 1531-1601: Update handleModeSwitch and the
activateProviderProfile/updateTaskApiHandlerIfNeeded flow so passing
targetTask=null also skips rebuilding the current task’s API configuration and
handler. Thread an explicit skip-current-task-rebuild or target-task control
through these methods, ensuring delegation fan-out applies global mode/config
side effects without using getCurrentTask() to mutate the still-running parent.
- Around line 3666-3703: Wrap the createTask call in the same failure-handling
flow as atomicReadAndUpdate so any exception during child creation triggers
cleanup. In the catch path, invoke childReservedRelease for fan-out and restore
the evicted parent for non-fan-out, while preserving existing post-creation
rollback behavior and avoiding duplicate cleanup.

---

Nitpick comments:
In `@src/core/task/__tests__/delegation-concurrent.spec.ts`:
- Around line 136-163: Replace the fixed Promise.resolve() microtask waits in
the permit-occupancy tests around callDelegate and the related concurrency cases
with polling of scheduler.available until it reaches the expected exhausted
state, matching the deterministic approach used by the atomic-reservation test.
Keep the never-resolving occupancy tasks and assertions unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 038f0437-f573-41dc-b617-b71c4911555b

📥 Commits

Reviewing files that changed from the base of the PR and between dcaa3cb and 0cd7dea.

📒 Files selected for processing (10)
  • apps/vscode-e2e/src/fixtures/subtasks.ts
  • apps/vscode-e2e/src/suite/subtasks.test.ts
  • packages/types/src/api.ts
  • src/__tests__/provider-delegation.spec.ts
  • src/core/task/TaskScheduler.ts
  • src/core/task/__tests__/delegation-concurrent.spec.ts
  • src/core/webview/ClineProvider.ts
  • src/eslint-suppressions.json
  • src/extension/api.ts
  • src/utils/TaskSemaphore.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/core/task/__tests__/delegation-concurrent.spec.ts (1)

251-272: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Restore the vscode.window.showErrorMessage spy.

Unless a global restoreMocks/beforeEach resets it, this spy leaks into later tests in the file.

♻️ Restore after use
 		expect(showErrorMessage).toHaveBeenCalledWith(
 			"Failed to restore the parent task after subtask creation failed. Reopen the task from history to continue.",
 		)
+		showErrorMessage.mockRestore()
 	})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/task/__tests__/delegation-concurrent.spec.ts` around lines 251 -
272, Restore the vscode.window.showErrorMessage spy created in the test after
its assertions complete, ensuring cleanup runs even when the test fails. Update
the test around showErrorMessage and preserve the existing error and call-count
assertions.
src/core/webview/ClineProvider.ts (2)

213-236: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

A hung profile mutation silently blocks every later mode/profile switch.

The timeout only rejects the caller; providerProfileMutationQueue still chains on run, so if a mutation never settles the queue stalls forever with no signal. Consider logging when the timeout fires (and when the underlying op eventually settles) so this state is diagnosable.

♻️ Log on timeout
 	private withProviderProfileMutationTimeout<T>(operation: Promise<T>): Promise<T> {
 		let timeoutId: ReturnType<typeof setTimeout> | undefined
 		const timeout = new Promise<never>((_, reject) => {
 			timeoutId = setTimeout(() => {
+				this.log(
+					`[providerProfileMutation] mutation exceeded ${ClineProvider.PENDING_OPERATION_TIMEOUT_MS}ms; queued mutations remain blocked until it settles`,
+				)
 				reject(new Error("Provider profile mutation timed out"))
 			}, ClineProvider.PENDING_OPERATION_TIMEOUT_MS)
 		})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 213 - 236, Update
enqueueProviderProfileMutation and withProviderProfileMutationTimeout so a
timeout emits a diagnostic log identifying the hung provider profile mutation,
and also log when the underlying operation eventually settles after timing out.
Preserve the existing caller rejection, queue chaining, and timeout cleanup
behavior.

3645-3679: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Duplicated restore block; a small loop reads better.

The two getTaskWithId + createTaskWithHistoryItem blocks are identical apart from log wording.

♻️ Retry loop
-		if (!fanOut) {
-			try {
-				const { historyItem: parentHistory } = await this.getTaskWithId(parentTaskId)
-				await this.createTaskWithHistoryItem(parentHistory)
-			} catch (firstRollbackError) {
-				...
-			}
-		} else {
+		if (!fanOut) {
+			for (let attempt = 1; attempt <= 2; attempt++) {
+				try {
+					const { historyItem: parentHistory } = await this.getTaskWithId(parentTaskId)
+					await this.createTaskWithHistoryItem(parentHistory)
+					return
+				} catch (error) {
+					this.log(
+						`[delegateParentAndOpenChild] Failed to restore parent ${parentTaskId} during rollback (attempt ${attempt}/2): ${
+							(error as Error)?.message ?? String(error)
+						}`,
+					)
+				}
+			}
+			vscode.window.showErrorMessage(
+				"Failed to restore the parent task after subtask creation failed. Reopen the task from history to continue.",
+			)
+		} else {
 			childReservedRelease?.()
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/webview/ClineProvider.ts` around lines 3645 - 3679, Refactor the
non-fan-out branch of restoreParentOrReleasePermit into a small bounded retry
loop around getTaskWithId and createTaskWithHistoryItem, preserving the
initial-attempt and single-retry behavior. Keep distinct logging for the first
failure and retry failure, and retain the existing showErrorMessage handling
after both attempts fail; leave the fan-out permit release path unchanged.
src/__tests__/provider-delegation.spec.ts (1)

9-26: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicate private-method binding logic.

bindRestoreParentOrReleasePermit re-implements the same proto.restoreParentOrReleasePermit.bind(s) pattern already centralized in src/__tests__/helpers/provider-stub.ts (makeProviderStub). Two copies of this binding logic now need to stay in sync if the private method's signature changes.

Consider exporting a shared binder from provider-stub.ts (or having this spec build its providers through makeProviderStub) instead of re-declaring the cast/bind locally.

♻️ Possible consolidation
-function bindRestoreParentOrReleasePermit(provider: ClineProvider): void {
-	type WithRestore = {
-		restoreParentOrReleasePermit: (
-			parentTaskId: string,
-			fanOut: boolean,
-			childReservedRelease: (() => void) | undefined,
-		) => Promise<void>
-	}
-	;(provider as unknown as WithRestore).restoreParentOrReleasePermit = (
-		ClineProvider.prototype as unknown as WithRestore
-	).restoreParentOrReleasePermit.bind(provider)
-}
+// import { bindRestoreParentOrReleasePermit } from "./helpers/provider-stub"

Run this to confirm whether the binder already exists/could be exported from provider-stub.ts:

#!/bin/bash
rg -n 'restoreParentOrReleasePermit' src/__tests__/helpers/provider-stub.ts src/__tests__/provider-delegation.spec.ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/__tests__/provider-delegation.spec.ts` around lines 9 - 26, Remove the
local bindRestoreParentOrReleasePermit implementation from
provider-delegation.spec.ts and reuse a shared binder exported from
makeProviderStub’s provider-stub helper, or construct these providers through
makeProviderStub. Ensure restoreParentOrReleasePermit binding and its signature
have a single centralized implementation.
apps/vscode-e2e/src/suite/subtasks.test.ts (1)

78-97: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Aimock journal is read/matched without per-test scoping.

readAimockJournal fetches the full server-side journal on every poll and waitForAimockRequest/assertAimockRequest match against it without filtering to the current test's requests. Since the aimock server likely persists its journal across the whole suite run, this risks matching stale entries from an earlier test if predicates aren't sufficiently unique (here they happen to rely on the newly-added entry.body.model, which is unique to this test, so the risk is currently mitigated in practice, but the helper itself provides no structural guarantee against pulling in older requests).
As per coding guidelines, "For fetch-interceptor test suites, reset in-memory request/event capture in setup() or allocate a fresh per-test buffer instead of reusing shared mutable state; scope request-shape assertions to the current probe or test tag only; do not pull in older requests."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/vscode-e2e/src/suite/subtasks.test.ts` around lines 78 - 97, Scope
Aimock journal assertions to the current test in waitForAimockRequest and
assertAimockRequest rather than matching the full persisted journal. Reset or
establish a fresh per-test request buffer during setup, and update
readAimockJournal or the matching flow to exclude entries from earlier tests
while preserving polling behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/vscode-e2e/src/suite/subtasks.test.ts`:
- Around line 255-256: Validate the results of both upsertProfile calls for the
parent and child profiles before using them to populate modeApiConfigs. Fail
immediately with a clear error if either result is undefined, and remove the
non-null assertions while preserving the existing successful-profile flow.

In `@src/core/task/__tests__/delegation-concurrent.spec.ts`:
- Around line 148-152: Bound both scheduler polling loops in
delegation-concurrent.spec.ts: lines 148-152 waiting for scheduler.available > 0
and lines 316-318 waiting for scheduler.available > 1. Use the bounded polling
pattern demonstrated by the childStarted loop, preserving the existing await
behavior while ensuring a regression exits the loop and allows the test
assertion to fail.

In `@src/core/task/Task.ts`:
- Around line 3786-3795: The task-local values are only introduced in the shown
scope but shared-state mode and apiConfiguration are still used elsewhere in
attemptApiRequest(). Replace every provider-state read passed to
buildNativeToolsArrayWithRestrictions, metadata.mode, and the Gemini
allowedFunctionNames path with the local await this.getTaskMode() and
this.apiConfiguration values, ensuring delegated requests consistently use the
current Task instance’s configuration.

---

Nitpick comments:
In `@apps/vscode-e2e/src/suite/subtasks.test.ts`:
- Around line 78-97: Scope Aimock journal assertions to the current test in
waitForAimockRequest and assertAimockRequest rather than matching the full
persisted journal. Reset or establish a fresh per-test request buffer during
setup, and update readAimockJournal or the matching flow to exclude entries from
earlier tests while preserving polling behavior.

In `@src/__tests__/provider-delegation.spec.ts`:
- Around line 9-26: Remove the local bindRestoreParentOrReleasePermit
implementation from provider-delegation.spec.ts and reuse a shared binder
exported from makeProviderStub’s provider-stub helper, or construct these
providers through makeProviderStub. Ensure restoreParentOrReleasePermit binding
and its signature have a single centralized implementation.

In `@src/core/task/__tests__/delegation-concurrent.spec.ts`:
- Around line 251-272: Restore the vscode.window.showErrorMessage spy created in
the test after its assertions complete, ensuring cleanup runs even when the test
fails. Update the test around showErrorMessage and preserve the existing error
and call-count assertions.

In `@src/core/webview/ClineProvider.ts`:
- Around line 213-236: Update enqueueProviderProfileMutation and
withProviderProfileMutationTimeout so a timeout emits a diagnostic log
identifying the hung provider profile mutation, and also log when the underlying
operation eventually settles after timing out. Preserve the existing caller
rejection, queue chaining, and timeout cleanup behavior.
- Around line 3645-3679: Refactor the non-fan-out branch of
restoreParentOrReleasePermit into a small bounded retry loop around
getTaskWithId and createTaskWithHistoryItem, preserving the initial-attempt and
single-retry behavior. Keep distinct logging for the first failure and retry
failure, and retain the existing showErrorMessage handling after both attempts
fail; leave the fan-out permit release path unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 844dfb75-f9db-47b2-b90f-4708230169b0

📥 Commits

Reviewing files that changed from the base of the PR and between 0cd7dea and 5e899f3.

📒 Files selected for processing (15)
  • apps/vscode-e2e/src/fixtures/subtasks.ts
  • apps/vscode-e2e/src/suite/subtasks.test.ts
  • docs/formal/task-delegation/README.md
  • docs/formal/task-delegation/TaskDelegation.cfg
  • docs/formal/task-delegation/TaskDelegation.tla
  • src/__tests__/helpers/provider-stub.ts
  • src/__tests__/provider-delegation.spec.ts
  • src/core/task/Task.ts
  • src/core/task/__tests__/Task.spec.ts
  • src/core/task/__tests__/delegation-concurrent.spec.ts
  • src/core/task/__tests__/delegation-state-machine.spec.ts
  • src/core/webview/ClineProvider.ts
  • src/core/webview/__tests__/ClineProvider.apiHandlerRebuild.spec.ts
  • src/eslint-suppressions.json
  • src/extension/api.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/eslint-suppressions.json

Comment thread apps/vscode-e2e/src/suite/subtasks.test.ts
Comment thread src/core/task/__tests__/delegation-concurrent.spec.ts
Comment thread src/core/task/Task.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/task/__tests__/Task.spec.ts (1)

462-481: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert task-local API configuration in this prompt test.

This test only distinguishes and asserts mode; it still passes if getSystemPrompt() regresses to provider-state apiConfiguration. Make a prompt option such as todoListEnabled differ and assert the SYSTEM_PROMPT options argument uses the task value.

As per coding guidelines, **/*.{test,spec}.{ts,tsx,js} must use package-local unit tests for state transitions and request construction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/task/__tests__/Task.spec.ts` around lines 462 - 481, The
getSystemPrompt test currently verifies only task-local mode and does not
protect task-local apiConfiguration. Update the test around
getTaskTestAccess(cline).getSystemPrompt() to give the task a distinct
apiConfiguration option such as todoListEnabled, then assert the SYSTEM_PROMPT
options argument uses that task value rather than mockProvider state; keep the
existing mode assertions intact.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/task/Task.ts`:
- Around line 4030-4033: Scope ProviderProfileChanged handling in
Task.setupProviderProfileChangeListener to the task affected by the profile
change, preventing updates from overwriting unrelated live tasks’
configurations; the request construction around getTaskMode and
this.apiConfiguration should then retain each task’s original metadata. In
src/core/task/Task.ts lines 4030-4033, ensure the request uses the task-isolated
configuration. In src/core/task/__tests__/Task.spec.ts lines 602-651, add a
package-local unit test that creates tasks with distinct configurations, emits a
profile-change event, and verifies the parent request metadata remains
unchanged.

---

Outside diff comments:
In `@src/core/task/__tests__/Task.spec.ts`:
- Around line 462-481: The getSystemPrompt test currently verifies only
task-local mode and does not protect task-local apiConfiguration. Update the
test around getTaskTestAccess(cline).getSystemPrompt() to give the task a
distinct apiConfiguration option such as todoListEnabled, then assert the
SYSTEM_PROMPT options argument uses that task value rather than mockProvider
state; keep the existing mode assertions intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fae2acc2-f12d-465b-9003-e1e5bfd14434

📥 Commits

Reviewing files that changed from the base of the PR and between 5e899f3 and 17109ab.

📒 Files selected for processing (7)
  • apps/vscode-e2e/src/suite/subtasks.test.ts
  • src/__tests__/helpers/provider-stub.ts
  • src/__tests__/provider-delegation.spec.ts
  • src/core/task/Task.ts
  • src/core/task/__tests__/Task.spec.ts
  • src/core/task/__tests__/delegation-concurrent.spec.ts
  • src/core/webview/__tests__/ClineProvider.lockApiConfig.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/vscode-e2e/src/suite/subtasks.test.ts

Comment thread src/core/task/Task.ts
Comment on lines +4030 to +4033
// mode/apiConfiguration come from this task's own fields, not shared
// provider state — see getSystemPrompt() for why.
const mode = await this.getTaskMode()
const apiConfiguration = this.apiConfiguration

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Keep task API configuration isolated after profile-change events.

Reading this.apiConfiguration here is insufficient: Task.setupProviderProfileChangeListener() at src/core/task/Task.ts Lines 705-710 updates every live task from shared provider state after ProviderProfileChanged. Switching a child profile can therefore overwrite the concurrently running parent’s model, provider, and rate-limit configuration before this request is built.

  • src/core/task/Task.ts#L4030-L4033: scope profile-change updates to the affected task, rather than broadcasting the newly active provider configuration to all tasks.
  • src/core/task/__tests__/Task.spec.ts#L602-L651: emit a profile-change event after creating tasks with distinct configurations, then assert the parent’s request metadata remains tied to its original configuration.

As per coding guidelines, **/*.{test,spec}.{ts,tsx,js} must use package-local unit tests for state transitions and request construction.

📍 Affects 2 files
  • src/core/task/Task.ts#L4030-L4033 (this comment)
  • src/core/task/__tests__/Task.spec.ts#L602-L651
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/task/Task.ts` around lines 4030 - 4033, Scope ProviderProfileChanged
handling in Task.setupProviderProfileChangeListener to the task affected by the
profile change, preventing updates from overwriting unrelated live tasks’
configurations; the request construction around getTaskMode and
this.apiConfiguration should then retain each task’s original metadata. In
src/core/task/Task.ts lines 4030-4033, ensure the request uses the task-isolated
configuration. In src/core/task/__tests__/Task.spec.ts lines 602-651, add a
package-local unit test that creates tasks with distinct configurations, emits a
profile-change event, and verifies the parent request metadata remains
unchanged.

Source: Coding guidelines

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant