Skip to content
Open
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
29 changes: 18 additions & 11 deletions apps/web/src/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1318,19 +1318,25 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
}
if (clicked !== "delete") return;

if (projectThreads.length > 0) {
toastManager.add({
type: "warning",
title: "Project is not empty",
description: "Delete all threads in this project before removing it.",
});
return;
}

const confirmed = await api.dialogs.confirm(`Remove project "${project.name}"?`);
const confirmed = await api.dialogs.confirm(
[
`Remove project "${project.name}"?`,
projectThreads.length > 0
? `This will also delete ${projectThreads.length} thread${projectThreads.length === 1 ? "" : "s"} in this project.`
: null,
"Your files on disk will not be affected.",
]
.filter(Boolean)
.join("\n"),
);
if (!confirmed) return;

try {
// Delete all threads in the project first
for (const thread of projectThreads) {
await deleteThread(scopeThreadRef(thread.environmentId, thread.id));
}
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.

Cascading delete triggers per-thread worktree confirmation dialogs

High Severity

The loop calls deleteThread for each thread, but deleteThread internally shows a native confirmation dialog for every thread that has an orphaned worktree (asking "Delete the worktree too?"). After the user already confirmed the project removal, they can be bombarded with additional unexpected dialogs — one per qualifying thread. This also contradicts the parent confirmation message claiming "Your files on disk will not be affected," since deleteThread can actually remove worktrees from disk if the user clicks through those prompts.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 88532c1. Configure here.

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.

Thread deletion before project validation risks data loss

High Severity

All threads are irreversibly deleted before validating that the project API is available and before attempting the project deletion command. If readEnvironmentApi returns null or dispatchCommand throws, the catch block shows a "Failed to remove project" toast — but the threads are already gone. This is a non-atomic cascading delete that can silently cause data loss while leaving the project intact.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 88532c1. Configure here.


const projectDraftThread = getDraftThreadByProjectRef(
scopeProjectRef(project.environmentId, project.id),
);
Expand Down Expand Up @@ -1363,12 +1369,13 @@ const SidebarProjectItem = memo(function SidebarProjectItem(props: SidebarProjec
clearComposerDraftForThread,
clearProjectDraftThreadId,
copyPathToClipboard,
deleteThread,
getDraftThreadByProjectRef,
project.cwd,
project.environmentId,
project.id,
project.name,
projectThreads.length,
projectThreads,
suppressProjectClickForContextMenuRef,
],
);
Expand Down
Loading