Skip to content

fix(preload): serialize object-shaped unhandled rejections#2561

Merged
IsmaelMartinez merged 2 commits into
mainfrom
fix/2560-renderer-rejection-serialization
May 20, 2026
Merged

fix(preload): serialize object-shaped unhandled rejections#2561
IsmaelMartinez merged 2 commits into
mainfrom
fix/2560-renderer-rejection-serialization

Conversation

@IsmaelMartinez
Copy link
Copy Markdown
Owner

Summary

Plain-object and undefined rejections from the renderer were stringified via String(reason), producing the literal "[object Object]" / "undefined" in main-process logs and discarding all diagnostic content. Teams emits a lot of object-shaped rejections, so a large fraction of [Renderer] Unhandled rejection log lines carried no useful information.

A small serializeRejectionReason helper now handles each case (Error-like with .message, string/number/boolean, plain object via safe JSON.stringify with circular-ref guard, null/undefined sentinels) so the forwarded message carries the actual payload. The unused reason field is also dropped from the IPC payload — the main side never read it and an unclonable object would have broken the whole ipcRenderer.send call.

First of four fixes from #2560.

Test plan

  • npm run lint — clean
  • Manual: ran ELECTRON_DEBUG_LOG=true npm start before and after. Before: 40 lines containing "[object Object]". After: 0, and rejection messages now show their JSON content (e.g. '{"statusMsg":"the request type for aadGetToken failed due to {...}"}' instead of '[object Object]').

Refs #2560

Plain-object and undefined rejections from the renderer were stringified
via String(reason), producing the literal "[object Object]" / "undefined"
in main-process logs and discarding all diagnostic content. A serializer
now handles each case (Error-like, string/number, object via safe
JSON.stringify with circular-ref guard, null/undefined sentinels) so the
forwarded message carries the actual payload. Also drops the unused
`reason` field from the IPC payload — the main side never read it and
unclonable objects could break the send.

Refs #2560
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a serializeRejectionReason function in app/browser/preload.js to improve the diagnostic reporting of unhandled promise rejections. This function ensures that various types of rejection reasons, including circular objects, are properly stringified instead of resulting in generic "[object Object]" strings. Feedback suggests wrapping the entire function in a try...catch block to handle potential exceptions when accessing properties like reason.message, ensuring the function remains robust.

Comment thread app/browser/preload.js
Comment on lines +434 to +452
function serializeRejectionReason(reason) {
if (reason === undefined) return "<undefined>";
if (reason === null) return "<null>";
if (typeof reason === "string") return reason;
if (typeof reason !== "object") return String(reason);
if (typeof reason.message === "string" && reason.message.length > 0) return reason.message;
try {
const seen = new WeakSet();
return JSON.stringify(reason, (_key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
}) ?? "[unserializable rejection]";
} catch {
return "[unserializable rejection]";
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This function is very well-written and handles many edge cases. To make it even more robust, consider wrapping the entire function body in a single try...catch block.

Currently, an error could be thrown when accessing reason.message if it's a getter that throws an exception. This would be caught by the outer try...catch in the unhandledrejection event listener, which would prevent the rejection from being reported to the main process.

By wrapping the whole function, any unexpected error during serialization will be caught, and the function will gracefully return '[unserializable rejection]', allowing the rest of the error data (like the stack trace) to still be forwarded.

Suggested change
function serializeRejectionReason(reason) {
if (reason === undefined) return "<undefined>";
if (reason === null) return "<null>";
if (typeof reason === "string") return reason;
if (typeof reason !== "object") return String(reason);
if (typeof reason.message === "string" && reason.message.length > 0) return reason.message;
try {
const seen = new WeakSet();
return JSON.stringify(reason, (_key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
}) ?? "[unserializable rejection]";
} catch {
return "[unserializable rejection]";
}
}
function serializeRejectionReason(reason) {
try {
if (reason === undefined) return "<undefined>";
if (reason === null) return "<null>";
if (typeof reason === "string") return reason;
if (typeof reason !== "object") return String(reason);
if (typeof reason.message === "string" && reason.message.length > 0) return reason.message;
const seen = new WeakSet();
return JSON.stringify(reason, (_key, value) => {
if (typeof value === "object" && value !== null) {
if (seen.has(value)) return "[Circular]";
seen.add(value);
}
return value;
}) ?? "[unserializable rejection]";
} catch {
return "[unserializable rejection]";
}
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Applied in 8ce9228. Wrapping the whole body in try/catch is the right defensive call — a throwing reason.message getter would otherwise swallow the rejection entirely.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

📦 PR Build Artifacts

Build successful! Download artifacts:

🐧 Linux

x86_64 (447.79 MB) - Contains: .deb, .rpm, .tar.gz, .AppImage

arm64 (438.04 MB) - Contains: .deb, .rpm, .tar.gz, .AppImage

armv7l (415.88 MB) - Contains: .deb, .rpm, .tar.gz, .AppImage

🍎 macOS

x86_64 (129.22 MB) - Contains: .dmg

🪟 Windows

x86_64 (109.57 MB) - Contains: .exe installer


📝 Note: Snap packages (.snap) are built in a separate workflow

View workflow run

🕐 Last updated: 2026-05-20 11:17 UTC

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

📦 PR Snap Build Artifacts

Snap builds successful! Download artifacts:

🐧 Linux Snap Packages

x86_64 (110.66 MB)

arm64 (107.48 MB)

armv7l (101.54 MB)


📝 Note: Other package formats (.deb, .rpm, .AppImage, .dmg, .exe) are built in the main workflow

View workflow run

Apply Gemini Code Assist suggestion: wrap the whole function body in
try/catch so a throwing `reason.message` getter (or any other unexpected
exception during serialization) degrades to "[unserializable rejection]"
instead of propagating to the outer handler and dropping the rejection
payload entirely.

Refs #2560
@sonarqubecloud
Copy link
Copy Markdown

@IsmaelMartinez IsmaelMartinez merged commit 6720768 into main May 20, 2026
16 checks passed
@IsmaelMartinez IsmaelMartinez deleted the fix/2560-renderer-rejection-serialization branch May 20, 2026 12:35
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