Skip to content

Commit 6db1dd6

Browse files
committed
Recognize crash mid OAuth2 token fetch as a network error
Windows security software that intercepts TLS (corporate antivirus, proxies) can make mailsync's HTTPS client throw an uncaught C++ exception while fetching an OAuth2 access token, crashing the process with SEH code 0xE06D7363 (the MSVC runtime's "C++ exception" signature) before it can log the usual {"offline":true} marker. This bypassed the existing network-failure detection in _buildCrashError, so every occurrence surfaced to users as an unhelpful "unknown error" message and was reported to Sentry as an unactionable crash (MAILSPRING-CLIENT-DH, ~100 Windows users hitting this exact exit code while linking a new Gmail/Outlook account). Detect this specific crash fingerprint - the known SEH code plus a log tail showing the process died fetching an OAuth2 token - and classify it the same as the existing offline-marker case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BfBhpanbqfLKX7csCsArzN
1 parent f1a64fe commit 6db1dd6

2 files changed

Lines changed: 56 additions & 1 deletion

File tree

‎app/spec/mailsync-process-spec.ts‎

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { MailsyncProcess } from '../src/mailsync-process';
2+
3+
describe('MailsyncProcess._buildCrashError', () => {
4+
let proc: MailsyncProcess;
5+
6+
beforeEach(() => {
7+
proc = new MailsyncProcess({ configDirPath: '/tmp', resourcePath: '/tmp', verbose: false });
8+
});
9+
10+
it('flags a crash with the offline marker during test mode as a network error', () => {
11+
const rawLog = 'Testing connection\n{"offline":true,"retryable":true}\n';
12+
const error: any = proc._buildCrashError('test', 1, null, rawLog);
13+
expect(error.isNetworkError).toBe(true);
14+
expect(error.message).not.toContain('unknown error');
15+
});
16+
17+
it('flags a crash mid OAuth2 token fetch (SEH 0xE06D7363) during test mode as a network error', () => {
18+
const rawLog =
19+
'Waiting for Account JSON:\n\nWaiting for Identity JSON:\ninfo: Identity created at 123 - using ID Schema 1\ninfo: Fetching XOAuth2 access token (gmail) for abc123\n';
20+
const error: any = proc._buildCrashError('test', 0xe06d7363, null, rawLog);
21+
expect(error.isNetworkError).toBe(true);
22+
expect(error.message).not.toContain('unknown error');
23+
});
24+
25+
it('does not flag the same OAuth2 crash outside of test mode', () => {
26+
const rawLog = 'info: Fetching XOAuth2 access token (gmail) for abc123\n';
27+
const error: any = proc._buildCrashError('sync', 0xe06d7363, null, rawLog);
28+
expect(error.isNetworkError).toBe(false);
29+
});
30+
31+
it('does not flag an unrelated crash with the same exit code as a network error', () => {
32+
const rawLog = 'Some other crash entirely, nothing about OAuth2\n';
33+
const error: any = proc._buildCrashError('test', 0xe06d7363, null, rawLog);
34+
expect(error.isNetworkError).toBe(false);
35+
expect(error.message).toContain('unknown error');
36+
});
37+
38+
it('reports a signal-terminated crash with the exit description in the message', () => {
39+
const error: any = proc._buildCrashError('sync', null, 'SIGKILL' as NodeJS.Signals, 'crashed');
40+
expect(error.isNetworkError).toBe(false);
41+
expect(error.message).toContain('signal SIGKILL');
42+
});
43+
});

‎app/src/mailsync-process.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,13 +325,25 @@ export class MailsyncProcess extends EventEmitter {
325325
// requests and shouldn't have unrelated crashes reclassified this way - and
326326
// surface a friendly, localized, network-flagged error so callers can avoid
327327
// reporting it to Sentry.
328+
//
329+
// A second signature covers the same TLS-interception scenario when mailsync
330+
// doesn't get a chance to log the offline marker at all: on Windows, security
331+
// software that intercepts TLS (corporate antivirus, proxies, etc.) can make
332+
// the HTTPS client throw an uncaught C++ exception mid-request, which crashes
333+
// the process with SEH code 0xE06D7363 - the MSVC runtime's signature for an
334+
// unhandled C++ exception - right after mailsync logs that it's about to fetch
335+
// an OAuth2 access token (see MAILSPRING-CLIENT-DH, ~100 Windows users hitting
336+
// this exact exit code while linking a new Gmail/Outlook account).
328337
_buildCrashError(
329338
mode: string,
330339
code: number | null,
331340
signal: NodeJS.Signals | null,
332341
rawLog: string
333342
) {
334-
const isNetworkFailure = mode === 'test' && /"offline"\s*:\s*true/.test(rawLog);
343+
const hasOfflineMarker = /"offline"\s*:\s*true/.test(rawLog);
344+
const crashedFetchingOAuthToken =
345+
code === 0xe06d7363 && /Fetching XOAuth2 access token/.test(rawLog);
346+
const isNetworkFailure = mode === 'test' && (hasOfflineMarker || crashedFetchingOAuthToken);
335347
const exitDescription = signal ? `signal ${signal}` : `${code}`;
336348
const error = isNetworkFailure
337349
? new Error(LocalizedErrorStrings.ErrorConnection)

0 commit comments

Comments
 (0)