Thanks to visit codestin.com
Credit goes to GitHub.com

Skip to content

Conversation

@nams1570
Copy link
Collaborator

@nams1570 nams1570 commented Jan 28, 2026

Context

We noticed some errors pop up on sentry related to email rendering. These errors seem to have been triggered by the same issue, and could be categorized as follows:

  1. Sanity test mismatch, even when the errors from freestyle and vercel sandbox were broadly similar. This occurred due to stack traces differing in different execution environments.
  2. Rendering errors from freestyle and vercel sandbox caused by the theme not being imported/ empty theme component.

Upon investigation, this occurred because hitting save on the email themes page with an invalid theme (ex: deleting the export keyword, or renaming the EmailTheme component) still triggers bundleAndExecute with the invalid themes. This will obviously fail and cause the errors to be logged, however there is no cause for concern here because the error is returned and the save is denied because an error is returned. It's more of a matter of noisy error logs and too strict sanity test comparisons.

Summary of Changes

We loosen the sanity test comparison between engine execution results in case of errors. We also add a new argument to the bundleAndExecute function to control whether to log the errors or not, and in case of preview mode, invoke bundleAndExecute with it disabled.

Summary by CodeRabbit

  • Bug Fixes

    • Made email preview error capture conditional to avoid unnecessary captures and reduce spurious logs during previews.
  • Improvements

    • More robust execution-result comparison for clearer, more reliable detection of differences across engines.
  • Tests

    • Added extensive end-to-end tests covering email template/theme/draft rendering failures, syntax errors, validation errors, and related error responses.

✏️ Tip: You can customize this high-level summary in your review settings.

Diff. execution environments can yield diff.
 stack traces.
These don't count as meaningful differences.
This isn't really a bug, but it leads to noisy sentry dashboards.
In preview mode for themes, when you hit save, you still end up triggering bundleAndExecute.
This used to cause error logging for something that we return as an error, meaning no state changes.
We expect users to be messy when working on preview.
Copilot AI review requested due to automatic review settings January 28, 2026 19:57
@vercel
Copy link

vercel bot commented Jan 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
stack-backend Ready Ready Preview, Comment Jan 29, 2026 1:02am
stack-dashboard Ready Ready Preview, Comment Jan 29, 2026 1:02am
stack-demo Ready Ready Preview, Comment Jan 29, 2026 1:02am
stack-docs Ready Ready Preview, Comment Jan 29, 2026 1:02am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

Adds a flag to control whether execution errors are captured during email bundling/execution, wires that flag through renderEmailWithTemplate, and introduces a structured ExecuteResult type plus equality logic used in sanity tests. Several E2E tests for rendering error scenarios were added.

Changes

Cohort / File(s) Summary
Email rendering core
apps/backend/src/lib/email-rendering.tsx
Added shouldCaptureErrors: boolean = true parameter to bundleAndExecute; made error-capture conditional on this flag; renderEmailWithTemplate forwards previewMode (negated) to control capture.
JS execution typing & tests
apps/backend/src/lib/js-execution.tsx
Added exported ExecuteResult union type and internal areResultsEqual helper; updated sanity-test comparison to use ExecuteResult-aware equality.
E2E tests — email rendering error cases
apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts, apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts, apps/e2e/tests/backend/endpoints/api/v1/internal/email-templates.test.ts
Added multiple tests covering invalid JSX, missing exports, runtime throws, non-function exports, and placeholders for infinite-loop / memory-pressure scenarios; assert 400 responses and specific error payload snapshots.

Sequence Diagram(s)

(omitted)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • BilalG1

Poem

🐇 I hopped through code with soft, quick paws,

I caught some errors and left others because.
Preview says "silence," so I fold my ears,
Types hold steady while tests chase their fears.
Hop, nibble, patch — and nibble more cheers!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: refactoring error logging behavior in email rendering to reduce noisy logs.
Description check ✅ Passed The description provides clear context about the Sentry errors, root causes, and the solution implemented, matching the template and PR objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 28, 2026

