Repository navigation
Description
Problem (one or two sentences)
Restoring a history task can apply its saved model profile globally after a different task has become current. The active profile can then disagree with the current task's saved profile, making the selected model misleading.
Context (who is affected and when)
Affects users who switch to or create another task while a history restore awaits profile loading or activation. An uncontended restore must still activate the restored task's profile normally.
Reproduction steps
Source-based interleaving to reproduce; not executed as part of this report:
- Have history task A saved with profile A and another task B with profile B.
- Begin restoring A; pause its asynchronous profile activation before the global writes complete.
- Switch to or create B so B becomes current with its own profile.
- Release A's delayed activation and wait for both operations to settle.
- Compare the final global profile name and provider settings, the UI's active profile, and the current task's saved profile.
Expected result
After any sequence of restores and task switches, the active profile shown in the UI matches the current task's profile. Final global profile name and provider settings must not retain a stale restore's profile.
Actual result
A delayed restore can write profile A globally after B becomes current. Task-local identity guards do not protect those earlier writes. The inspected fork also lacks the checkout's task-local guards, so effects can extend beyond stale global state.
App Version
Installed version not supplied. Inspected fork main: b32c5cf (package version 3.53.6). Local checkout HEAD: 22cb84b8bfd0b626308424037d96b0003cdbb379. Source-verified race report, not a runtime reproduction on that release.
Relevant logs or errors (optional)
No runtime logs were collected. Verified source locations and scope follow.
Inspected checkout: history restoration and global writes
ClineProvider.createTaskWithHistoryItem()usesClineProvider.enqueueHistoryTaskTransition(); restore runs inClineProvider.createTaskWithHistoryItemUnlocked().- Mode-profile activation passes the restored task identity at lines 1367–1370; saved task-profile activation does so at lines 1400–1407 in
ClineProvider.createTaskWithHistoryItemUnlocked(). ClineProvider.activateProviderProfile()queues profile mutation separately.ClineProvider.activateProviderProfileUnlocked()activates the profile at line 2232, then writes global metadata, current profile name, and provider settings at lines 2242–2246 without checking the expected current task identity.ClineProvider.updateTaskApiHandlerIfNeeded()checks that identity at line 1937.ClineProvider.persistStickyProviderProfileToCurrentTask()checks it at lines 2182–2184. These protect task-local updates, not global activation.- Rehydration detects a task change and returns at lines 1452–1459 in
ClineProvider.createTaskWithHistoryItemUnlocked(), after activation has written globals.ClineProvider.createTask()does not use the history transition queue. - Important distinction: the checkout already projects the current task's profile into main webview state at
ClineProvider.ts. This can mask stale globals in that UI path; it does not guard the writes. Do not assume all checkout UI paths still read globals.
Equivalent locations verified in this fork
The fork revision above lacks the checkout's history transition queue, unlocked helpers, and expected-current-task option. Task-local guards should not be described as already present in this fork.
ClineProvider.createTaskWithHistoryItem()restores the saved task profile at lines 894–908, disabling mode-default and sticky-history persistence but not global writes.ClineProvider.activateProviderProfile()writes global profile metadata, current profile name, and provider settings at lines 1495–1499 without a task-current check.- Task-handler update at lines 1507–1513 also lacks an expected-current-task guard. The fork's webview profile field at line 2050 comes from global state.
Fix constraints and possible directions
Simply skipping global activation is insufficient: a successful uncontended restore must still show its own profile. Routing all task creation through the history-task queue changes ordering for every caller.
Possible directions, not a prescribed implementation:
- Roll back or re-apply the appropriate global profile when the current task changes during restore.
- Make profile activation and the current-task check atomic within the existing history-task queue, accounting for task changes outside that queue. That queue is present in the inspected checkout, not this fork revision.
- Derive the UI's active profile from the current task instead of global state; also address remaining global-state consumers.
Regression coverage
Add a deterministic test interleaving history restore with a task switch. Release delayed activation, await settlement, and check final global profile name and provider settings against the current task's profile. Also check posted UI profile and that the newly current task's saved profile is not overwritten.
Keep an uncontended-restore test proving successful profile activation. Existing checkout identity-guard tests in ClineProvider.sticky-profile.spec.ts assert task-local writes, not the final global profile after this interleaving.
Activity