Skip to content

Commit 1736b9e

Browse files
committed
fix: prevent pending task deletion races with edit flush
Race 1: confirmDeletePendingTask called setEditingQueuedMessageId(null) before removeThreadOutboxMessage resolved, allowing the outbox drain to send the message during async removal. Fix: defer clearing the editing lock until removal completes. Race 2: confirmDeletePendingTask did not clear editingPendingTaskRef, so the editor's unmount flush (cancelEditingPendingTask) could re-enqueue the task via enqueueThreadOutboxMessage before the serialized removal updated atom state. Fix: track pending deletions in a shared Set so the flush bails out when a deletion is in progress.
1 parent 060ca27 commit 1736b9e

3 files changed

Lines changed: 39 additions & 8 deletions

File tree

apps/mobile/src/features/home/usePendingTaskListActions.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { useNavigation } from "@react-navigation/native";
22
import { useCallback } from "react";
33
import { Alert } from "react-native";
44

5-
import { removeThreadOutboxMessage } from "../../state/thread-outbox";
5+
import {
6+
clearPendingDeletion,
7+
markPendingDeletion,
8+
removeThreadOutboxMessage,
9+
} from "../../state/thread-outbox";
610
import type { PendingNewTask } from "../../state/use-pending-new-tasks";
711
import { setEditingQueuedMessageId } from "../../state/use-thread-outbox";
812

@@ -36,13 +40,18 @@ export function usePendingTaskListActions(): {
3640
text: "Delete",
3741
style: "destructive",
3842
onPress: () => {
39-
setEditingQueuedMessageId(null);
40-
void removeThreadOutboxMessage(pendingTask.message).catch((error) => {
41-
Alert.alert(
42-
"Could not delete pending task",
43-
error instanceof Error ? error.message : "The pending task could not be removed.",
44-
);
45-
});
43+
markPendingDeletion(pendingTask.message.messageId);
44+
void removeThreadOutboxMessage(pendingTask.message)
45+
.catch((error) => {
46+
Alert.alert(
47+
"Could not delete pending task",
48+
error instanceof Error ? error.message : "The pending task could not be removed.",
49+
);
50+
})
51+
.finally(() => {
52+
clearPendingDeletion(pendingTask.message.messageId);
53+
setEditingQueuedMessageId(null);
54+
});
4655
},
4756
},
4857
],

apps/mobile/src/features/threads/new-task-flow-provider.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ import { useBranches } from "../../state/queries";
4141
import {
4242
enqueueThreadOutboxMessage,
4343
flattenQueuedThreadMessages,
44+
isPendingDeletion,
4445
threadOutboxManager,
4546
type QueuedThreadMessage,
4647
} from "../../state/thread-outbox";
@@ -616,6 +617,13 @@ export function NewTaskFlowProvider(props: React.PropsWithChildren) {
616617
editingPendingTaskRef.current = null;
617618
setEditingPendingTask(null);
618619

620+
// If a deletion is in progress, bail out and let the delete handler
621+
// manage the editing lock and cleanup.
622+
if (isPendingDeletion(editing.messageId)) {
623+
clearComposerDraft(pendingTaskDraftKey(editing.messageId));
624+
return;
625+
}
626+
619627
// If the task was deleted externally, skip re-enqueuing.
620628
const stillQueued = findQueuedPendingTask(editing.messageId);
621629

apps/mobile/src/state/thread-outbox.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,17 @@ export function removeThreadOutboxMessage(message: QueuedThreadMessage): Promise
2727
export function clearThreadOutboxEnvironment(environmentId: EnvironmentId): Promise<void> {
2828
return threadOutboxManager.clearEnvironment(environmentId);
2929
}
30+
31+
const pendingDeletionMessageIds = new Set<string>();
32+
33+
export function markPendingDeletion(messageId: string): void {
34+
pendingDeletionMessageIds.add(messageId);
35+
}
36+
37+
export function clearPendingDeletion(messageId: string): void {
38+
pendingDeletionMessageIds.delete(messageId);
39+
}
40+
41+
export function isPendingDeletion(messageId: string): boolean {
42+
return pendingDeletionMessageIds.has(messageId);
43+
}

0 commit comments

Comments
 (0)