Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/types/src/global-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const globalSettingsSchema = z.object({
lastShownAnnouncementId: z.string().optional(),
customInstructions: z.string().optional(),
taskHistory: z.array(historyItemSchema).optional(),
currentActiveTaskId: z.string().optional(),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Could we add a more descriptive JSDoc comment here? Something like:

Suggested change
currentActiveTaskId: z.string().optional(),
/**
* The ID of the currently active task that should be restored when VS Code restarts.
* This ensures chat context persistence across unexpected crashes or restarts.
* @since 3.26.7
*/
currentActiveTaskId: z.string().optional(),


// Image generation settings (experimental) - flattened for simplicity
openRouterImageApiKey: z.string().optional(),
Expand Down
28 changes: 28 additions & 0 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,9 @@ export class ClineProvider
this.clineStack.push(task)
task.emit(RooCodeEventName.TaskFocused)

// Persist the current active task ID
await this.updateGlobalState("currentActiveTaskId", task.taskId)

// Perform special setup provider specific tasks.
await this.performPreparationTasks(task)

Expand Down Expand Up @@ -417,6 +420,15 @@ export class ClineProvider
// garbage collected.
task = undefined
}

// Clear the current active task ID if no tasks remain
if (this.clineStack.length === 0) {
await this.updateGlobalState("currentActiveTaskId", undefined)
} else {
// Update to the new top task
const newCurrentTask = this.clineStack[this.clineStack.length - 1]
await this.updateGlobalState("currentActiveTaskId", newCurrentTask.taskId)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this intentional? There's a potential race condition here between checking the stack length and updating the task ID. If another operation modifies the stack between lines 425 and 430, we might update to the wrong task ID. Consider using a lock or making this operation atomic.

}
}

getTaskStackSize(): number {
Expand Down Expand Up @@ -725,6 +737,22 @@ export class ClineProvider

// If the extension is starting a new session, clear previous task state.
await this.removeClineFromStack()

// Attempt to restore the last active task if one was persisted
const lastActiveTaskId = this.getGlobalState("currentActiveTaskId")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The task restoration logic could benefit from explicit cleanup if restoration fails partway through. Consider wrapping this in a try-finally block to ensure any partially initialized resources are properly released on error:

Suggested change
const lastActiveTaskId = this.getGlobalState("currentActiveTaskId")
const lastActiveTaskId = this.getGlobalState("currentActiveTaskId")
if (lastActiveTaskId && typeof lastActiveTaskId === "string") {
let partiallyRestoredTask = null
try {
const { historyItem } = await this.getTaskWithId(lastActiveTaskId)
if (historyItem) {
partiallyRestoredTask = await this.createTaskWithHistoryItem(historyItem)
this.log(`Restored last active task: ${lastActiveTaskId}`)
}
} catch (error) {
// Task may have been deleted or corrupted, clear the saved ID
this.log(`Failed to restore last active task ${lastActiveTaskId}: ${error}`)
await this.updateGlobalState("currentActiveTaskId", undefined)
} finally {
// Clean up any partially restored task if needed
if (partiallyRestoredTask && this.clineStack.length === 0) {
// Cleanup logic here if needed
}
}
}

if (lastActiveTaskId && typeof lastActiveTaskId === "string") {
try {
const { historyItem } = await this.getTaskWithId(lastActiveTaskId)
if (historyItem) {
await this.createTaskWithHistoryItem(historyItem)
this.log(`Restored last active task: ${lastActiveTaskId}`)
}
} catch (error) {
// Task may have been deleted or corrupted, clear the saved ID
this.log(`Failed to restore last active task ${lastActiveTaskId}: ${error}`)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Consider adding more detailed logging here for debugging purposes. Including task details in the error log would help diagnose restoration issues:

Suggested change
this.log(`Failed to restore last active task ${lastActiveTaskId}: ${error}`)
// Task may have been deleted or corrupted, clear the saved ID
this.log(`Failed to restore last active task ${lastActiveTaskId}: ${error instanceof Error ? error.message : String(error)}`)
this.log(`Task restoration error details: ${JSON.stringify({ taskId: lastActiveTaskId, error: error instanceof Error ? { message: error.message, stack: error.stack } : error })}`)
await this.updateGlobalState("currentActiveTaskId", undefined)

await this.updateGlobalState("currentActiveTaskId", undefined)
}
}
}

public async createTaskWithHistoryItem(historyItem: HistoryItem & { rootTask?: Task; parentTask?: Task }) {
Expand Down
Loading
Loading