From ad4fce1f49410817ced061a8b2b5ebe57baa1832 Mon Sep 17 00:00:00 2001 From: Shayna Chambless Date: Tue, 16 Jun 2026 15:23:44 -0700 Subject: [PATCH 1/2] add message --- .../hooks/useCopyIssueDetails.spec.tsx | 31 +++++++++++++++++++ .../hooks/useCopyIssueDetails.tsx | 7 +++++ 2 files changed, 38 insertions(+) diff --git a/static/app/views/issueDetails/hooks/useCopyIssueDetails.spec.tsx b/static/app/views/issueDetails/hooks/useCopyIssueDetails.spec.tsx index e517b808713700..6d57673e84f6c0 100644 --- a/static/app/views/issueDetails/hooks/useCopyIssueDetails.spec.tsx +++ b/static/app/views/issueDetails/hooks/useCopyIssueDetails.spec.tsx @@ -114,6 +114,37 @@ describe('useCopyIssueDetails', () => { expect(result).toContain('## Plan'); }); + it('includes the message when it differs from the title', () => { + const result = issueAndEventToMarkdown({ + group: GroupFixture({title: 'TypeError'}), + event: EventFixture({...event, message: 'Connection to database timed out'}), + organization, + }); + + expect(result).toContain('## Message'); + expect(result).toContain('Connection to database timed out'); + }); + + it('omits the message when it is already part of the title', () => { + const result = issueAndEventToMarkdown({ + group: GroupFixture({title: 'TypeError: connection failed'}), + event: EventFixture({...event, message: 'connection failed'}), + organization, + }); + + expect(result).not.toContain('## Message'); + }); + + it('omits the message when it is empty', () => { + const result = issueAndEventToMarkdown({ + group: GroupFixture({title: 'TypeError'}), + event: EventFixture({...event, message: ' '}), + organization, + }); + + expect(result).not.toContain('## Message'); + }); + it('includes tags when present in event', () => { const eventWithTags = { ...event, diff --git a/static/app/views/issueDetails/hooks/useCopyIssueDetails.tsx b/static/app/views/issueDetails/hooks/useCopyIssueDetails.tsx index ab156e2546177b..e911926d1a6b79 100644 --- a/static/app/views/issueDetails/hooks/useCopyIssueDetails.tsx +++ b/static/app/views/issueDetails/hooks/useCopyIssueDetails.tsx @@ -272,6 +272,13 @@ export const issueAndEventToMarkdown = ({ markdownText += `**Date:** ${new Date(event.dateCreated).toLocaleString()}\n`; } + // Mirror Seer: include the event message only when it adds something beyond + // the title, since for most errors the title already is the message. + const message = event?.message?.trim(); + if (message && !group.title.includes(message)) { + markdownText += `\n## Message\n\n${message}\n`; + } + if (autofixData) { const sections = getOrderedAutofixSections(autofixData); const rootCauseSection = sections.find(isRootCauseSection); From 2bf9458e4bae1b4cea648e4ff8393eeac1820dbd Mon Sep 17 00:00:00 2001 From: Shayna Chambless Date: Tue, 16 Jun 2026 15:45:43 -0700 Subject: [PATCH 2/2] handled --- .../hooks/useCopyIssueDetails.spec.tsx | 58 +++++++++++++++++++ .../hooks/useCopyIssueDetails.tsx | 6 ++ 2 files changed, 64 insertions(+) diff --git a/static/app/views/issueDetails/hooks/useCopyIssueDetails.spec.tsx b/static/app/views/issueDetails/hooks/useCopyIssueDetails.spec.tsx index 6d57673e84f6c0..7735a08e1e7d0e 100644 --- a/static/app/views/issueDetails/hooks/useCopyIssueDetails.spec.tsx +++ b/static/app/views/issueDetails/hooks/useCopyIssueDetails.spec.tsx @@ -206,6 +206,64 @@ describe('useCopyIssueDetails', () => { expect(result).toContain('**Type:** TypeError'); expect(result).toContain('**Value:** Cannot read property of undefined'); expect(result).toContain('#### Stacktrace'); + // No mechanism on this exception, so no handled line. + expect(result).not.toContain('**Handled:**'); + }); + + it('marks an unhandled exception', () => { + const eventWithUnhandled = EventFixture({ + ...event, + entries: [ + { + type: EntryType.EXCEPTION, + data: { + values: [ + { + type: 'TypeError', + value: 'boom', + mechanism: {type: 'onerror', handled: false}, + }, + ], + }, + }, + ], + }); + + const result = issueAndEventToMarkdown({ + group, + event: eventWithUnhandled, + organization, + }); + + expect(result).toContain('**Handled:** No'); + }); + + it('marks a handled exception', () => { + const eventWithHandled = EventFixture({ + ...event, + entries: [ + { + type: EntryType.EXCEPTION, + data: { + values: [ + { + type: 'ValueError', + value: 'caught', + mechanism: {type: 'generic', handled: true}, + }, + ], + }, + }, + ], + }); + + const result = issueAndEventToMarkdown({ + group, + event: eventWithHandled, + organization, + }); + + expect(result).toContain('**Handled:** Yes'); }); it('includes thread stacktrace when activeThreadId matches', () => { diff --git a/static/app/views/issueDetails/hooks/useCopyIssueDetails.tsx b/static/app/views/issueDetails/hooks/useCopyIssueDetails.tsx index e911926d1a6b79..908d559aa7a34f 100644 --- a/static/app/views/issueDetails/hooks/useCopyIssueDetails.tsx +++ b/static/app/views/issueDetails/hooks/useCopyIssueDetails.tsx @@ -203,6 +203,12 @@ function formatEventToMarkdown(event: Event, activeThreadId: number | undefined) if (exception.type) { markdownText += `**Type:** ${exception.type}\n`; } + // Mirror Seer's `is_exception_handled`: an unhandled exception crashed + // the program, a handled one was caught. Only emit it when known. + const handled = exception.mechanism?.handled; + if (handled !== null && handled !== undefined) { + markdownText += `**Handled:** ${handled ? 'Yes' : 'No'}\n`; + } if (exception.value) { markdownText += `**Value:** ${exception.value}\n\n`; }