Skip to content

feat: crash report dialog with pre-filled GitHub issue URL - #732

Draft
Letark wants to merge 1 commit into
mainfrom
feat/crash-report-dialog
Draft

feat: crash report dialog with pre-filled GitHub issue URL#732
Letark wants to merge 1 commit into
mainfrom
feat/crash-report-dialog

Conversation

@Letark

@Letark Letark commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Replaces bare dialog.showErrorBox in the uncaughtException and unhandledRejection handlers with a two-button native dialog offering to open a pre-filled GitHub issue in the user's browser.

Before: Error dialog shows crash text with no actionable next step.
After: User sees "Report on GitHub" / "Dismiss" — clicking Report opens their browser with issue template, platform info, error message, and stack trace pre-filled.

Design decisions

  • Zero infrastructure — no tokens, proxies, or telemetry. User submits via their own GitHub account.
  • Pre-filled URL — uses ?template=crash_report.md&title=...&body=... query params (truncated to ~8000 chars for browser safety).
  • showMessageBoxSync — synchronous because uncaughtException is a sync context that may exit immediately after.
  • 60s cooldown — prevents dialog spam from tight error loops (replaces the inline lastUnhandledRejectionDialogAt pattern).
  • Graceful fallback — if dialog is unavailable (early startup, after quit), catches silently.

What changed

File Change
src/main/crash-report-dialog.ts New module — URL builder + native dialog
src/main/crash-report-dialog.test.ts Vitest tests (URL, truncation, cooldown, dialog)
.github/ISSUE_TEMPLATE/crash_report.md Issue template for crash reports
src/main/index.ts Import + replace both error handlers

What it looks like for the user

  1. App crashes → native error dialog with "Report on GitHub" button
  2. Click → browser opens with issue pre-filled (crash source, OS, version, stack)
  3. User edits, attaches "Export for GitHub" zip, submits

Testing

  • Unit tests cover URL building, title/body truncation, dialog behavior, cooldown, and graceful failure
  • CI will validate lint, typecheck, and full test suite
  • Manual: trigger throw new Error('test') in main process to verify dialog + URL

Notes

  • Existing console.error + flushLogBeforeQuit() calls preserved — crash data always on disk
  • Future: could add render-process-gone listener using the same API

Summary by CodeRabbit

  • New Features

    • Added crash reporting for fatal application errors and unhandled failures.
    • Users can review sanitized error details and open a pre-filled GitHub crash report in their browser.
    • Crash reports include relevant app, platform, and error information while limiting sensitive or excessive content.
    • Added a cooldown to prevent repeated crash-report prompts.
  • Documentation

    • Added a structured crash report template with reproduction steps and diagnostic guidance.

Replace bare dialog.showErrorBox in uncaughtException and unhandledRejection
handlers with a two-button dialog offering to open a pre-filled GitHub issue.

- New module: src/main/crash-report-dialog.ts
  - buildCrashReportUrl(): pre-fills template, title, platform, version, stack
  - showCrashReportDialog(): native sync dialog with Report/Dismiss buttons
  - 60s cooldown to prevent dialog spam from error loops
  - URL truncation to stay within browser/GitHub limits (~8000 chars)
  - No tokens, no telemetry — user controls submission via their own GH account

- New issue template: .github/ISSUE_TEMPLATE/crash_report.md
  - Fields auto-populated by the crash reporter URL params
  - Includes privacy warning matching existing bug_report.md

- Integration: src/main/index.ts
  - uncaughtException: showErrorBox → showCrashReportDialog
  - unhandledRejection: showErrorBox + inline cooldown → showCrashReportDialog
  - Keeps console.error + flushLogBeforeQuit before the dialog call

- Tests: src/main/crash-report-dialog.test.ts (Vitest, node environment)
  - URL building, truncation, title limits
  - Dialog interaction (report, dismiss, cooldown, graceful failure)
@Letark
Letark marked this pull request as draft July 30, 2026 03:06
@coderabbitai

coderabbitai Bot commented Jul 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a crash-report dialog that formats sanitized error details into a pre-filled GitHub issue URL, rate-limits prompts, integrates uncaught exceptions and rejected promises, and adds tests plus a GitHub crash-report issue template.

Changes

Crash reporting

