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

Skip to content

Conversation

@nams1570
Copy link
Collaborator

@nams1570 nams1570 commented Jan 8, 2026

Previously, we were using an old version of freestyle.sh and experienced issues with it failing. We want to update it to the latest API and also introduce a fallback that can be used when freestyle fails to improve reliability. We refactor the freestyle.tsx wrapper code around freestyle to a js-execution-engine, which also offers opportunities to extend the email rendering engine abstraction in the future. If freestyle encounters runtime errors while running the code or fails, we retry once and then default to Vercel sandbox. Note that we also introduce a sanity test: 5% of the time, we will run the code through both freestyle and the Vercel Sandbox and log it if the results don't match.

As a chore, we also up the time limit on a failed-email-digest.test.ts test to make it less flakey.

Summary by CodeRabbit

  • Refactor

    • Unified email rendering into a bundle-and-execute runtime, introduced a pluggable JS execution flow with multi-engine fallback, and removed the legacy execution module.
  • Chores

    • Added sandbox execution dependency and updated sandbox-related packages.
  • Config

    • Added environment variables to support sandbox provisioning.
  • Tests

    • Increased wait time and added retries in a backend e2e test to improve digest reliability.

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

Copilot AI review requested due to automatic review settings January 8, 2026 18:52
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

@vercel
Copy link

vercel bot commented Jan 8, 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 14, 2026 6:22pm
stack-dashboard Ready Ready Preview, Comment Jan 14, 2026 6:22pm
stack-demo Ready Ready Preview, Comment Jan 14, 2026 6:22pm
stack-docs Ready Ready Preview, Comment Jan 14, 2026 6:22pm
test-sandbox Error Error Jan 14, 2026 6:22pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 8, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

Replaces a Freestyle-only runtime with a pluggable JS executor (Freestyle + Vercel Sandbox), migrates email rendering to a bundle-and-execute model using the new executor, removes the legacy Freestyle wrapper, updates backend deps and env vars, and increases two test wait times.

Changes

Cohort / File(s) Summary
Dependency updates
apps/backend/package.json
Added @vercel/sandbox (^1.1.2) and bumped freestyle-sandboxes to ^0.1.5.
New JS execution module
apps/backend/src/lib/js-execution.tsx
Adds executeJavascript(code, options) with pluggable engines (freestyle, vercel-sandbox), ordered fallback, optional sanity tests, tracing/metrics, and normalized error handling.
Email rendering migration
apps/backend/src/lib/email-rendering.tsx
Replaces Freestyle orchestration with bundleAndExecute that uses executeJavascript; introduces EmailRenderResult and ExecuteResult types; updates renderEmailWithTemplate and batched rendering signatures to return Result<...>.
Removed legacy Freestyle wrapper
apps/backend/src/lib/freestyle.tsx
Deleted the Freestyle class and its retry/tracing/executeScript logic.
Env additions
apps/backend/.env
Added Vercel sandbox variables: STACK_VERCEL_SANDBOX_PROJECT_ID, STACK_VERCEL_SANDBOX_TEAM_ID, STACK_VERCEL_SANDBOX_TOKEN.
Tests (timing)
apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
Increased two wait durations from 5s to 10s and added { repeats: 10 } to one test.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Bundle as bundleAndExecute
    participant Executor as executeJavascript
    participant Freestyle
    participant Vercel as VercelSandbox

    Caller->>Bundle: request render (files + '/entry.js')
    Bundle->>Executor: send bundled code + nodeModules
    alt engine specified
        Executor->>Freestyle: execute (if 'freestyle')
        Executor->>Vercel: execute (if 'vercel-sandbox')
    else engine not specified
        Executor->>Freestyle: attempt primary (with retries)
        alt Freestyle succeeds
            Freestyle-->>Executor: result
        else Freestyle fails
            Executor->>Vercel: fallback attempt
            alt Vercel succeeds
                Vercel-->>Executor: result
            else Vercel fails
                Vercel-->>Executor: error
            end
        end
    end
    Executor-->>Bundle: execution result or normalized error
    Bundle-->>Caller: EmailRenderResult | error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • N2D4

Poem

🐰 I bundled code and hopped along,

Sandboxes hummed a two-step song,
Templates stitched in a single run,
Old wrapper gone, the new begun,
🥕 small paws, big deploy — job done.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 summarizes the main changes: updating to the latest freestyle version and adding Vercel Sandbox as a fallback for email rendering.
Description check ✅ Passed The description provides comprehensive context about the changes, including the motivation for updating freestyle, the new js-execution-engine abstraction, fallback behavior, sanity testing, and test improvements.

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

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 05dc86f and c09f0ad.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/backend/.env
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
  • apps/backend/src/lib/js-execution.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
💤 Files with no reviewable changes (1)
  • apps/backend/src/lib/freestyle.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/backend/package.json
🧰 Additional context used
📓 Path-based instructions (4)
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx,js,jsx}: Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry
Use .toMatchInlineSnapshot over other selectors in tests when possible, and check/modify snapshot-serializer.ts to understand how snapshots are formatted

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
🧠 Learnings (11)
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Always add new E2E tests when changing the API or SDK interface, erring on the side of creating too many tests due to the critical nature of the industry

Applied to files:

  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Use `.toMatchInlineSnapshot` over other selectors in tests when possible, and check/modify snapshot-serializer.ts to understand how snapshots are formatted

Applied to files:

  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-08T20:30:36.983Z
