feat: make support error-report emails legible, add a version 2 report, and fix #835 items 1-2 - #2014
Merged
Merged
Conversation
Support reports arrive at vcell_support as the raw JSON they were posted in: one line, tens of thousands of characters, every newline written as a literal backslash-n and every apostrophe as a unicode escape. Measured on five real reports, the bodies run from 112 KB to 434 KB, each a single line. ErrorReport.toEmailText() renders the same content as plain text with a header (user, version, platform) and headed sections. It also drops a duplicate. Reports are raised as new RuntimeException(log), so the client log arrives twice: once as exceptionMessage, and again as the message of the exception that opens the stack trace. On a real report that is 94% of the body, and the second copy buries the exception chain behind 58 KB of routine logging. The log is now carried once, at the end, and the stack trace keeps a marker where it was. On that report: 124 KB to 66 KB, one line to 1106, and the frame naming the actual fault is on the first screen instead of past 58,000 characters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUd61NUJgz88h4PZs5MqHn
The wire format between client and server is unchanged -- the report is still posted as JSON -- so this improves the email from every client already installed, with no client update needed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUd61NUJgz88h4PZs5MqHn
Version 1 arrives as one blob: the caller wrapped the client log, the model info and the recorded user events in the message of a new RuntimeException before sending, so the log landed in exceptionMessage and again at the head of stackTrace, and the frames identifying the fault sat past fifty thousand characters of routine logging. Version 2 gives each part its own field -- modelInfo, clientLog, userEvents -- and sends the exception as it was thrown. A reportVersion field says which form it is; the server renders both, so a client of either vintage is understood and nothing has to be deployed in lockstep. The legacy renderer keeps the de-duplication heuristic for reports from older clients. Version 2 needs none of it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUd61NUJgz88h4PZs5MqHn
… exception DialogUtils built the report as new RuntimeException(supplemental, exception), which had two costs: the log was carried twice, and the stack trace was the wrapper's -- rooted at DialogUtils rather than at the fault. It now sends the exception as thrown, with the model, the log and the recorded events as separate fields. DialogMessagePanel gained accessors for the model and the log so they no longer have to be concatenated and pulled apart again. Reporting an error can no longer raise one: the send is guarded. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUd61NUJgz88h4PZs5MqHn
This was referenced Aug 21, 2026
…ported it Addresses the first two items of #835. The error dialog gains an optional one-line field, "What were you doing?". A log records what VCell did; only the user can say what they were trying to do, which is often what makes a report reproducible. An empty field sends exactly as before. Reports were also arriving anonymous. ErrorUtils was told who was logged in once, at startup, from the command-line user name -- normally absent, because people log in through the dialog. It is now told again on each successful connect, so a report carries the account and support can follow one up. Item 3 of #835 (include database transactions) is not addressed here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUd61NUJgz88h4PZs5MqHn
LWNamespace.findLWOwner walks up looking for an LWContainerHandle and returns null when it finds none -- it logs an error when that happens, so it does happen. The dialog was then constructed with a null owner, and AWT downgrades DOCUMENT_MODAL to modeless when there is no owner: the error dialog would neither block the window that raised it nor stay in front of it, which is how an error message ends up behind the main window. findLWOwnerOrMostRecent falls back to the most recently focused visible top-level window, which liveWindows() already returns first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FUd61NUJgz88h4PZs5MqHn
jcschaff
force-pushed
the
feat/legible-error-report-emails
branch
from
August 21, 2026 16:28
7164af7 to
a96f2da
Compare
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1495. Addresses items 1 and 2 of #835.
The problem
An error report reaches
vcell_support@uchc.eduas the raw JSON it was posted in: one line, every newline a literal\n, every apostrophe a'.Five real messages, read-only from the support mailbox: 112 KB, 136 KB, 138 KB, 188 KB, 434 KB — each a single line.
Most of that is duplication.
DialogUtilsbuilt the report asnew RuntimeException(supplemental, exception), so the client log became the wrapper's message and landed twice — asexceptionMessageand again at the head ofstackTrace. On one 124 KB report:Frames began at line 219, past 58,000 characters. And because the report was wrapped, the stack trace was the wrapper's — rooted at
DialogUtils:766, with the real fault demoted to aCaused by:.Changes
1. Render the email as text (server side). Header (user, version, platform) plus headed sections. For reports from existing clients it also removes the duplicate log, leaving a marker where the stack trace had inlined it.
2. A version 2 report (client side). Each part gets its own field —
modelInfo,clientLog,userEvents— and the exception is sent as thrown. AreportVersionfield marks the form; the server renders both, so neither side deploys in lockstep.3. #835 item 1 — users can say what they were doing. An optional one-line field in the error dialog. A log records what VCell did; only the user can say what they were trying to do. Empty sends exactly as before.
4. #835 item 2 — reports identify the reporter.
ErrorUtilswas told who was logged in once, at startup, from the command-line user name — normally absent, since people log in through the dialog. Every report was therefore anonymous. It is now told again on each successful connect.5. The error dialog always has an owner.
LWNamespace.findLWOwnerreturns null when no ancestor implementsLWContainerHandle(it logs an error when that happens, so it does). AWT downgradesDOCUMENT_MODALto modeless when the owner is null, so the error dialog would neither block the window that raised it nor stay in front of it — one way an error ends up behind the main window.findLWOwnerOrMostRecentfalls back to the most recently focused visible top-level window.Result on the real report
The dialog, rendered from the built classes:
Tests
ErrorUtilsTest3 → 14. The 3 existing JSON round-trip cases pass unchanged — the check that the wire format still serialises as before. New cases cover field rendering, the body not being one long line, CRLF normalisation, missing fields, a multi-line platform staying on its header line, the legacy log appearing exactly once with frames ahead of it, a short exception message left alone, and the v2 form.Not addressed
"VCell Support sent through Contact-Us". Including the exception type would make the inbox scannable, but these are forwarded by a mail rule that may match the exact subject — worth checking the rule first.🤖 Generated with Claude Code
https://claude.ai/code/session_01FUd61NUJgz88h4PZs5MqHn