Greptile Overview

Greptile Summary

Reduced noisy error logging by suppressing Sentry captures during email theme preview mode and loosening sanity test comparisons to ignore environment-specific stack traces.

Changes Made

  • Email Rendering: Added shouldCaptureErrors parameter to bundleAndExecute() function (defaults to true). When previewMode is enabled, errors are not logged to Sentry but are still returned to deny the save operation.

  • JS Execution: Introduced areResultsEqual() helper that compares execution results by status and error message only, excluding stack traces which differ across execution environments (freestyle vs vercel-sandbox).

Behavior

  • Invalid theme saves (missing exports, renamed components) still fail correctly and return errors
  • Preview mode no longer generates noisy Sentry alerts for expected validation failures
  • Sanity tests compare error semantics rather than exact stack trace strings
  • Production email rendering continues to capture all errors normally

Confidence Score: 5/5

  • This PR is safe to merge with no risk
  • The changes are simple refactors that improve error logging behavior without changing core functionality. The logic correctly suppresses logs during preview while maintaining validation, and the sanity test improvement is a sensible relaxation of overly strict comparisons.
  • No files require special attention

Important Files Changed

Filename Overview
apps/backend/src/lib/email-rendering.tsx Added shouldCaptureErrors parameter to bundleAndExecute to suppress error logging in preview mode
apps/backend/src/lib/js-execution.tsx Introduced areResultsEqual function to compare execution results by message only, not stack traces

Sequence Diagram

sequenceDiagram
    participant API as Email Theme API
    participant Render as renderEmailWithTemplate
    participant Bundle as bundleAndExecute
    participant Execute as executeJavascript
    participant Sanity as runSanityTest
    participant Compare as areResultsEqual
    participant Sentry as captureError

    Note over API,Render: Preview Mode Flow
    API->>Render: renderEmailWithTemplate(template, theme, {previewMode: true})
    Render->>Bundle: bundleAndExecute(files, shouldCaptureErrors=false)
    Bundle->>Execute: executeJavascript(code, options)
    
    alt Execution fails
        Execute-->>Bundle: ExecuteResult {status: "error"}
        Note over Bundle: Error returned but NOT logged to Sentry
        Bundle-->>Render: Result.error(message)
        Render-->>API: Error response (save denied)
    end

    Note over Execute,Sanity: Sanity Test Flow (5% random)
    Execute->>Sanity: runSanityTest(code, options)
    Sanity->>Execute: Run on freestyle engine
    Sanity->>Execute: Run on vercel-sandbox engine
    Sanity->>Compare: areResultsEqual(result1, result2)
    
    alt Results differ
        Compare->>Compare: Compare status and message only (not stack)
        Compare-->>Sanity: false
        Sanity->>Sentry: captureError("sanity-test-mismatch")
    else Results match
        Compare-->>Sanity: true
        Note over Sanity: No error logged
    end
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

2 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors email rendering error logging to reduce noise in Sentry logs and fix overly strict sanity test comparisons. The changes address issues where invalid theme saves trigger unnecessary error logs and where stack trace differences between execution environments cause false positive sanity test failures.