Layer / File(s) Summary
Crash report URL construction
src/main/crash-report-dialog.ts, src/main/crash-report-dialog.test.ts
Defines crash context formatting, sanitization, stack and URL truncation, and GitHub issue URL generation with coverage for encoded and oversized errors.
Crash dialog and browser handoff
src/main/crash-report-dialog.ts, src/main/crash-report-dialog.test.ts
Adds a 60-second dialog cooldown, report/dismiss actions, browser URL opening, failure handling, and cooldown reset support.
Process integration and issue template
src/main/index.ts, .github/ISSUE_TEMPLATE/crash_report.md, src/main/crash-report-dialog.test.ts
Routes uncaught exceptions and unhandled rejections through the crash dialog and adds structured crash-report issue fields and guidance.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProcessError
  participant CrashReportDialog
  participant ElectronDialog
  participant Browser
  participant GitHub
  ProcessError->>CrashReportDialog: showCrashReportDialog(ctx)
  CrashReportDialog->>ElectronDialog: showMessageBoxSync(options)
  ElectronDialog-->>CrashReportDialog: Report on GitHub
  CrashReportDialog->>Browser: openExternal(issue URL)
  Browser->>GitHub: Load pre-filled crash issue
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a crash report dialog that opens a pre-filled GitHub issue URL.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/crash-report-dialog

Comment @coderabbitai help to get the list of available commands.

@sonarqubecloud

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (1)
src/main/crash-report-dialog.ts (1)

52-56: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant .replace(/\n/g, ' ') — dead code.

sanitizeLogMessage already replaces \x00-\x1F (which includes \n/\r) and then collapses all \s+ to a single space, so by the time this line runs there are no newlines left to replace. SonarCloud also flags this line for replaceAll vs replace; either way the extra .replace() call is unreachable and can simply be dropped.