Learnt from: nams1570
Repo: stack-auth/stack-auth PR: 1091
File: apps/backend/src/lib/email-rendering.tsx:49-54
Timestamp: 2026-01-08T20:30:36.983Z
Learning: In apps/backend/src/lib/email-rendering.tsx, the nodeModules constant intentionally pins React and react-dom to version 19.1.1 (rather than matching the project's React version) because pinning provides more reliability for email rendering in the sandbox environment.

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use `runAsynchronously` or `runAsynchronouslyWithAlert` instead of general try-catch error handling

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : Code defensively using `?? throwErr(...)` instead of non-null assertions, with good error messages explicitly stating violated assumptions

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : Try to avoid the `any` type. When using `any`, leave a comment explaining why and how the type system fails or how errors would still be caught

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use `usePathname` instead of `await params`)

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to packages/stack-shared/src/config/schema.ts : Whenever making backwards-incompatible changes to the config schema, update the migration functions in `packages/stack-shared/src/config/schema.ts`

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to {.env*,**/*.{ts,tsx,js,jsx}} : All environment variables should be prefixed with `STACK_` (or `NEXT_PUBLIC_STACK_` if public) to ensure Turborepo picks up changes and improve readability

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
  • apps/backend/.env
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2025-08-12T17:55:06.710Z
Learnt from: madster456
Repo: stack-auth/stack-auth PR: 769
File: apps/backend/.env:67-71
Timestamp: 2025-08-12T17:55:06.710Z
Learning: In the stack-auth project, .env files use inline comments after the = sign (e.g., KEY=# description) as a consistent pattern throughout the configuration file. The maintainers prefer this format over separate comment lines above each key.

Applied to files:

  • apps/backend/.env
🪛 dotenv-linter (4.0.0)
apps/backend/.env

[warning] 90-90: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 91-91: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 92-92: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: setup-tests
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test-with-custom-base-port
🔇 Additional comments (13)
apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts (2)

105-105: LGTM: Wait time increase is reasonable.

Increasing the wait from 5 to 10 seconds is a pragmatic fix for test flakiness, especially given the backend changes to email rendering with the new JS execution framework.


161-162: Subject filter broadened - verify this is intentional.

The subject filter was changed from "Sign in to" to "Sign in", making it less specific. This broader filter could potentially match additional email subjects beyond the original intent (e.g., "Sign in link" vs "Sign in to ProjectName").

The { repeats: 10 } retry mechanism is a reasonable approach for flaky tests, but consider whether the broader filter might inadvertently hide test failures by filtering out emails that should be checked.

apps/backend/.env (1)

90-92: LGTM: Vercel sandbox environment variables properly added.

The new environment variables follow the project conventions:

  • Properly prefixed with STACK_ as per coding guidelines
  • Alphabetically ordered (addressing the past review comment)
  • Using the inline comment pattern consistent with the rest of this file

The static analysis warnings about ValueWithoutQuotes are false positives—these are placeholder comments, not actual values. Based on learnings, inline comments after = is the preferred pattern in this project.

apps/backend/src/lib/js-execution.tsx (5)

19-53: LGTM: Freestyle engine implementation is well-structured.

The freestyle engine properly:

  • Validates the API key and environment
  • Handles mock key for development/test environments
  • Throws StackAssertionError with appropriate context when execution returns undefined

78-135: LGTM: Vercel sandbox engine with proper resource cleanup.

The try/finally pattern correctly ensures sandbox.stop() is called even when execution fails. The error handling uses StackAssertionError consistently as required.

One minor note: the stream module import on line 114 could be moved to the top-level for slight performance improvement, but it's acceptable as-is since Node.js caches module imports.


195-206: LGTM: Sanity test validation is correct.

The implementation properly:

  • Returns early when fewer than 2 engines succeed (can't meaningfully compare)
  • Uses JSON.stringify for deep comparison (addressing the past review about object comparison with ===)
  • Captures errors for monitoring when engines return different results

Note: JSON.stringify comparison has edge cases (key ordering, undefined values), but for structured EmailRenderResult objects with consistent shapes, this is acceptable.


209-247: LGTM: Fallback mechanism with proper retry logic.

The implementation correctly:

  • Gives the primary engine (freestyle) 2 attempts with exponential backoff
  • Falls back to secondary engines with single attempts
  • Captures errors for observability when falling back
  • Propagates the last engine's error when all fail

The exponentialDelayBase: 500 provides a reasonable 500ms initial delay with exponential growth for retries.


147-173: LGTM: Main execution function with observability and sanity testing.

The implementation properly:

  • Uses traceSpan for telemetry with relevant attributes
  • Allows explicit engine selection to bypass fallback
  • Runs 5% sanity tests asynchronously without blocking the main execution path
  • Uses runAsynchronouslyAndWaitUntil which aligns with the coding guidelines (not voiding promises)
apps/backend/src/lib/email-rendering.tsx (5)

50-55: LGTM: Pinned node module versions.

The React 19.1.1 pinning (rather than matching the project's 19.2.3) is intentional for sandbox reliability, as confirmed in previous review discussions. Based on learnings, pinning provides more consistency in the sandbox environment.


69-72: LGTM: Well-defined discriminated union types.

ExecuteResult is properly defined as a discriminated union, enabling TypeScript to narrow types based on the status field. This addresses the previous review suggestion for improved type safety.


87-109: LGTM: Execution flow with appropriate fallback layers.

The implementation correctly separates two fallback concerns:

  1. Engine-level failures (network errors, timeouts) - handled by js-execution.tsx's runWithFallback
  2. Runtime errors (code executes but throws) - handled here with explicit vercel-sandbox retry

The production path (lines 95-109) correctly tries vercel-sandbox when freestyle returns an error status, and captures the discrepancy for monitoring. This distinction was clarified in previous review discussions.


130-183: LGTM: Updated renderEmailWithTemplate with proper typing.

The function now correctly returns Promise<Result<EmailRenderResult, string>> and uses the new bundleAndExecute flow. The standardized return type improves type safety and error handling consistency.


205-269: LGTM: Batched rendering updated to use new execution flow.

The renderEmailsForTenancyBatched function correctly:

  • Returns Promise<Result<EmailRenderResult[], string>> for consistency
  • Uses the shared entryJs wrapper and bundleAndExecute flow
  • Handles the empty requests case early (line 206-208)

The type assertion on line 268 is acceptable since the files object is built programmatically with /entry.js always present.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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.

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.

Greptile Overview

Greptile Summary

This PR refactors email rendering to use an updated freestyle API (v0.1.5) and introduces a robust fallback mechanism with Vercel Sandbox for improved reliability.

Major Changes:

  • Created new js-execution.tsx abstraction layer with pluggable execution engines (freestyle and vercel-sandbox)
  • Implemented automatic fallback: freestyle (with 1 retry) → vercel-sandbox
  • Added 5% sanity testing to compare engine results and detect discrepancies
  • Removed old freestyle.tsx wrapper in favor of the new abstraction
  • Simplified email-rendering.tsx by delegating execution logic to the new engine layer

Issues Found:

  • Critical: Sanity test comparison uses === for objects, which will always return false (line 190-191 in js-execution.tsx)
  • Critical: Sanity test doesn't validate successful results exist before comparing (line 175-188 in js-execution.tsx)
  • The manual fallback in bundleAndExecute (line 86-99 in email-rendering.tsx) bypasses the retry mechanism in runWithFallback, creating inconsistent behavior

Confidence Score: 2/5

  • This PR has critical bugs in the sanity test logic that will cause it to always report mismatches
  • The sanity test comparison logic has a critical flaw (using === for object comparison) that will cause false positives. Additionally, the sanity test doesn't validate that results exist before comparing them. While the main execution path appears solid, these bugs in monitoring/validation code need to be fixed before merge.
  • apps/backend/src/lib/js-execution.tsx requires fixes to the sanity test logic (lines 175-191)

Important Files Changed

File Analysis

Filename Score Overview
apps/backend/src/lib/js-execution.tsx 3/5 New JS execution engine abstraction with freestyle and vercel-sandbox fallback; has comparison logic issue in sanity test
apps/backend/src/lib/email-rendering.tsx 4/5 Refactored to use new JS execution abstraction with proper fallback handling and error recovery
apps/backend/package.json 5/5 Updated freestyle-sandboxes to v0.1.5 and added @vercel/sandbox v1.1.2

Sequence Diagram

sequenceDiagram
    participant Client as Email Renderer
    participant Bundler as bundleJavaScript
    participant Executor as executeJavascript
    participant Freestyle as Freestyle Engine
    participant Vercel as Vercel Sandbox
    participant Monitor as Sanity Test (5%)

    Client->>Bundler: Bundle email template code
    Bundler-->>Client: Return bundled ESM code
    
    Client->>Executor: executeJavascript(code, options)
    
    alt 5% of requests (sanity test)
        Executor->>Monitor: runSanityTest()
        par Run both engines
            Monitor->>Freestyle: execute(code)
            Monitor->>Vercel: execute(code)
        end
        Monitor->>Monitor: Compare results (log if mismatch)
    end
    
    Executor->>Freestyle: execute(code) [attempt 1]
    alt Freestyle succeeds
        Freestyle-->>Executor: Return result
        Executor-->>Client: Success
    else Freestyle fails (attempt 1)
        Executor->>Freestyle: execute(code) [attempt 2, retry]
        alt Freestyle succeeds on retry
            Freestyle-->>Executor: Return result
            Executor-->>Client: Success
        else Freestyle fails (attempt 2)
            Note over Executor: Log error, fallback to Vercel
            Executor->>Vercel: execute(code) [attempt 1]
            alt Vercel succeeds
                Vercel-->>Executor: Return result
                Executor-->>Client: Success
            else Vercel fails
                Vercel-->>Executor: Error
                Executor-->>Client: Throw error
            end
        end
    end
Loading

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 pull request updates the email rendering infrastructure to use the latest Freestyle API (v0.1.5) and introduces Vercel Sandbox as a fallback mechanism for improved reliability. The changes refactor the freestyle wrapper into a more extensible js-execution abstraction that supports multiple execution engines with automatic fallback, retry logic, and 5% sanity testing to detect discrepancies between engines.

Key changes:

  • New js-execution.tsx module providing an abstraction layer for JavaScript execution engines (Freestyle and Vercel Sandbox)
  • Automatic fallback from Freestyle to Vercel Sandbox with retry logic (2 attempts for first engine)
  • 5% random sanity testing to compare results across engines
  • Updates to email-rendering.tsx to use the new execution abstraction
  • Package updates: freestyle-sandboxes upgraded from ^0.0.92 to ^0.1.5, added @vercel/sandbox ^1.1.2

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
apps/backend/src/lib/js-execution.tsx New abstraction layer implementing Freestyle and Vercel Sandbox engines with fallback, retry, and sanity testing logic
apps/backend/src/lib/email-rendering.tsx Refactored to use executeJavascript from js-execution module; added bundleAndExecute helper; removed unused batched rendering function
apps/backend/src/lib/freestyle.tsx Removed - functionality migrated to js-execution.tsx
apps/backend/package.json Updated freestyle-sandboxes to v0.1.5, added @vercel/sandbox v1.1.2
apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts Increased test timeout from 5s to 10s; made email subject filter less specific

💡 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: 2

🤖 Fix all issues with AI agents
In @apps/backend/src/lib/email-rendering.tsx:
- Around line 84-102: The manual retry using executeJavascript with engine:
'vercel-sandbox' duplicates the built-in fallback in executeJavascript and may
cause double execution; remove the entire fallback block that creates
vercelResult (the try/ catch that calls executeJavascript a second time) and
instead trust the initial executeResult returned from executeJavascript,
returning Result.error(executeResult.error ?? "Unknown execution error") when
executeResult.status === "error"; also remove the empty catch that swallows
errors and the captureError/StackAssertionError call tied to the manual retry
since engine orchestration and sanity testing are handled by executeJavascript.
🧹 Nitpick comments (3)
apps/backend/src/lib/js-execution.tsx (3)

86-89: Consider using StackAssertionError for consistency.

For uniformity with the codebase's error handling patterns, consider using StackAssertionError instead of the generic Error class. This provides better context and telemetry.

♻️ Refactor to use StackAssertionError
          if (installResult.exitCode !== 0) {
-            throw new Error(`Failed to install packages: exit code ${installResult.exitCode}`);
+            throw new StackAssertionError(`Failed to install packages: exit code ${installResult.exitCode}`, { 
+              exitCode: installResult.exitCode, 
+              packages 
+            });
          }

120-122: Consider using StackAssertionError with additional context.

Including the captured stdout in the error would aid debugging when execution fails.

♻️ Refactor to use StackAssertionError
        if (runResult.exitCode !== 0) {
-          throw new Error(`Vercel Sandbox execution failed with exit code ${runResult.exitCode}`);
+          throw new StackAssertionError(`Vercel Sandbox execution failed with exit code ${runResult.exitCode}`, {
+            exitCode: runResult.exitCode,
+            stdout
+          });
        }

132-132: Consider using StackAssertionError with stdout for debugging.

Including the captured output would help diagnose why the result marker wasn't found.

♻️ Refactor to use StackAssertionError
-        throw new Error("Could not find result in Vercel Sandbox output");
+        throw new StackAssertionError("Could not find result in Vercel Sandbox output", { stdout });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66b2e65 and 115e16e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
  • apps/backend/src/lib/js-execution.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
💤 Files with no reviewable changes (1)
  • apps/backend/src/lib/freestyle.tsx
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors, never use toast; instead, use alerts as toasts are easily missed by the user

Files:

  • apps/backend/src/lib/js-execution.tsx
  • apps/backend/src/lib/email-rendering.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{tsx,css}: Keep hover/click animations snappy and fast; don't delay actions with pre-transitions (e.g., no fade-in on button hover) as it makes UI feel sluggish; instead apply transitions after the action like smooth fade-out when hover ends
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)

Files:

  • apps/backend/src/lib/js-execution.tsx
  • apps/backend/src/lib/email-rendering.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use usePathname instead of await params)

Files:

  • apps/backend/src/lib/js-execution.tsx
  • apps/backend/src/lib/email-rendering.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use runAsynchronously or runAsynchronouslyWithAlert instead
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/src/lib/js-execution.tsx
  • apps/backend/src/lib/email-rendering.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Code defensively; prefer ?? throwErr(...) over non-null assertions with good error messages explicitly stating violated assumptions
Avoid the any type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Files:

  • apps/backend/src/lib/js-execution.tsx
  • apps/backend/src/lib/email-rendering.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
**/e2e/**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

ALWAYS add new E2E tests when changing the API or SDK interface; err on the side of creating too many tests due to the critical nature of the authentication industry

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

When writing tests, prefer .toMatchInlineSnapshot() over other selectors if possible; check snapshot-serializer.ts to understand how snapshots are formatted and how non-deterministic values are handled

Files:

  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
🧠 Learnings (2)
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to {packages/stack,packages/js}/**/*.{ts,tsx,js,jsx} : NEVER UPDATE packages/stack OR packages/js; instead, update packages/template as those packages are copies of it

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/e2e/**/*.{test,spec}.{ts,tsx,js,jsx} : ALWAYS add new E2E tests when changing the API or SDK interface; err on the side of creating too many tests due to the critical nature of the authentication industry

Applied to files:

  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
🧬 Code graph analysis (2)
apps/backend/src/lib/email-rendering.tsx (4)
packages/stack-shared/src/utils/strings.tsx (1)
  • deindent (235-238)
packages/stack-shared/src/utils/esbuild.tsx (1)
  • bundleJavaScript (43-203)
apps/backend/src/lib/js-execution.tsx (1)
  • executeJavascript (147-173)
packages/stack-shared/src/utils/errors.tsx (2)
  • captureError (126-134)
  • StackAssertionError (69-85)
apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts (1)
packages/stack-shared/src/utils/promises.tsx (1)
  • wait (260-268)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests
  • GitHub Check: all-good
  • GitHub Check: docker
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: build (22.x)
🔇 Additional comments (7)
apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts (1)

105-105: LGTM - Timing adjustment aligns with new execution model.

The increased wait time (5s → 10s) appropriately accounts for the new JS execution engine with fallback and retry mechanisms introduced in this PR.

apps/backend/src/lib/js-execution.tsx (3)

19-53: LGTM - Freestyle engine implementation is clean.

The mock key handling for dev/test environments and proper error checking for undefined results are well-implemented.


164-168: LGTM - Async sanity testing approach is sound.

Running the sanity test via runAsynchronouslyAndWaitUntil allows it to execute in the background without blocking the main execution path, which is appropriate for telemetry and monitoring.


209-220: LGTM - Result-based error handling is appropriate.

The try-catch pattern here correctly converts thrown errors into Result types for the retry mechanism, which is a valid use case for structured error handling.

apps/backend/src/lib/email-rendering.tsx (2)

135-176: LGTM - Clean refactor to unified execution model.

The refactoring to use bundleAndExecute properly separates bundling from execution and maintains type safety throughout the rendering pipeline.


198-262: LGTM - Batched rendering refactor is well-structured.

The batched rendering implementation efficiently handles multiple render requests by dynamically generating imports and render calls, maintaining type safety throughout.

apps/backend/package.json (1)

76-76: Package versions are valid and secure.

Both @vercel/sandbox@^1.1.2 and freestyle-sandboxes@^0.1.5 exist in the npm registry with no known security vulnerabilities. @vercel/sandbox is at 1.1.2 (latest is 1.1.5, but within the caret range), and freestyle-sandboxes is at the latest version 0.1.5.

@nams1570 nams1570 force-pushed the vercel-backup-email-render branch from 115e16e to 84b3526 Compare January 8, 2026 19:25
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap

Screenshot capture was skipped.

No UI changes detected - screenshots skipped


Generated by cmux preview system

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/email-rendering.tsx:
- Around line 86-100: Remove the redundant vercel-sandbox retry and the empty
catch: inside the executeResult.status === "error" branch delete the try/catch
that calls executeJavascript with engine: 'vercel-sandbox' and the
StackAssertionError capture; instead immediately return
Result.error(executeResult.error ?? "Unknown execution error"). This removes the
duplicated fallback logic (executeJavascript already handles engine fallbacks)
and eliminates the empty catch that swallows errors; if you need special
handling keep a documented, explicit error handler but do not silently catch all
exceptions.
🧹 Nitpick comments (6)
apps/backend/src/lib/js-execution.tsx (4)

94-99: Hardcoded assumption about code module structure.

The runner script assumes the code exports a default async function (import fn from './code.mjs'). This is tightly coupled to the current usage pattern but makes this engine less flexible for future use cases. If the code doesn't export a default function, execution will fail with a runtime error.

Consider documenting this assumption or making it more flexible.


85-89: Consider adding timeout protection for npm install.

The npm install command could hang indefinitely if the registry is slow or unresponsive. While the sandbox has a 30-second overall timeout (line 71), adding explicit handling would provide better error messages.


140-143: Module-level engine initialization may fail during startup.

Creating engines at module load time means any environment variable issues or initialization errors will cause import failures. This can make the entire backend fail to start if, for example, STACK_FREESTYLE_API_KEY is missing, even if the code is never called.

Consider lazy initialization or wrapping engine creation in a try-catch with a clear error message about missing configuration.

♻️ Proposed lazy initialization approach
-const engineMap = new Map<string, JsEngine>([
-  ['freestyle', createFreestyleEngine()],
-  ['vercel-sandbox', createVercelSandboxEngine()],
-]);
+let engineMap: Map<string, JsEngine> | undefined;
+
+function getEngineMap(): Map<string, JsEngine> {
+  if (!engineMap) {
+    engineMap = new Map([
+      ['freestyle', createFreestyleEngine()],
+      ['vercel-sandbox', createVercelSandboxEngine()],
+    ]);
+  }
+  return engineMap;
+}

-const engines: JsEngine[] = Array.from(engineMap.values());
+function getEngines(): JsEngine[] {
+  return Array.from(getEngineMap().values());
+}

Then update usages of engineMap and engines to call these functions.


209-247: Consider preserving all engine failures for better diagnostics.

Line 246 throws only the last error, which discards diagnostic information from previous engine failures. When all engines fail, developers need visibility into why each engine failed to identify whether it's a code issue or an infrastructure issue.

♻️ Proposed aggregate error approach
-  throw errors[errors.length - 1].error;
+  throw new StackAssertionError(
+    `All JS execution engines failed (${errors.length} engines)`,
+    { 
+      errors: errors.map(e => ({ engine: e.engine, error: String(e.error) })),
+      lastError: errors[errors.length - 1].error
+    }
+  );
apps/backend/src/lib/email-rendering.tsx (2)

84-84: Add runtime type validation for executeJavascript result.

Line 84 uses a type assertion (as ExecuteResult) without runtime validation. If executeJavascript returns an unexpected shape due to a bug or API change, the subsequent property access on line 86 could fail with a confusing error.

♻️ Proposed defensive validation
  const executeResult = await executeJavascript(bundle.data, { nodeModules }) as ExecuteResult;
+  
+  if (!executeResult || typeof executeResult !== 'object' || !('status' in executeResult)) {
+    return Result.error("Invalid execution result format");
+  }

  if (executeResult.status === "error") {

259-261: Type assertion is safe but fragile.

The type assertion on line 261 is necessary and currently safe (line 259 ensures /entry.js exists). However, this pattern is fragile—if line 259 is accidentally removed during refactoring, the type system won't catch the error.

♻️ Proposed type-safe approach
-  files["/entry.js"] = entryJs;
-
-  return await bundleAndExecute<EmailRenderResult[]>(files as Record<string, string> & { '/entry.js': string });
+  const filesWithEntry: Record<string, string> & { '/entry.js': string } = {
+    ...files,
+    '/entry.js': entryJs,
+  };
+
+  return await bundleAndExecute<EmailRenderResult[]>(filesWithEntry);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 115e16e and 84b3526.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
  • apps/backend/src/lib/js-execution.tsx
💤 Files with no reviewable changes (1)
  • apps/backend/src/lib/freestyle.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors, never use toast; instead, use alerts as toasts are easily missed by the user

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{tsx,css}: Keep hover/click animations snappy and fast; don't delay actions with pre-transitions (e.g., no fade-in on button hover) as it makes UI feel sluggish; instead apply transitions after the action like smooth fade-out when hover ends
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use usePathname instead of await params)

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use runAsynchronously or runAsynchronouslyWithAlert instead
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Code defensively; prefer ?? throwErr(...) over non-null assertions with good error messages explicitly stating violated assumptions
Avoid the any type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
🧠 Learnings (5)
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use `runAsynchronously` or `runAsynchronouslyWithAlert` instead

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx} : Code defensively; prefer `?? throwErr(...)` over non-null assertions with good error messages explicitly stating violated assumptions

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : When writing tests, prefer `.toMatchInlineSnapshot()` over other selectors if possible; check snapshot-serializer.ts to understand how snapshots are formatted and how non-deterministic values are handled

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/e2e/**/*.{test,spec}.{ts,tsx,js,jsx} : ALWAYS add new E2E tests when changing the API or SDK interface; err on the side of creating too many tests due to the critical nature of the authentication industry

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Always run typecheck, lint, and test to ensure changes are working as expected; can optimize by only linting and testing changed files and related E2E tests

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
🧬 Code graph analysis (1)
apps/backend/src/lib/js-execution.tsx (5)
packages/stack-shared/src/utils/env.tsx (2)
  • getEnvVariable (16-58)
  • getNodeEnvironment (76-78)