Changes:

  • Added a new areResultsEqual function in js-execution.tsx that compares execution results while ignoring stack trace differences for error cases
  • Modified the sanity test logic to use the new comparison function instead of strict JSON equality
  • Added a shouldCaptureErrors parameter to bundleAndExecute to control error logging, disabled for preview mode

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
apps/backend/src/lib/js-execution.tsx Added ExecuteResult type, areResultsEqual comparison function, and updated runSanityTest to use relaxed error comparison
apps/backend/src/lib/email-rendering.tsx Added shouldCaptureErrors parameter to bundleAndExecute and conditionally wrap error captures based on preview mode

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/backend/src/lib/js-execution.tsx`:
- Around line 164-181: areResultsEqual currently assumes both inputs are
ExecuteResult and directly reads .status which can throw if an engine returns
null or a primitive; update areResultsEqual to runtime-guard both arguments
(check typeof === 'object' && arg !== null && 'status' in arg) and only then
compare status/data/error as before, otherwise fall back to a safe equality
check (e.g., strict === for primitives or JSON.stringify for objects) to avoid
crashes; also apply the same guard/fallback logic to the other sanity-test
comparison site referenced (the comparison around runSanityTest) so
non-ExecuteResult outputs are handled consistently.
🧹 Nitpick comments (1)
apps/backend/src/lib/email-rendering.tsx (1)

203-203: Make the boolean argument self‑explanatory.
Positional !previewMode is a bit opaque; a named constant improves readability.

♻️ Suggested tweak
-  return await bundleAndExecute<EmailRenderResult>(files, !previewMode);
+  const shouldCaptureErrors = !previewMode;
+  return await bundleAndExecute<EmailRenderResult>(files, shouldCaptureErrors);

It fits better here logically
@nams1570 nams1570 requested a review from N2D4 January 28, 2026 22:23
Copy link
Contributor

Choose a reason for hiding this comment

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

can you add E2E tests that reproduce the bugs you fixed?

},
});
expect(renderRes.status).toBe(400);
expect(renderRes.body).toMatchInlineSnapshot("???");
Copy link

Choose a reason for hiding this comment

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

The test "should reject theme_tsx_source that throws an error when rendered" has an incomplete snapshot placeholder that will cause the test to fail. The snapshot needs to be populated with the expected error response.

View Details
📝 Patch Details
diff --git a/apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts
index 13effc0b..4d0dc996 100644
--- a/apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts
+++ b/apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts
@@ -411,7 +411,13 @@ describe("invalid JSX inputs", () => {
       }
     );
     expect(response.status).toBe(400);
-    expect(response.body).toMatchInlineSnapshot("???");
+    expect(response.body).toMatchInlineSnapshot(`
+      {
+        "code": "EMAIL_RENDERING_ERROR",
+        "details": { "error": "{\\"message\\":\\"Intentional error from theme\\",\\"stack\\":\\"Error: Intentional error from theme\\\\n    at EmailTheme (/app/tmp/job-<stripped UUID>/script.ts:98:13)\\\\n    at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:155:40)\\\\n    at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:145:18)\\\\n    at fulfilled (/app/tmp/job-<stripped UUID>/script.ts:32:24)\\"}" },
+        "error": "Failed to render email with theme: {\\"message\\":\\"Intentional error from theme\\",\\"stack\\":\\"Error: Intentional error from theme\\\\n    at EmailTheme (/app/tmp/job-<stripped UUID>/script.ts:98:13)\\\\n    at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:155:40)\\\\n    at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:145:18)\\\\n    at fulfilled (/app/tmp/job-<stripped UUID>/script.ts:32:24)\\"}",
+      }
+    `);
   });
 
   it("should reject theme that does not export EmailTheme function", async ({ expect }) => {
diff --git a/apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts b/apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts
index c5a84158..bba06e53 100644
--- a/apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts
+++ b/apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts
@@ -621,7 +621,13 @@ it("should reject theme_tsx_source that throws an error when rendered", async ({
     },
   });
   expect(renderRes.status).toBe(400);
-  expect(renderRes.body).toMatchInlineSnapshot("???");
+  expect(renderRes.body).toMatchInlineSnapshot(`
+    {
+      "code": "EMAIL_RENDERING_ERROR",
+      "details": { "error": "{\\"message\\":\\"Intentional error from theme\\",\\"stack\\":\\"Error: Intentional error from theme\\\\n    at EmailTheme (/app/tmp/job-<stripped UUID>/script.ts:98:13)\\\\n    at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:155:40)\\\\n    at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:145:18)\\\\n    at fulfilled (/app/tmp/job-<stripped UUID>/script.ts:32:24)\\"}" },
+      "error": "Failed to render email with theme: {\\"message\\":\\"Intentional error from theme\\",\\"stack\\":\\"Error: Intentional error from theme\\\\n    at EmailTheme (/app/tmp/job-<stripped UUID>/script.ts:98:13)\\\\n    at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:155:40)\\\\n    at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:145:18)\\\\n    at fulfilled (/app/tmp/job-<stripped UUID>/script.ts:32:24)\\"}",
+    }
+  `);
 });
 
 it("should reject theme_tsx_source that does not export EmailTheme function", async ({ expect }) => {

Analysis

Incomplete Vitest inline snapshots with "???" placeholder cause test failures

What fails: Two tests use toMatchInlineSnapshot("???") as a placeholder which will always fail when executed:

  1. "should reject theme_tsx_source that throws an error when rendered" in apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts:624
  2. "should reject theme that throws an error when rendered" in apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts:413

How to reproduce:

cd apps/e2e
pnpm exec vitest run tests/backend/endpoints/api/v1/internal/email-drafts.test.ts
pnpm exec vitest run tests/backend/endpoints/api/v1/email-themes.test.ts

Both tests will fail with error:

Error: Snapshot `<test name>` mismatched

- Expected
+ Received

- ???
+ { "code": "EMAIL_RENDERING_ERROR", ... }

Result: Tests fail because the literal string "???" does not match the actual API response object

Expected: Tests should contain the actual expected error response structure matching other EMAIL_RENDERING_ERROR responses in the same test files

Root cause: The tests were added in commit bfa208a with incomplete snapshot placeholders that were never filled in. The "???" placeholder syntax is not a valid Vitest mechanism - it's a literal string that will never match any real response.

Fix applied: Replaced "???" with properly structured EMAIL_RENDERING_ERROR response objects matching the pattern from similar tests (e.g., toMatchInlineSnapshot at lines 444-450 in email-drafts.test.ts). The response structure includes:

  • code: "EMAIL_RENDERING_ERROR"
  • details: Object containing stringified error object
  • error: Error message "Failed to render email with theme: ..."

Note: The exact line numbers in the stack trace may need adjustment when tests are run. If line numbers don't match exactly, re-run with --update flag to auto-update snapshots: pnpm exec vitest run --update

}
);
expect(response.status).toBe(400);
expect(response.body).toMatchInlineSnapshot("???");
Copy link

Choose a reason for hiding this comment

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

Suggested change
expect(response.body).toMatchInlineSnapshot("???");
expect(response.body).toMatchInlineSnapshot(`
{
"code": "EMAIL_RENDERING_ERROR",
"details": { "error": "{\\"message\\":\\"Intentional error from theme\\",\\"stack\\":\\"Error: Intentional error from theme\\\\n at EmailTheme (/app/tmp/job-<stripped UUID>/script.ts:99:13)\\\\n at findComponentValue (/app/tmp/job-<stripped UUID>/script.ts:70:20)\\\\n at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:146:18)\\\\n at fulfilled (/app/tmp/job-<stripped UUID>/script.ts:32:24)\\"}" },
"error": "Failed to render email with theme: {\\"message\\":\\"Intentional error from theme\\",\\"stack\\":\\"Error: Intentional error from theme\\\\n at EmailTheme (/app/tmp/job-<stripped UUID>/script.ts:99:13)\\\\n at findComponentValue (/app/tmp/job-<stripped UUID>/script.ts:70:20)\\\\n at <anonymous> (/app/tmp/job-<stripped UUID>/script.ts:146:18)\\\\n at fulfilled (/app/tmp/job-<stripped UUID>/script.ts:32:24)\\"}",
}
`);

The test "should reject theme that throws an error when rendered" has an incomplete snapshot placeholder that will cause the test to fail. The snapshot needs to be populated with the expected error response.

View Details

Analysis

Incomplete snapshot placeholder in email-themes test

What fails: The test "should reject theme that throws an error when rendered" at line 414 of apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts has an incomplete snapshot placeholder that prevents the test from passing.

How to reproduce:

pnpm test:e2e -- apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts -t "should reject theme that throws an error when rendered"

Result: The test fails because the snapshot toMatchInlineSnapshot("???") is a literal placeholder string that will never match the actual error response object returned by the API.

Expected: The snapshot should contain the expected error response structure with code: "EMAIL_RENDERING_ERROR", details with the error message, and an error field with the full error description, following the same pattern as similar tests in email-drafts.test.ts and email-templates.test.ts that handle runtime errors during email/theme rendering.

Fix applied: Replaced the placeholder "???" with the proper inline snapshot containing the complete error response structure that matches the pattern from other similar tests in the codebase. The response includes the serialized error object with message and stack trace information.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts`:
- Around line 413-415: The test currently uses a placeholder inline snapshot
("???") for expect(response.body).toMatchInlineSnapshot which always fails; run
the test to capture the actual response payload (or manually inspect the
response.body returned by the API call in this test), then replace the "???"
placeholder with the real inline snapshot string or update the snapshot using
the test runner's snapshot update flag so that the assertion in this test block
(the expect(response.status).toBe(400) /
expect(response.body).toMatchInlineSnapshot pair) compares against the actual
JSON payload returned by the endpoint.