🧹 Proposed cleanup
 function formatErrorForTitle(ctx: CrashContext): string {
   const msg = ctx.error instanceof Error ? ctx.error.message : String(ctx.error);
-  const cleaned = sanitizeLogMessage(msg).replace(/\n/g, ' ').slice(0, 80);
+  const cleaned = sanitizeLogMessage(msg).slice(0, 80);
   return `[Crash] ${cleaned}`;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/crash-report-dialog.ts` around lines 52 - 56, Remove the redundant
`.replace(/\n/g, ' ')` call from `formatErrorForTitle`; use the sanitized result
directly before applying the 80-character slice and title prefix.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/crash-report-dialog.test.ts`:
- Around line 186-198: Reset the crash-dialog cooldown between the two
showCrashReportDialog invocations in handles dialog unavailable gracefully by
calling resetCrashDialogCooldownForTests(), ensuring the second assertion
reaches the mocked showMessageBoxSync throw path rather than being skipped by
the cooldown gate.

In `@src/main/crash-report-dialog.ts`:
- Around line 35-41: Add the required `// catch-no-log-ok <reason>` annotation
to the `catch` block in `getAppVersion`, documenting why the version lookup
failure intentionally returns `'unknown'` without logging; preserve the existing
fallback behavior.
- Around line 58-102: Update formatErrorForBody to use sanitizeForBody for both
the error message and stack trace, preserving stack-trace line breaks inside the
code fence instead of using sanitizeLogMessage. Also add the missing warning
after the diagnostic bundle instructions telling users not to attach Export for
Developer or mesh-client.db because it may contain saved passwords.
- Around line 108-135: Update buildCrashReportUrl so truncation measures the
URL-encoded body rather than subtracting raw body.length from the encoded URL
length. Rebuild the URL after truncation and continue reducing the body, if
necessary, until the final URL is at or below MAX_URL_LENGTH.

In `@src/main/index.ts`:
- Around line 470-477: Update the unhandledRejection handler to await
flushLogBeforeQuit() before calling showCrashReportDialog, ensuring the log
flush settles before the blocking dialog appears; apply the same sequencing
change to the uncaughtException handler.

---

Nitpick comments:
In `@src/main/crash-report-dialog.ts`:
- Around line 52-56: Remove the redundant `.replace(/\n/g, ' ')` call from
`formatErrorForTitle`; use the sanitized result directly before applying the
80-character slice and title prefix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9fcf3b2c-ec0b-48ba-8001-5d086a37657e

📥 Commits

Reviewing files that changed from the base of the PR and between 5405fe4 and 192769f.

📒 Files selected for processing (4)
  • .github/ISSUE_TEMPLATE/crash_report.md
  • src/main/crash-report-dialog.test.ts
  • src/main/crash-report-dialog.ts
  • src/main/index.ts

Comment on lines +186 to +198
it('handles dialog unavailable gracefully', () => {
mockShowMessageBoxSync.mockImplementation(() => {
throw new Error('dialog unavailable');
});

const ctx: CrashContext = {
source: 'uncaughtException',
error: new Error('test'),
};

expect(() => showCrashReportDialog(ctx)).not.toThrow();
expect(showCrashReportDialog(ctx)).toBe(false);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Second showCrashReportDialog call doesn't exercise the throw path.

Cooldown state (lastCrashDialogAt) isn't reset between the two calls in this test, so the second call at line 197 is short-circuited by the 60s cooldown gate (line 149-151) before it ever reaches the mocked showMessageBoxSync that throws. The assertion still passes, but it isn't verifying repeated graceful handling of a throwing dialog — call resetCrashDialogCooldownForTests() between the two invocations if that's the intent.

✅ Proposed fix
     expect(() => showCrashReportDialog(ctx)).not.toThrow();
+    resetCrashDialogCooldownForTests();
     expect(showCrashReportDialog(ctx)).toBe(false);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('handles dialog unavailable gracefully', () => {
mockShowMessageBoxSync.mockImplementation(() => {
throw new Error('dialog unavailable');
});
const ctx: CrashContext = {
source: 'uncaughtException',
error: new Error('test'),
};
expect(() => showCrashReportDialog(ctx)).not.toThrow();
expect(showCrashReportDialog(ctx)).toBe(false);
});
it('handles dialog unavailable gracefully', () => {
mockShowMessageBoxSync.mockImplementation(() => {
throw new Error('dialog unavailable');
});
const ctx: CrashContext = {
source: 'uncaughtException',
error: new Error('test'),
};
expect(() => showCrashReportDialog(ctx)).not.toThrow();
resetCrashDialogCooldownForTests();
expect(showCrashReportDialog(ctx)).toBe(false);
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/crash-report-dialog.test.ts` around lines 186 - 198, Reset the
crash-dialog cooldown between the two showCrashReportDialog invocations in
handles dialog unavailable gracefully by calling
resetCrashDialogCooldownForTests(), ensuring the second assertion reaches the
mocked showMessageBoxSync throw path rather than being skipped by the cooldown
gate.

Comment on lines +35 to +41
function getAppVersion(): string {
try {
return app.getVersion();
} catch {
return 'unknown';
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Missing catch-no-log-ok annotation.

This catch block neither logs, rethrows, nor carries the required annotation comment, unlike the other two catches in this same file (lines 178-180, 183-185) which correctly include one.

As per coding guidelines, "Catches must log, rethrow, or include // catch-no-log-ok <reason>; use console.debug, console.warn, or console.error, never bare console.log."

🩹 Proposed fix
 function getAppVersion(): string {
   try {
     return app.getVersion();
-  } catch {
+  } catch {
+    // catch-no-log-ok app.getVersion() rarely throws; version is cosmetic only
     return 'unknown';
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getAppVersion(): string {
try {
return app.getVersion();
} catch {
return 'unknown';
}
}
function getAppVersion(): string {
try {
return app.getVersion();
} catch {
// catch-no-log-ok app.getVersion() rarely throws; version is cosmetic only
return 'unknown';
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/crash-report-dialog.ts` around lines 35 - 41, Add the required `//
catch-no-log-ok <reason>` annotation to the `catch` block in `getAppVersion`,
documenting why the version lookup failure intentionally returns `'unknown'`
without logging; preserve the existing fallback behavior.

Source: Coding guidelines

Comment on lines +58 to +102
function formatErrorForBody(ctx: CrashContext): string {
const msg = ctx.error instanceof Error ? ctx.error.message : String(ctx.error);
const stack =
ctx.error instanceof Error && ctx.error.stack
? ctx.error.stack.slice(0, MAX_STACK_LENGTH)
: '(no stack trace)';

const platform = getPlatformLabel();
const version = getAppVersion();
const arch = process.arch;
const os = osRelease();
const packaged = app.isPackaged ? 'yes' : 'no (dev)';

return [
'**Crash source:** `' + ctx.source + '`',
'',
'**Desktop:**',
`- OS: ${platform} ${os} (${arch})`,
`- App version: ${version}`,
`- Packaged: ${packaged}`,
'',
'**Error message:**',
'```',
sanitizeLogMessage(msg),
'```',
'',
'**Stack trace:**',
'```',
sanitizeLogMessage(stack),
'```',
'',
'---',
'',
'**Diagnostic bundle:**',
'Please also attach the zip from **App → Support / Bug reports → Export for GitHub** if the app is still responsive.',
'',
'**Steps to reproduce (please fill in):**',
'1. ',
'2. ',
'3. ',
'',
'**Additional context:**',
'',
].join('\n');
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

sanitizeLogMessage collapses the stack trace into a single line, and the "do not attach password DB" warning from the template is dropped.

Two separate issues in this function:

  1. Stack trace loses all line breaks (line 86). sanitizeLogMessage strips \x00-\x1F (including \n/\r) and collapses remaining whitespace to single spaces — it's built for single-line log entries. Applying it to the multi-line stack trace inside the ``` code fence turns the entire stack into one unreadable wall of text, defeating the purpose of including it. Note the test suite mocks sanitizeLogMessage as an identity function, so this regression isn't caught by any test.
  2. Missing password-DB warning (compare with .github/ISSUE_TEMPLATE/crash_report.md line 33). The template explicitly warns: **Do not** attach **Export for Developer** or \mesh-client.db`... may contain saved passwords. Since GitHub's ?body=` query param completely overrides the template body, this warning never reaches the user through the auto-filled crash report flow — exactly the scenario where a user might attach the wrong export.
🛠️ Proposed fix
+function sanitizeForBody(text: string): string {
+  // Strip dangerous control chars but preserve newlines so multi-line
+  // content (stack traces) stays readable in the GitHub issue body.
+  return text
+    .replace(/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F\u2028\u2029]+/g, ' ')
+    .replace(/[ \t]+/g, ' ')
+    .trim();
+}
+
 function formatErrorForBody(ctx: CrashContext): string {
   ...
   return [
     ...
     '**Error message:**',
     '```',
-    sanitizeLogMessage(msg),
+    sanitizeForBody(msg),
     '```',
     '',
     '**Stack trace:**',
     '```',
-    sanitizeLogMessage(stack),
+    sanitizeForBody(stack),
     '```',
     '',
     '---',
     '',
     '**Diagnostic bundle:**',
     'Please also attach the zip from **App → Support / Bug reports → Export for GitHub** if the app is still responsive.',
+    '',
+    '**Do not** attach **Export for Developer** or `mesh-client.db` to this public issue — the database may contain saved passwords.',
     '',
     ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function formatErrorForBody(ctx: CrashContext): string {
const msg = ctx.error instanceof Error ? ctx.error.message : String(ctx.error);
const stack =
ctx.error instanceof Error && ctx.error.stack
? ctx.error.stack.slice(0, MAX_STACK_LENGTH)
: '(no stack trace)';
const platform = getPlatformLabel();
const version = getAppVersion();
const arch = process.arch;
const os = osRelease();
const packaged = app.isPackaged ? 'yes' : 'no (dev)';
return [
'**Crash source:** `' + ctx.source + '`',
'',
'**Desktop:**',
`- OS: ${platform} ${os} (${arch})`,
`- App version: ${version}`,
`- Packaged: ${packaged}`,
'',
'**Error message:**',
'```',
sanitizeLogMessage(msg),
'```',
'',
'**Stack trace:**',
'```',
sanitizeLogMessage(stack),
'```',
'',
'---',
'',
'**Diagnostic bundle:**',
'Please also attach the zip from **App → Support / Bug reports → Export for GitHub** if the app is still responsive.',
'',
'**Steps to reproduce (please fill in):**',
'1. ',
'2. ',
'3. ',
'',
'**Additional context:**',
'',
].join('\n');
}
function sanitizeForBody(text: string): string {
// Strip dangerous control chars but preserve newlines so multi-line
// content (stack traces) stays readable in the GitHub issue body.
return text
.replace(/[\x00-\x09\x0B\x0C\x0E-\x1F\x7F\u2028\u2029]+/g, ' ')
.replace(/[ \t]+/g, ' ')
.trim();
}
function formatErrorForBody(ctx: CrashContext): string {
const msg = ctx.error instanceof Error ? ctx.error.message : String(ctx.error);
const stack =
ctx.error instanceof Error && ctx.error.stack
? ctx.error.stack.slice(0, MAX_STACK_LENGTH)
: '(no stack trace)';
const platform = getPlatformLabel();
const version = getAppVersion();
const arch = process.arch;
const os = osRelease();
const packaged = app.isPackaged ? 'yes' : 'no (dev)';
return [
'**Crash source:** `' + ctx.source + '`',
'',
'**Desktop:**',
`- OS: ${platform} ${os} (${arch})`,
`- App version: ${version}`,
`- Packaged: ${packaged}`,
'',
'**Error message:**',
'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/crash-report-dialog.ts` around lines 58 - 102, Update
formatErrorForBody to use sanitizeForBody for both the error message and stack
trace, preserving stack-trace line breaks inside the code fence instead of using
sanitizeLogMessage. Also add the missing warning after the diagnostic bundle
instructions telling users not to attach Export for Developer or mesh-client.db
because it may contain saved passwords.

Comment on lines +108 to +135
export function buildCrashReportUrl(ctx: CrashContext): string {
const title = formatErrorForTitle(ctx);
let body = formatErrorForBody(ctx);

const baseUrl = `https://github.com/${REPO_OWNER}/${REPO_NAME}/issues/new`;
const params = new URLSearchParams({
template: ISSUE_TEMPLATE,
title,
body,
});

let url = `${baseUrl}?${params.toString()}`;

if (url.length > MAX_URL_LENGTH) {
const overhead = url.length - body.length;
const maxBody = MAX_URL_LENGTH - overhead - 100;
body =
body.slice(0, maxBody) + '\n\n_(truncated — attach Export for GitHub zip for full details)_';
const truncatedParams = new URLSearchParams({
template: ISSUE_TEMPLATE,
title,
body,
});
url = `${baseUrl}?${truncatedParams.toString()}`;
}

return url;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does URLSearchParams.toString() percent-encode newlines and backticks in Node.js?

💡 Result:

Yes, the URLSearchParams.toString method in Node.js percent-encodes both newlines and backticks, along with most other characters that are not ASCII alphanumeric or specific "unreserved" characters [1][2]. The URLSearchParams API adheres to the WHATWG URL specification, which uses the application/x-www-form-urlencoded percent-encode set [1][3]. According to this specification, this encode set includes all code points except for: - ASCII alphanumeric characters (a-z, A-Z, 0-9) [1][2] - The asterisk (*) [1][2] - The hyphen (-) [1][2] - The period (.) [1][2] - The underscore (_) [1][2] Because newlines (U+000A) and backticks (U+0060) are not in this list of allowed characters, they are percent-encoded by the toString method [1][2]. Additionally, while URLSearchParams encodes spaces as a plus sign (+) [1][2][4], other non-alphanumeric characters are typically percent-encoded as a percentage sign followed by their hexadecimal character code (e.g., a newline might be encoded as %0A and a backtick as %60).

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' src/main/crash-report-dialog.ts | cat -n

Repository: Colorado-Mesh/mesh-client

Length of output: 7154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the function and nearby constants
sed -n '1,220p' src/main/crash-report-dialog.ts | cat -n

Repository: Colorado-Mesh/mesh-client

Length of output: 7154


🏁 Script executed:

#!/bin/bash
set -euo pipefail

node <<'JS'
const MAX_URL_LENGTH = 8000;
const baseUrl = 'https://github.com/Colorado-Mesh/mesh-client/issues/new';
const title = '[Crash] ' + 'x'.repeat(80);
const template = 'crash_report.md';
const suffix = '\n\n_(truncated — attach Export for GitHub zip for full details)_';

function makeUrl(body) {
  const params = new URLSearchParams({ template, title, body });
  return `${baseUrl}?${params.toString()}`;
}

function build(body) {
  let url = makeUrl(body);
  if (url.length > MAX_URL_LENGTH) {
    const overhead = url.length - body.length;
    const maxBody = MAX_URL_LENGTH - overhead - 100;
    const truncatedBody = body.slice(0, maxBody) + suffix;
    url = makeUrl(truncatedBody);
  }
  return url;
}

const chars = [
  ['a', 'ASCII'],
  ['\n', 'newline'],
  ['€', 'euro'],
  ['🙂', 'emoji'],
  ['`', 'backtick'],
  ['(', 'paren'],
  ['—', 'emdash'],
];

for (const [ch, label] of chars) {
  let worst = null;
  for (let a = 0; a <= 3000; a += 100) {
    for (let b = 0; b <= 7000; b += 100) {
      const body = ch.repeat(a) + 'a'.repeat(b);
      const url = makeUrl(body);
      if (url.length <= MAX_URL_LENGTH) continue;
      const out = build(body);
      if (out.length > MAX_URL_LENGTH) {
        worst = { label, a, b, inLen: url.length, outLen: out.length };
        console.log('FOUND', JSON.stringify(worst));
        process.exit(0);
      }
    }
  }
}
console.log('No overflow found in coarse grid.');
JS

Repository: Colorado-Mesh/mesh-client

Length of output: 230


Recompute the limit after encoding. overhead mixes encoded url.length with raw body.length, so a body with lots of multibyte/percent-encoded characters can still produce a final URL over MAX_URL_LENGTH after truncation. Recalculate against the encoded body (or loop until the rebuilt URL fits).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/crash-report-dialog.ts` around lines 108 - 135, Update
buildCrashReportUrl so truncation measures the URL-encoded body rather than
subtracting raw body.length from the encoded URL length. Rebuild the URL after
truncation and continue reducing the body, if necessary, until the final URL is
at or below MAX_URL_LENGTH.

Comment thread src/main/index.ts
Comment on lines 470 to +477
process.on('unhandledRejection', (reason) => {
console.error(
'[main] Unhandled rejection:',
sanitizeLogMessage(reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)),
);
void flushLogBeforeQuit();
const now = Date.now();
if (now - lastUnhandledRejectionDialogAt < UNHANDLED_REJECTION_DIALOG_COOLDOWN_MS) return;
lastUnhandledRejectionDialogAt = now;
const message =
reason instanceof Error ? `${reason.message}\n\n${reason.stack ?? ''}` : String(reason);
try {
dialog.showErrorBox(
'Mesh-Client — Unhandled Promise Rejection',
`A promise rejected without a handler. Check the main process terminal for full details.\n\n${message.slice(0, 1500)}${message.length > 1500 ? '…' : ''}`,
);
} catch {
// catch-no-log-ok dialog unavailable during early startup; rejection already logged above
}
const error = reason instanceof Error ? reason : new Error(String(reason));
showCrashReportDialog({ source: 'unhandledRejection', error });

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does Electron dialog.showMessageBoxSync block the Node.js event loop until dismissed?

💡 Result:

Yes, the electron dialog.showMessageBoxSync method blocks the process until the message box is closed [1][2]. Because Electron's main process runs on the Node.js event loop, this synchronous blocking behavior prevents the event loop from processing other tasks, such as handling IPC messages or updating windows, until the dialog is dismissed [3][4]. {value: "Yes, the electron dialog.showMessageBoxSync method blocks the process until the message box is closed [1][2]. Because Electron's main process runs on the Node.js event loop, this synchronous blocking behavior prevents the event loop from processing other tasks, such as handling IPC messages or updating windows, until the dialog is dismissed [3][4].", citations: [1][2][3][4], confidence: 1.0}

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and locate the functions mentioned in the comment.
ast-grep outline src/main/index.ts --view expanded || true
rg -n "flushLogBeforeQuit|showCrashReportDialog|showMessageBoxSync|unhandledRejection|uncaughtException|appendChain" src/main/index.ts src/main -S || true

# Read the relevant section around the handlers.
sed -n '430,520p' src/main/index.ts

# Find the flush implementation.
rg -n "function flushLogBeforeQuit|const flushLogBeforeQuit|flushLogBeforeQuit =" src/main -S

Repository: Colorado-Mesh/mesh-client

Length of output: 18188


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the log flush implementation and crash dialog behavior.
sed -n '1,240p' src/main/log-service.ts
printf '\n--- crash-report-dialog ---\n'
sed -n '1,240p' src/main/crash-report-dialog.ts

Repository: Colorado-Mesh/mesh-client

Length of output: 13900


Await flushLogBeforeQuit() before showing the crash dialog. showMessageBoxSync() blocks the main process, so the fire-and-forget flush won’t settle before the dialog appears. The same pattern exists in uncaughtException.

🧰 Tools
🪛 ast-grep (0.45.0)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { spawn } from 'child_process';
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/index.ts` around lines 470 - 477, Update the unhandledRejection
handler to await flushLogBeforeQuit() before calling showCrashReportDialog,
ensuring the log flush settles before the blocking dialog appears; apply the
same sequencing change to the uncaughtException handler.

Source: Coding guidelines

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.

1 participant