fix: dismiss background tasks even when session lock-conflicted

The X button in the footer's Background Tasks popover silently failed when
ai_sessions.lockedByTab pointed to a stale/closed tab: cancelPlanning et al.
returned 409, dismissSession set lockConflict and bailed before calling
deleteAiSession. Always proceed to delete; the dismissal tombstone already
prevents stale SSE/sync from resurrecting the session.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-26 23:13:08 -07:00
parent e79394846b
commit 9514d12ef2
2 changed files with 11 additions and 21 deletions

View File

@@ -298,7 +298,7 @@ describe("useBackgroundSessions", () => {
expect(mockDeleteAiSession).toHaveBeenCalledWith("planning-session");
});
it("does not dismiss planning session when cancellation is lock-conflicted", async () => {
it("force-dismisses a planning session even when cancellation is lock-conflicted", async () => {
mockFetchAiSessions.mockResolvedValueOnce([
makeSession({ id: "planning-locked", status: "generating", type: "planning" }),
]);
@@ -314,9 +314,8 @@ describe("useBackgroundSessions", () => {
await result.current.dismissSession("planning-locked");
});
expect(mockDeleteAiSession).not.toHaveBeenCalled();
expect(result.current.sessions.map((session) => session.id)).toEqual(["planning-locked"]);
expect(result.current.generating).toBe(1);
expect(mockDeleteAiSession).toHaveBeenCalledWith("planning-locked");
expect(result.current.sessions).toEqual([]);
});
it("keeps a dismissed planning session hidden when stale sync update arrives", async () => {

View File

@@ -272,20 +272,20 @@ export function useBackgroundSessions(projectId?: string): UseBackgroundSessions
const sessionType = session?.type;
const sessionTabId = getSessionTabId();
// Cancel the session based on its type to ensure proper cleanup
// Pass tabId for lock-aware cancellation - if locked by another tab, the API returns 409
// Cancel the session based on its type to ensure proper cleanup.
// Pass tabId for lock-aware cancellation; if locked by another tab the API
// returns 409 — we still proceed to DELETE below, which has no lock check,
// because the user explicitly chose to dismiss. The dismissal tombstone
// recorded below prevents stale sync/SSE updates from resurrecting it.
let cancelFailed = false;
let lockConflict = false;
if (sessionType === "planning") {
try {
await cancelPlanning(id, projectId, sessionTabId);
} catch (err: unknown) {
cancelFailed = true;
// Check if this was a lock conflict (409)
if (err instanceof Error && err.message.includes("locked")) {
lockConflict = true;
console.warn(`[useBackgroundSessions] Cannot dismiss planning session ${id}: locked by another tab`);
console.warn(`[useBackgroundSessions] Forcing dismiss of planning session ${id} despite lock by another tab`);
}
}
} else if (sessionType === "subtask") {
@@ -294,8 +294,7 @@ export function useBackgroundSessions(projectId?: string): UseBackgroundSessions
} catch (err: unknown) {
cancelFailed = true;
if (err instanceof Error && err.message.includes("locked")) {
lockConflict = true;
console.warn(`[useBackgroundSessions] Cannot dismiss subtask session ${id}: locked by another tab`);
console.warn(`[useBackgroundSessions] Forcing dismiss of subtask session ${id} despite lock by another tab`);
}
}
} else if (sessionType === "mission_interview") {
@@ -304,20 +303,12 @@ export function useBackgroundSessions(projectId?: string): UseBackgroundSessions
} catch (err: unknown) {
cancelFailed = true;
if (err instanceof Error && err.message.includes("locked")) {
lockConflict = true;
console.warn(`[useBackgroundSessions] Cannot dismiss mission interview session ${id}: locked by another tab`);
console.warn(`[useBackgroundSessions] Forcing dismiss of mission interview session ${id} despite lock by another tab`);
}
}
}
// If another tab owns the lock, local dismiss must not pretend success.
if (lockConflict) {
return;
}
// Only proceed with deletion if cancellation succeeded or wasn't needed
if (cancelFailed) {
// Non-lock cancellation failure: still try to delete
console.warn(`[useBackgroundSessions] Cancellation failed for session ${id}, attempting delete anyway`);
}