In `@apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts`:
- Around line 623-625: The inline snapshot placeholder in the test using
expect(renderRes.body).toMatchInlineSnapshot("???") is invalid; run the test to
capture the actual response payload and replace "???" with the recorded inline
snapshot string (or remove the argument and run the test with Jest's -u to
auto-fill the snapshot). Update the assertion in the email-drafts.test (the
expect on renderRes.body) so it contains the real JSON/string snapshot that
matches the current response.

Comment on lines +413 to +415
expect(response.status).toBe(400);
expect(response.body).toMatchInlineSnapshot("???");
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace the placeholder inline snapshot.

Line 414 uses "???", which will always fail. Record the real payload (or update the inline snapshot after running the test).

🧪 Suggested change
-    expect(response.body).toMatchInlineSnapshot("???");
+    expect(response.body).toMatchInlineSnapshot();
📝 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
expect(response.status).toBe(400);
expect(response.body).toMatchInlineSnapshot("???");
});
expect(response.status).toBe(400);
expect(response.body).toMatchInlineSnapshot();
});
🤖 Prompt for AI Agents
In `@apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts` around lines
413 - 415, The test currently uses a placeholder inline snapshot ("???") for
expect(response.body).toMatchInlineSnapshot which always fails; run the test to
capture the actual response payload (or manually inspect the response.body
returned by the API call in this test), then replace the "???" placeholder with
the real inline snapshot string or update the snapshot using the test runner's
snapshot update flag so that the assertion in this test block (the
expect(response.status).toBe(400) / expect(response.body).toMatchInlineSnapshot
pair) compares against the actual JSON payload returned by the endpoint.

Comment on lines +623 to +625
expect(renderRes.status).toBe(400);
expect(renderRes.body).toMatchInlineSnapshot("???");
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Replace the placeholder inline snapshot.

Line 624 uses "???", which will fail. Record the real payload (or update the inline snapshot after running the test).

🧪 Suggested change
-  expect(renderRes.body).toMatchInlineSnapshot("???");
+  expect(renderRes.body).toMatchInlineSnapshot();
📝 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
expect(renderRes.status).toBe(400);
expect(renderRes.body).toMatchInlineSnapshot("???");
});
expect(renderRes.status).toBe(400);
expect(renderRes.body).toMatchInlineSnapshot();
});
🤖 Prompt for AI Agents
In `@apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts` around
lines 623 - 625, The inline snapshot placeholder in the test using
expect(renderRes.body).toMatchInlineSnapshot("???") is invalid; run the test to
capture the actual response payload and replace "???" with the recorded inline
snapshot string (or remove the argument and run the test with Jest's -u to
auto-fill the snapshot). Update the assertion in the email-drafts.test (the
expect on renderRes.body) so it contains the real JSON/string snapshot that
matches the current response.

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.

3 participants