packages/stack-shared/src/utils/errors.tsx (2)
  • StackAssertionError (69-85)
  • captureError (126-134)
apps/dev-launchpad/scripts/write-env-config.js (1)
  • prefix (6-6)
apps/backend/src/utils/vercel.tsx (1)
  • runAsynchronouslyAndWaitUntil (5-9)
packages/stack-shared/src/utils/results.tsx (1)
  • error (36-41)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: setup-tests
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: restart-dev-and-test
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
🔇 Additional comments (2)
apps/backend/package.json (1)

76-76: Both packages are published and legitimate.

@vercel/[email protected] and [email protected] are both valid, published versions with no known vulnerabilities.

apps/backend/src/lib/js-execution.tsx (1)

19-53: Verify Freestyle API compatibility with version 0.1.5.

The code uses freestyle.serverless.runs.create with a config.nodeModules parameter. While the code is integrated and the mock server validates this structure, confirm the exact API signature matches [email protected] by checking the official package documentation or release notes for this version.

@nams1570 nams1570 force-pushed the vercel-backup-email-render branch from 84b3526 to f7104f0 Compare January 8, 2026 20:15
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap

Screenshot capture was skipped.

No UI changes detected - screenshots skipped


Generated by cmux preview system

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/backend/package.json:
- Line 76: The dependency entry "@vercel/sandbox": "^1.1.2" should be updated to
the latest published patch version and its security implications reviewed;
change the version string to "@vercel/sandbox": "^1.1.5" in package.json, run
yarn/npm install to update lockfile, and add a short note in your repo's
security or README documenting that this package is beta and runs code in
isolated VMs—include the risk of sandbox escape and any mitigations or audit
checks you performed before deploying.

In @apps/backend/src/lib/email-rendering.tsx:
- Around line 49-54: The nodeModules constant pins "react" and "react-dom" to
19.1.1 which conflicts with the project's React 19.2.3 and can cause
runtime/template mismatches; update the nodeModules object (the nodeModules
constant in email-rendering.tsx) to use "react" and "react-dom" version "19.2.3"
to match project dependencies, or if 19.1.1 is intentionally required for the
sandbox, add a brief comment next to the nodeModules definition explaining why
that older version is pinned and documenting compatibility rationale.
🧹 Nitpick comments (2)
apps/backend/src/lib/email-rendering.tsx (2)

68-69: Use a discriminated union for stronger type safety.

ExecuteResult with status: string is too loose. A discriminated union would enable exhaustive checking and prevent invalid states.

♻️ Proposed fix
 type EmailRenderResult = { html: string, text: string, subject?: string, notificationCategory?: string };
-type ExecuteResult = { status: string, data?: unknown, error?: string };
+type ExecuteResult = 
+  | { status: "ok", data: unknown }
+  | { status: "error", error: string };

84-98: Add runtime validation for type assertions or document why they're safe.

Multiple as casts bypass the type system without validation. Per coding guidelines, when type assertions are unavoidable, add a comment explaining why and how errors would be caught.

Consider adding runtime validation:

♻️ Proposed approach with validation
+function isExecuteResult(value: unknown): value is ExecuteResult {
+  if (typeof value !== 'object' || value === null) return false;
+  const obj = value as Record<string, unknown>;
+  return obj.status === 'ok' || obj.status === 'error';
+}
+
 async function bundleAndExecute<T>(
   files: Record<string, string> & { '/entry.js': string }
 ): Promise<Result<T, string>> {
   // ... bundling code ...

-  const executeResult = await executeJavascript(bundle.data, { nodeModules }) as ExecuteResult;
+  const rawResult = await executeJavascript(bundle.data, { nodeModules });
+  // Runtime validation required: executeJavascript returns unknown from sandbox
+  if (!isExecuteResult(rawResult)) {
+    return Result.error("Invalid execution result shape from sandbox");
+  }
+  const executeResult = rawResult;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84b3526 and f7104f0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
  • apps/backend/src/lib/js-execution.tsx
💤 Files with no reviewable changes (1)
  • apps/backend/src/lib/freestyle.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/backend/src/lib/js-execution.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors, never use toast; instead, use alerts as toasts are easily missed by the user

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{tsx,css}: Keep hover/click animations snappy and fast; don't delay actions with pre-transitions (e.g., no fade-in on button hover) as it makes UI feel sluggish; instead apply transitions after the action like smooth fade-out when hover ends
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use usePathname instead of await params)

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use runAsynchronously or runAsynchronouslyWithAlert instead
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Code defensively; prefer ?? throwErr(...) over non-null assertions with good error messages explicitly stating violated assumptions
Avoid the any type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Files:

  • apps/backend/src/lib/email-rendering.tsx
🧠 Learnings (4)
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use `runAsynchronously` or `runAsynchronouslyWithAlert` instead

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx} : Code defensively; prefer `?? throwErr(...)` over non-null assertions with good error messages explicitly stating violated assumptions

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx} : Avoid the `any` type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to {packages/stack,packages/js}/**/*.{ts,tsx,js,jsx} : NEVER UPDATE packages/stack OR packages/js; instead, update packages/template as those packages are copies of it

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
🧬 Code graph analysis (1)
apps/backend/src/lib/email-rendering.tsx (3)
packages/stack-shared/src/utils/strings.tsx (1)
  • deindent (235-238)
packages/stack-shared/src/utils/esbuild.tsx (1)
  • bundleJavaScript (43-203)
apps/backend/src/lib/js-execution.tsx (1)
  • executeJavascript (147-173)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: check_prisma_migrations (22.x)
🔇 Additional comments (4)
apps/backend/src/lib/email-rendering.tsx (3)

56-66: LGTM!

The try-catch pattern here is appropriate since this code runs in an isolated sandbox where errors must be caught and serialized back as structured data. This is distinct from the guideline against "try-catch-all" which targets swallowing errors in normal application code.


119-172: LGTM!

The refactored renderEmailWithTemplate cleanly adopts the new bundle-and-execute pattern. The structured Result<EmailRenderResult, string> return type properly communicates success/failure states.


194-258: LGTM!

The batched rendering function is properly refactored. The type assertion on line 257 is acceptable since we've explicitly added /entry.js two lines prior — TypeScript simply can't narrow Record<string, string> after dynamic key assignment.

apps/backend/package.json (1)

82-82: No action needed. Version 0.1.5 is confirmed as the latest available version with no identified security concerns. The upgrade appears intentional and validated by the PR context.

@nams1570 nams1570 requested a review from N2D4 January 8, 2026 20:27
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap

Screenshot capture was skipped.

No UI changes detected - screenshots skipped


Generated by cmux preview system

@nams1570 nams1570 force-pushed the vercel-backup-email-render branch from 654c253 to afa1e43 Compare January 8, 2026 20:59
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

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: 0

🧹 Nitpick comments (2)
apps/backend/src/lib/email-rendering.tsx (2)

68-69: Consider using a literal union type for status.

The ExecuteResult.status is typed as string, but it only has two valid values. A union type would provide better type safety and enable exhaustive checking.

🔧 Suggested improvement
 type EmailRenderResult = { html: string, text: string, subject?: string, notificationCategory?: string };
-type ExecuteResult = { status: string, data?: unknown, error?: string };
+type ExecuteResult = { status: "ok" | "error", data?: unknown, error?: string };

86-96: Fix inconsistent indentation in the fallback block.

Lines 91-95 have extra indentation that doesn't match the block structure. While the logic is correct (capture error and return vercel result when freestyle fails but vercel succeeds), the formatting is misleading.

🧹 Formatting fix
   if (executeResult.status === "error") {
     const vercelResult = await executeJavascript(bundle.data, { nodeModules, engine: 'vercel-sandbox' }) as ExecuteResult;
     if (vercelResult.status === "error") {
       return Result.error(executeResult.error ?? "Unknown execution error");
     }
-      captureError("email-rendering-freestyle-runtime-error", new StackAssertionError(
-        "Email rendering failed with freestyle but succeeded with vercel-sandbox",
-        { freestyleError: executeResult.error }
-      ));
-      return Result.ok(vercelResult.data as T);
+    captureError("email-rendering-freestyle-runtime-error", new StackAssertionError(
+      "Email rendering failed with freestyle but succeeded with vercel-sandbox",
+      { freestyleError: executeResult.error }
+    ));
+    return Result.ok(vercelResult.data as T);
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f7104f0 and afa1e43.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
  • apps/backend/src/lib/js-execution.tsx
💤 Files with no reviewable changes (1)
  • apps/backend/src/lib/freestyle.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/backend/src/lib/js-execution.tsx
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors, never use toast; instead, use alerts as toasts are easily missed by the user

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{tsx,css}: Keep hover/click animations snappy and fast; don't delay actions with pre-transitions (e.g., no fade-in on button hover) as it makes UI feel sluggish; instead apply transitions after the action like smooth fade-out when hover ends
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use usePathname instead of await params)

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use runAsynchronously or runAsynchronouslyWithAlert instead
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Code defensively; prefer ?? throwErr(...) over non-null assertions with good error messages explicitly stating violated assumptions
Avoid the any type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Files:

  • apps/backend/src/lib/email-rendering.tsx
🧠 Learnings (4)
📚 Learning: 2026-01-08T20:30:28.914Z
Learnt from: nams1570
Repo: stack-auth/stack-auth PR: 1091
File: apps/backend/src/lib/email-rendering.tsx:49-54
Timestamp: 2026-01-08T20:30:28.914Z
Learning: In apps/backend/src/lib/email-rendering.tsx, the nodeModules constant intentionally pins React and react-dom to version 19.1.1 (rather than matching the project's React version) because pinning provides more reliability for email rendering in the sandbox environment.

Applied to files:

  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use `runAsynchronously` or `runAsynchronouslyWithAlert` instead

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx} : Code defensively; prefer `?? throwErr(...)` over non-null assertions with good error messages explicitly stating violated assumptions

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx} : Avoid the `any` type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
🧬 Code graph analysis (1)
apps/backend/src/lib/email-rendering.tsx (4)
packages/stack-shared/src/utils/strings.tsx (1)
  • deindent (235-238)
packages/stack-shared/src/utils/esbuild.tsx (1)
  • bundleJavaScript (43-203)
apps/backend/src/lib/js-execution.tsx (1)
  • executeJavascript (147-173)
packages/stack-shared/src/utils/errors.tsx (1)
  • StackAssertionError (69-85)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests-with-custom-base-port
🔇 Additional comments (6)
apps/backend/src/lib/email-rendering.tsx (5)

1-8: LGTM!

The new imports (executeJavascript, bundleJavaScript, deindent) correctly support the refactored bundle-and-execute flow.


49-66: LGTM!

The nodeModules version pinning is intentional for sandbox reliability. The entryJs try-catch wrapper appropriately captures runtime errors from user-provided email templates within the isolated sandbox environment. Based on learnings, the React version pinning to 19.1.1 (rather than the project's 19.2.3) is deliberate.


119-172: LGTM!

The refactoring cleanly delegates to bundleAndExecute while preserving the existing validation logic for user/project requirements. The return type properly reflects the new EmailRenderResult type.


194-258: LGTM!

The batched rendering function is cleanly refactored to use bundleAndExecute. The type assertion on line 257 is justified since /entry.js is explicitly assigned on line 255.


260-365: No changes to review.

These utility functions (findComponentValueUtil and stackframeEmailsPackage) appear unchanged and support the email rendering logic.

apps/backend/package.json (1)

76-76: Both dependency versions are valid and have no security advisories.

@vercel/sandbox@^1.1.2 and freestyle-sandboxes@^0.1.5 both exist on npm and allow appropriate minor/patch updates via caret constraints.

@nams1570 nams1570 force-pushed the vercel-backup-email-render branch from afa1e43 to ff31a3c Compare January 8, 2026 21:06
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap

Screenshot capture was skipped.

No UI changes detected - screenshots skipped


Generated by cmux preview system

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/backend/.env:
- Around line 90-92: Reorder the three Vercel sandbox env vars in
apps/backend/.env so they are alphabetical: change the sequence from
STACK_VERCEL_SANDBOX_TOKEN, STACK_VERCEL_SANDBOX_TEAM_ID,
STACK_VERCEL_SANDBOX_PROJECT_ID to STACK_VERCEL_SANDBOX_PROJECT_ID,
STACK_VERCEL_SANDBOX_TEAM_ID, STACK_VERCEL_SANDBOX_TOKEN by moving the
PROJECT_ID line before TEAM_ID and TOKEN, keeping the existing comments/values
unchanged.

In @apps/backend/src/lib/email-rendering.tsx:
- Around line 71-99: The code in bundleAndExecute casts executeJavascript
results to ExecuteResult without checking shape; add a runtime type guard (e.g.,
isExecuteResult(obj)) that verifies the presence and types of status, data, and
error before accessing them, and use it to validate both executeResult and
vercelResult; if a result fails the guard, capture/log the invalid response (use
captureError or process logger) and return Result.error with a clear message
instead of indexing into a malformed object so defensive handling prevents
runtime crashes.
🧹 Nitpick comments (1)
apps/backend/src/lib/email-rendering.tsx (1)

56-66: Consider capturing more error details in sandboxed execution.

The error handler uses String(e) which may lose important debugging information such as stack traces. Consider capturing additional error properties:

♻️ Enhanced error capture
 const entryJs = deindent`
   export default async () => {
     try {
       const { renderAll } = await import("./render.tsx");
       const result = await renderAll();
       return { status: "ok", data: result };
     } catch (e) {
-      return { status: "error", error: String(e) };
+      return { 
+        status: "error", 
+        error: e instanceof Error ? e.message : String(e),
+        stack: e instanceof Error ? e.stack : undefined
+      };
     }
   };
 `;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afa1e43 and cfaf695.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • apps/backend/.env
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
  • apps/backend/src/lib/js-execution.tsx
💤 Files with no reviewable changes (1)
  • apps/backend/src/lib/freestyle.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/backend/src/lib/js-execution.tsx
  • apps/backend/package.json
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors, never use toast; instead, use alerts as toasts are easily missed by the user

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{tsx,css}: Keep hover/click animations snappy and fast; don't delay actions with pre-transitions (e.g., no fade-in on button hover) as it makes UI feel sluggish; instead apply transitions after the action like smooth fade-out when hover ends
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use usePathname instead of await params)

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use runAsynchronously or runAsynchronouslyWithAlert instead
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Code defensively; prefer ?? throwErr(...) over non-null assertions with good error messages explicitly stating violated assumptions
Avoid the any type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Files:

  • apps/backend/src/lib/email-rendering.tsx
🧠 Learnings (7)
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to .env*,**/.env* : Any environment variables created should be prefixed with `STACK_` (or `NEXT_PUBLIC_STACK_` if public) to ensure Turborepo picks up changes and improves readability

Applied to files:

  • apps/backend/.env
📚 Learning: 2025-08-12T17:55:06.710Z
Learnt from: madster456
Repo: stack-auth/stack-auth PR: 769
File: apps/backend/.env:67-71
Timestamp: 2025-08-12T17:55:06.710Z
Learning: In the stack-auth project, .env files use inline comments after the = sign (e.g., KEY=# description) as a consistent pattern throughout the configuration file. The maintainers prefer this format over separate comment lines above each key.

Applied to files:

  • apps/backend/.env
📚 Learning: 2026-01-08T20:30:28.914Z
Learnt from: nams1570
Repo: stack-auth/stack-auth PR: 1091
File: apps/backend/src/lib/email-rendering.tsx:49-54
Timestamp: 2026-01-08T20:30:28.914Z
Learning: In apps/backend/src/lib/email-rendering.tsx, the nodeModules constant intentionally pins React and react-dom to version 19.1.1 (rather than matching the project's React version) because pinning provides more reliability for email rendering in the sandbox environment.

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use `runAsynchronously` or `runAsynchronouslyWithAlert` instead

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx} : Code defensively; prefer `?? throwErr(...)` over non-null assertions with good error messages explicitly stating violated assumptions

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx} : Avoid the `any` type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to {packages/stack,packages/js}/**/*.{ts,tsx,js,jsx} : NEVER UPDATE packages/stack OR packages/js; instead, update packages/template as those packages are copies of it

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
🧬 Code graph analysis (1)
apps/backend/src/lib/email-rendering.tsx (4)
packages/stack-shared/src/utils/strings.tsx (1)
  • deindent (235-238)
packages/stack-shared/src/utils/esbuild.tsx (1)
  • bundleJavaScript (43-203)
apps/backend/src/lib/js-execution.tsx (1)
  • executeJavascript (147-173)
packages/stack-shared/src/utils/errors.tsx (1)
  • StackAssertionError (69-85)
🪛 dotenv-linter (4.0.0)
apps/backend/.env

[warning] 90-90: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 91-91: [UnorderedKey] The STACK_VERCEL_SANDBOX_TEAM_ID key should go before the STACK_VERCEL_SANDBOX_TOKEN key

(UnorderedKey)


[warning] 91-91: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 92-92: [UnorderedKey] The STACK_VERCEL_SANDBOX_PROJECT_ID key should go before the STACK_VERCEL_SANDBOX_TEAM_ID key

(UnorderedKey)


[warning] 92-92: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: all-good
  • GitHub Check: docker
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
🔇 Additional comments (6)
apps/backend/.env (1)

90-92: Environment variables are correctly named and prefixed.

All three new Vercel sandbox variables follow the project's naming conventions with the STACK_ prefix and use the established inline comment pattern. No concerns with the structure beyond the ordering noted above.

apps/backend/src/lib/email-rendering.tsx (5)

1-9: LGTM: Import changes align with new execution engine.

The transition from Freestyle to executeJavascript and the addition of bundleJavaScript correctly support the new bundle-and-execute architecture described in the PR objectives.


68-69: Type definitions look reasonable.

The types use unknown appropriately instead of any, and the structure matches the expected execution results.


101-173: LGTM: Function correctly refactored to use bundle-and-execute approach.

The renderEmailWithTemplate function properly constructs the files object and delegates to bundleAndExecute. The generated render logic handles arktype validation errors appropriately and structures the output correctly.


194-258: LGTM: Batch rendering correctly refactored.

The renderEmailsForTenancyBatched function properly constructs files for batch rendering and delegates to bundleAndExecute. The type assertion at line 257 is necessary for TypeScript to recognize the /entry.js key exists in the files object.


49-54: No action needed. The nodeModules constant intentionally pins React and react-dom to 19.1.1 for sandbox email rendering reliability, as documented. This is a separate, isolated React instance from the project's main version (19.2.3), and the version pinning does not cause compatibility issues within the email-rendering sandbox context. React 19.2's changes (useId prefix, SSR semantics, new APIs) do not affect email rendering with react-email in an isolated sandbox environment.

@nams1570 nams1570 force-pushed the vercel-backup-email-render branch from cfaf695 to ccb604b Compare January 8, 2026 21:16
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

1 similar comment
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

@nams1570 nams1570 force-pushed the vercel-backup-email-render branch from 674a0ee to e9dc7cf Compare January 8, 2026 22:01
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Diff Heatmap

Preview screenshots are being captured...

Workspace and dev browser links will appear here once the preview environment is ready.


Generated by cmux preview system

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: 4

🤖 Fix all issues with AI agents
In @apps/backend/src/lib/email-rendering.tsx:
- Around line 69-70: The ExecuteResult type is too loose; replace it with a
discriminated union so TypeScript enforces that "ok" results carry data and
"error" results carry an error string; specifically change ExecuteResult to a
union like { status: "ok"; data: unknown; } | { status: "error"; error: string;
} (add other optional fields only on the appropriate branch if needed) and
update call sites that check executeResult.status (e.g., where Result.ok and
Result.error are used) to rely on the narrowed types.

In @apps/backend/src/lib/js-execution.tsx:
- Around line 87-89: Replace the generic throw new Error(...) used when
installResult.exitCode !== 0 with throw new StackAssertionError(...) consistent
with other errors in this module (see existing StackAssertionError usage at
lines ~64/109/125/131); use the same message format including exitCode (e.g.,
"Failed to install packages: exit code X") and ensure StackAssertionError is
imported/used the same way as the other occurrences in this file so the error
type and stack-handling behavior remain consistent.
🧹 Nitpick comments (2)
apps/backend/src/lib/email-rendering.tsx (1)

98-98: Improve error context when both engines fail.

When both freestyle (via auto mode) and the explicit vercel-sandbox retry fail, returning only executeResult.error loses context about the vercel-sandbox failure. This makes debugging harder.

💬 Provide more error context
 if (vercelResult.status === "error") {
-  return Result.error(executeResult.error ?? "Unknown execution error");
+  return Result.error(
+    `All engines failed. Freestyle: ${executeResult.error ?? "unknown"}; Vercel: ${vercelResult.error ?? "unknown"}`
+  );
 }
apps/backend/src/lib/js-execution.tsx (1)

246-246: Consider preserving context from all engine failures.

When all engines fail, throwing only the last error (line 246) loses context about earlier engine failures. While captureError preserves this in telemetry, the thrown error could be more informative for immediate debugging.

💡 Aggregate all errors
+if (errors.length > 0) {
+  const errorSummary = errors.map(e => `${e.engine}: ${String(e.error)}`).join('; ');
+  throw new StackAssertionError(
+    `All JS execution engines failed: ${errorSummary}`,
+    { errors }
+  );
+}
 throw errors[errors.length - 1].error;
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cfaf695 and e9dc7cf.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • apps/backend/.env
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
  • apps/backend/src/lib/js-execution.tsx
💤 Files with no reviewable changes (1)
  • apps/backend/src/lib/freestyle.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/backend/package.json
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{tsx,ts,jsx,js}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors, never use toast; instead, use alerts as toasts are easily missed by the user

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{tsx,css}: Keep hover/click animations snappy and fast; don't delay actions with pre-transitions (e.g., no fade-in on button hover) as it makes UI feel sluggish; instead apply transitions after the action like smooth fade-out when hover ends
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

NEVER use Next.js dynamic functions if avoidable; prefer using client components instead to keep pages static (e.g., use usePathname instead of await params)

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use runAsynchronously or runAsynchronouslyWithAlert instead
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Code defensively; prefer ?? throwErr(...) over non-null assertions with good error messages explicitly stating violated assumptions
Avoid the any type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Files:

  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/js-execution.tsx
🧠 Learnings (9)
📚 Learning: 2026-01-08T20:30:28.914Z
Learnt from: nams1570
Repo: stack-auth/stack-auth PR: 1091
File: apps/backend/src/lib/email-rendering.tsx:49-54
Timestamp: 2026-01-08T20:30:28.914Z
Learning: In apps/backend/src/lib/email-rendering.tsx, the nodeModules constant intentionally pins React and react-dom to version 19.1.1 (rather than matching the project's React version) because pinning provides more reliability for email rendering in the sandbox environment.

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER use .catch(console.error) or similar; use loading indicators instead; if asynchronous handling is necessary, use `runAsynchronously` or `runAsynchronouslyWithAlert` instead

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx} : Code defensively; prefer `?? throwErr(...)` over non-null assertions with good error messages explicitly stating violated assumptions

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{ts,tsx} : Avoid the `any` type; when necessary, leave a comment explaining why it's used, why the type system fails, and how errors would be caught at compile-, test-, or runtime

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : When writing tests, prefer `.toMatchInlineSnapshot()` over other selectors if possible; check snapshot-serializer.ts to understand how snapshots are formatted and how non-deterministic values are handled

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to **/e2e/**/*.{test,spec}.{ts,tsx,js,jsx} : ALWAYS add new E2E tests when changing the API or SDK interface; err on the side of creating too many tests due to the critical nature of the authentication industry

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Always run typecheck, lint, and test to ensure changes are working as expected; can optimize by only linting and testing changed files and related E2E tests

Applied to files:

  • apps/backend/src/lib/js-execution.tsx
📚 Learning: 2026-01-07T00:55:19.856Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-07T00:55:19.856Z
Learning: Applies to .env*,**/.env* : Any environment variables created should be prefixed with `STACK_` (or `NEXT_PUBLIC_STACK_` if public) to ensure Turborepo picks up changes and improves readability

Applied to files:

  • apps/backend/.env
📚 Learning: 2025-08-12T17:55:06.710Z
Learnt from: madster456
Repo: stack-auth/stack-auth PR: 769
File: apps/backend/.env:67-71
Timestamp: 2025-08-12T17:55:06.710Z
Learning: In the stack-auth project, .env files use inline comments after the = sign (e.g., KEY=# description) as a consistent pattern throughout the configuration file. The maintainers prefer this format over separate comment lines above each key.

Applied to files:

  • apps/backend/.env
🧬 Code graph analysis (1)
apps/backend/src/lib/email-rendering.tsx (5)
packages/stack-shared/src/utils/strings.tsx (1)
  • deindent (235-238)
packages/stack-shared/src/utils/esbuild.tsx (1)
  • bundleJavaScript (43-203)
packages/stack-shared/src/utils/env.tsx (1)
  • getNodeEnvironment (76-78)
apps/backend/src/lib/js-execution.tsx (1)
  • executeJavascript (147-173)
packages/stack-shared/src/utils/errors.tsx (1)
  • StackAssertionError (69-85)
🪛 dotenv-linter (4.0.0)
apps/backend/.env

[warning] 90-90: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 91-91: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 92-92: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
🔇 Additional comments (14)
apps/backend/.env (2)

90-92: LGTM! Vercel sandbox environment variables properly configured.

The new environment variables follow the project's STACK_ prefix convention and inline comment format. The static analysis warnings about missing quotes are false positives—empty placeholder values don't require quotes in .env files.


96-98: Verify these environment variables belong in this PR.

These Stripe and Telegram environment variables aren't mentioned in the PR objectives, which focus on updating email rendering to use freestyle and vercel sandbox. Are these intentionally included or should they be in a separate PR?

apps/backend/src/lib/email-rendering.tsx (5)

1-8: LGTM! Clean imports for the new execution model.

The new imports (executeJavascript and getNodeEnvironment) properly support the refactored bundle-and-execute flow.


50-55: LGTM! Pinned versions provide reliability.

The pinned React 19.1.1 version is intentional for sandbox reliability. The versions align with the React 19 features used in email rendering.

Based on learnings, pinning provides more reliability for email rendering in the sandbox environment.


57-67: LGTM! Proper entry point with structured error handling.

The try-catch at the entry boundary returns structured results rather than swallowing errors, which is appropriate for this execution boundary.


110-182: LGTM! Clean refactoring to bundle-and-execute model.

The function properly migrates to the new execution model while preserving the rendering logic. The Result-wrapped return type improves error handling ergonomics.


203-267: LGTM! Batch rendering properly migrated.

The batched email rendering logic is cleanly refactored to use the new bundle-and-execute flow while maintaining the batch optimization.

apps/backend/src/lib/js-execution.tsx (7)

1-17: LGTM! Clean type definitions.

The types properly define the execution options and engine interface with good separation of concerns.


19-53: LGTM! Solid freestyle engine implementation.

Good defensive coding with the undefined result check (lines 46-48) and proper environment-aware mock key handling (lines 26-32).


55-137: LGTM! Robust vercel sandbox implementation.

Good resource management with the finally block (line 134) ensuring sandbox cleanup, and proper validation of required environment variables (lines 63-67). The result reading via cat command with stream handling is well-implemented.


140-145: LGTM! Clean engine registry.

Using both a Map for lookup and an Array for ordered iteration provides a clean pattern for engine selection and fallback.


147-173: LGTM! Well-structured execution orchestration.

Excellent observability with tracing (lines 148-154), proper async handling with runAsynchronouslyAndWaitUntil for the sanity test (line 167), and clean separation of explicit vs. auto engine selection.


175-207: LGTM! Effective sanity testing.

The 5% sampling with result comparison provides good observability into engine consistency. Using JSON.stringify for comparison is pragmatic for this sanity check use case.


209-247: LGTM! Solid fallback implementation with retry logic.

The fallback mechanism with retry logic (2 attempts for first engine, 1 for others) and exponential backoff provides good resilience. Error capture for telemetry (lines 239-242) ensures visibility into engine failures.

@nams1570 nams1570 force-pushed the vercel-backup-email-render branch from e9dc7cf to b014062 Compare January 8, 2026 22:20
@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Older cmux preview screenshots (latest comment is below)

Preview Screenshots

Open Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap

Screenshot capture was skipped.

No UI changes detected - screenshots skipped


Generated by cmux preview system

@cmux-agent
Copy link

cmux-agent bot commented Jan 8, 2026

Preview Screenshots

Open Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap

Screenshot capture was skipped.

No UI changes detected - screenshots skipped


Generated by cmux preview system

@nams1570 nams1570 force-pushed the vercel-backup-email-render branch from 9f76df9 to 05dc86f Compare January 14, 2026 18:00
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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/backend/.env (1)

97-97: Fix typo and inconsistent spacing.

The comment has a typo ("you" → "your") and inconsistent spacing compared to other lines.

✏️ Proposed fix
-STACK_TELEGRAM_BOT_TOKEN= # enter you telegram bot token
+STACK_TELEGRAM_BOT_TOKEN=# enter your telegram bot token
🧹 Nitpick comments (1)
apps/backend/src/lib/email-rendering.tsx (1)

87-109: Well-structured dual-tier fallback mechanism.

The environment-based execution strategy is clear:

  • Dev/test uses freestyle directly for faster feedback
  • Production uses auto mode with explicit runtime error fallback to vercel-sandbox

The separation between engine-level failures (handled by runWithFallback in js-execution.tsx) and runtime errors (handled here) is correctly implemented.

Consider adding a brief comment above line 97 to clarify the two-tier fallback distinction for future maintainers:

// Runtime error fallback: handles when freestyle executes successfully but the email code throws
// (Engine-level failures are handled by js-execution.tsx's retry mechanism)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e9dc7cf and 05dc86f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (6)
  • apps/backend/.env
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
  • apps/backend/src/lib/js-execution.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
💤 Files with no reviewable changes (1)
  • apps/backend/src/lib/freestyle.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/backend/package.json
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
  • apps/backend/src/lib/js-execution.tsx
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts
Keep hover/click transitions snappy and fast without pre-transition delays (e.g., no fade-in when hovering a button). Apply transitions after the action, like smooth fade-out when hover ends
NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use runAsynchronously or runAsynchronouslyWithAlert instead of general try-catch error handling
When creating hover transitions, avoid hover-enter transitions and use only hover-exit transitions (e.g., transition-colors hover:transition-none)
Don't use Date.now() for measuring elapsed (real) time; instead use performance.now()
Use ES6 maps instead of records wherever possible

Files:

  • apps/backend/src/lib/email-rendering.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: NEVER use Next.js dynamic functions if you can avoid them. Prefer using client components to keep pages static (e.g., use usePathname instead of await params)
Code defensively using ?? throwErr(...) instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid the any type. When using any, leave a comment explaining why and how the type system fails or how errors would still be caught

Files:

  • apps/backend/src/lib/email-rendering.tsx
{.env*,**/*.{ts,tsx,js,jsx}}

📄 CodeRabbit inference engine (AGENTS.md)

All environment variables should be prefixed with STACK_ (or NEXT_PUBLIC_STACK_ if public) to ensure Turborepo picks up changes and improve readability

Files:

  • apps/backend/src/lib/email-rendering.tsx
🧠 Learnings (6)
📚 Learning: 2026-01-08T20:30:36.983Z
Learnt from: nams1570
Repo: stack-auth/stack-auth PR: 1091
File: apps/backend/src/lib/email-rendering.tsx:49-54
Timestamp: 2026-01-08T20:30:36.983Z
Learning: In apps/backend/src/lib/email-rendering.tsx, the nodeModules constant intentionally pins React and react-dom to version 19.1.1 (rather than matching the project's React version) because pinning provides more reliability for email rendering in the sandbox environment.

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : NEVER try-catch-all, NEVER void a promise, and NEVER .catch(console.error). Use loading indicators for async operations. Use `runAsynchronously` or `runAsynchronouslyWithAlert` instead of general try-catch error handling

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : Code defensively using `?? throwErr(...)` instead of non-null assertions, with good error messages explicitly stating violated assumptions

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to **/*.{ts,tsx} : Try to avoid the `any` type. When using `any`, leave a comment explaining why and how the type system fails or how errors would still be caught

Applied to files:

  • apps/backend/src/lib/email-rendering.tsx
📚 Learning: 2026-01-13T18:14:29.974Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-01-13T18:14:29.974Z
Learning: Applies to {.env*,**/*.{ts,tsx,js,jsx}} : All environment variables should be prefixed with `STACK_` (or `NEXT_PUBLIC_STACK_` if public) to ensure Turborepo picks up changes and improve readability

Applied to files:

  • apps/backend/.env
📚 Learning: 2025-08-12T17:55:06.710Z
Learnt from: madster456
Repo: stack-auth/stack-auth PR: 769
File: apps/backend/.env:67-71
Timestamp: 2025-08-12T17:55:06.710Z
Learning: In the stack-auth project, .env files use inline comments after the = sign (e.g., KEY=# description) as a consistent pattern throughout the configuration file. The maintainers prefer this format over separate comment lines above each key.

Applied to files:

  • apps/backend/.env
🧬 Code graph analysis (1)
apps/backend/src/lib/email-rendering.tsx (5)
packages/stack-shared/src/utils/strings.tsx (1)
  • deindent (235-238)
packages/stack-shared/src/utils/esbuild.tsx (1)
  • bundleJavaScript (77-237)
packages/stack-shared/src/utils/env.tsx (1)
  • getNodeEnvironment (76-78)
apps/backend/src/lib/js-execution.tsx (1)
  • executeJavascript (147-173)
packages/stack-shared/src/utils/errors.tsx (1)
  • StackAssertionError (69-85)
🪛 dotenv-linter (4.0.0)
apps/backend/.env

[warning] 90-90: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 91-91: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)


[warning] 92-92: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: setup-tests
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: all-good
  • GitHub Check: restart-dev-and-test
  • GitHub Check: docker
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
🔇 Additional comments (5)
apps/backend/.env (1)

90-92: LGTM!

The Vercel sandbox environment variables are correctly prefixed with STACK_ and properly ordered alphabetically.

apps/backend/src/lib/email-rendering.tsx (4)

50-55: LGTM!

The nodeModules constant with pinned versions provides reliable execution in the sandbox environment. Based on learnings, this intentional pinning ensures consistency for email rendering.


57-72: LGTM!

The entryJs wrapper correctly converts runtime exceptions into structured error results (not swallowing them), and the ExecuteResult discriminated union provides proper type narrowing for status checks.


112-184: LGTM!

The renderEmailWithTemplate function properly validates required parameters, handles preview mode defaults, and cleanly integrates with the new bundleAndExecute pipeline.


205-269: LGTM!

The batched rendering function efficiently processes multiple templates in a single execution. The type assertion on line 268 is necessary due to TypeScript's limitations with dynamically built objects.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Note that with the old version,
 the options and the code were different arguments.
Now they are one argument.
This is why we have to extract the params differently.
There is no change in function with this commit.
There are retries further up in the stack.
An additional retry here does not accomplish much.
Thanks to the freestyle updates, we get the error message directly so there isn't a need to hack it.
Freestyle runtime errors make it return status ok, but data.result undefined.
Since it isn't easy to distinguish these from cases where the executed code just returns nothing, we make changes.
We wrap the script sent to freestyle in a try catch.
This way, we can force it to return an error if there is a runtime/compile time error.
We remove the old code to deal with the result undefined as it is superfluous now.
@nams1570 nams1570 force-pushed the vercel-backup-email-render branch from 05dc86f to c09f0ad Compare January 14, 2026 18:15
@nams1570 nams1570 merged commit ba38f26 into dev Jan 14, 2026
25 of 27 checks passed
@nams1570 nams1570 deleted the vercel-backup-email-render branch January 14, 2026 18:53
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