Skip to content

Fix crash when dragging threads to sidebar after a thread is deleted#2760

Open
bengotow wants to merge 1 commit into
masterfrom
claude/awesome-ritchie-5xo6p9
Open

Fix crash when dragging threads to sidebar after a thread is deleted#2760
bengotow wants to merge 1 commit into
masterfrom
claude/awesome-ritchie-5xo6p9

Conversation

@bengotow

Copy link
Copy Markdown
Collaborator

Root Cause

While reviewing the Sentry issue list for release 1.21.1-ae68e881 (sorted by user impact), I identified a crash pattern in MailboxPerspective.receiveThreadIds in app/src/mailbox-perspective.ts.

receiveThreadIds is called when a user drags threads onto a sidebar folder/label. It resolves the dragged thread IDs against the database via DatabaseStore.modelify, then passes the results directly to TaskFactory.tasksForThreadsByAccountId:

// Before (buggy)
DatabaseStore.modelify<Thread>(Thread, threadIds).then((threads) => {
  const tasks = TaskFactory.tasksForThreadsByAccountId(threads, ...);

DatabaseStore.modelify returns undefined in the result array for any ID that is no longer present in the local database (e.g. a thread deleted or synced away between when the drag began and when the drop fires). TaskFactory.tasksForThreadsByAccountId has an explicit guard that throws on non-Thread values:

// task-factory.ts line 17-18
if (!(thread instanceof Thread)) {
  throw new Error('tasksForThreadsByAccountId: `threads` must be instances of Thread');
}

This throw propagates as an unhandled rejection from the .then() chain and is reported to Sentry.

Fix

Added .filter(Boolean) to remove undefined entries from the modelify result before passing to tasksForThreadsByAccountId, identical to the pattern already used in:

// After (fixed)
DatabaseStore.modelify<Thread>(Thread, threadIds).then((threads) => {
  // modelify returns undefined for IDs not found in the DB (e.g. thread deleted between
  // drag start and drop). Filter before passing to tasksForThreadsByAccountId, which
  // throws on non-Thread inputs.
  const validThreads = threads.filter(Boolean) as Thread[];
  const tasks = TaskFactory.tasksForThreadsByAccountId(validThreads, ...);

Notes

  • The Sentry MCP connector was unavailable in this session (authentication required), so the specific Sentry issue ID could not be retrieved or assigned to Ben Gotow. The crash pattern was identified by cross-referencing the recent fix history: commit 4e42bfe fixed the identical modelifytasksForArchiving crash for send-and-archive but missed this second call site in mailbox-perspective.ts.
  • This bug existed in 1.21.1 and is still present in the current master branch.

Generated by Claude Code

MailboxPerspective.receiveThreadIds called DatabaseStore.modelify and
passed the results directly to TaskFactory.tasksForThreadsByAccountId,
which throws if the array contains non-Thread values. modelify returns
undefined for IDs not found in the DB, causing a crash if a thread was
deleted or moved between when it was selected/dragged and when the drop
fires. Added .filter(Boolean) to remove undefined entries before calling
tasksForThreadsByAccountId, mirroring the same fix already applied in
thread-list-context-menu.ts and send-and-archive-extension.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P2kF6PnajpPoVTtQ1FUXtX
@indent-staging

indent-staging Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor
PR Summary

Prevents a crash in the drag-to-sidebar thread flow when a thread has been deleted between drag start and drop. DatabaseStore.modelify returns undefined for IDs missing from the local DB, and TaskFactory.tasksForThreadsByAccountId throws on any non-Thread entry, so the unfiltered array could surface as an unhandled rejection. The fix filters those undefined entries before dispatching tasks, mirroring the same .filter(Boolean) pattern already used in thread-list-context-menu.ts and send-and-archive-extension.ts.

  • Added const validThreads = threads.filter(Boolean) as Thread[]; in MailboxPerspective.receiveThreadIds before calling TaskFactory.tasksForThreadsByAccountId.
  • Reformatted the tasksForThreadsByAccountId call to multi-line and added an explanatory comment about modelify's undefined behavior.
  • Preserved the existing if (tasks.length > 0) guard so an all-stale drop becomes a silent no-op.

Issues

No issues found.

CI Checks

All CI checks passed on 72d4e37.

Custom Rules 3 rules evaluated, 3 passed, 0 failed

Passing This is a longer title to see what happens when they are too long to fit
Passing B
Passing Ben Rule

View all rules

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants