-
Notifications
You must be signed in to change notification settings - Fork 498
Update email rendering code to use latest version of freestyle and to have a fallback with vercel sandbox #1091
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit 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. 📝 WalkthroughWalkthroughReplaces 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (6)
💤 Files with no reviewable changes (1)
🚧 Files skipped from review as they are similar to previous changes (1)
🧰 Additional context used📓 Path-based instructions (4)**/*.test.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (AGENTS.md)
Files:
{.env*,**/*.{ts,tsx,js,jsx}}📄 CodeRabbit inference engine (AGENTS.md)
Files:
🧠 Learnings (11)📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2026-01-08T20:30:36.983ZApplied to files:
📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2026-01-13T18:14:29.974ZApplied to files:
📚 Learning: 2025-10-11T04:13:19.308ZApplied to files:
📚 Learning: 2025-08-12T17:55:06.710ZApplied to files:
🪛 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)
🔇 Additional comments (13)
✏️ Tip: You can disable this entire section by setting 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. Comment |
There was a problem hiding this 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.tsxabstraction 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.tsxwrapper in favor of the new abstraction - Simplified
email-rendering.tsxby 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 injs-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 inemail-rendering.tsx) bypasses the retry mechanism inrunWithFallback, 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.tsxrequires 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
There was a problem hiding this 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.tsxmodule 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.tsxto use the new execution abstraction - Package updates:
freestyle-sandboxesupgraded 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.
apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
Show resolved
Hide resolved
There was a problem hiding this 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
StackAssertionErrorinstead of the genericErrorclass. 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
stdoutin 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
apps/backend/package.jsonapps/backend/src/lib/email-rendering.tsxapps/backend/src/lib/freestyle.tsxapps/backend/src/lib/js-execution.tsxapps/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.tsxapps/backend/src/lib/email-rendering.tsxapps/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.tsxapps/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
usePathnameinstead ofawait params)
Files:
apps/backend/src/lib/js-execution.tsxapps/backend/src/lib/email-rendering.tsxapps/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, userunAsynchronouslyorrunAsynchronouslyWithAlertinstead
Use ES6 maps instead of records wherever possible
Files:
apps/backend/src/lib/js-execution.tsxapps/backend/src/lib/email-rendering.tsxapps/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 theanytype; 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.tsxapps/backend/src/lib/email-rendering.tsxapps/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
runAsynchronouslyAndWaitUntilallows 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
Resulttypes 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
bundleAndExecuteproperly 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.2andfreestyle-sandboxes@^0.1.5exist in the npm registry with no known security vulnerabilities.@vercel/sandboxis at 1.1.2 (latest is 1.1.5, but within the caret range), andfreestyle-sandboxesis at the latest version 0.1.5.
115e16e to
84b3526
Compare
Older cmux preview screenshots (latest comment is below)Preview ScreenshotsOpen Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap Screenshot capture was skipped.
Generated by cmux preview system |
There was a problem hiding this 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 installcommand 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_KEYis 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
engineMapandenginesto 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. IfexecuteJavascriptreturns 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.jsexists). 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
apps/backend/package.jsonapps/backend/src/lib/email-rendering.tsxapps/backend/src/lib/freestyle.tsxapps/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.tsxapps/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.tsxapps/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
usePathnameinstead ofawait params)
Files:
apps/backend/src/lib/email-rendering.tsxapps/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, userunAsynchronouslyorrunAsynchronouslyWithAlertinstead
Use ES6 maps instead of records wherever possible
Files:
apps/backend/src/lib/email-rendering.tsxapps/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 theanytype; 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.tsxapps/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.createwith aconfig.nodeModulesparameter. 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.
84b3526 to
f7104f0
Compare
Older cmux preview screenshots (latest comment is below)Preview ScreenshotsOpen Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap Screenshot capture was skipped.
Generated by cmux preview system |
There was a problem hiding this 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.
ExecuteResultwithstatus: stringis 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
ascasts 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
apps/backend/package.jsonapps/backend/src/lib/email-rendering.tsxapps/backend/src/lib/freestyle.tsxapps/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
usePathnameinstead ofawait 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, userunAsynchronouslyorrunAsynchronouslyWithAlertinstead
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 theanytype; 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
renderEmailWithTemplatecleanly adopts the new bundle-and-execute pattern. The structuredResult<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.jstwo lines prior — TypeScript simply can't narrowRecord<string, string>after dynamic key assignment.apps/backend/package.json (1)
82-82: No action needed. Version0.1.5is confirmed as the latest available version with no identified security concerns. The upgrade appears intentional and validated by the PR context.
Older cmux preview screenshots (latest comment is below)Preview ScreenshotsOpen Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap Screenshot capture was skipped.
Generated by cmux preview system |
654c253 to
afa1e43
Compare
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this 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 forstatus.The
ExecuteResult.statusis typed asstring, 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (4)
apps/backend/package.jsonapps/backend/src/lib/email-rendering.tsxapps/backend/src/lib/freestyle.tsxapps/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
usePathnameinstead ofawait 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, userunAsynchronouslyorrunAsynchronouslyWithAlertinstead
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 theanytype; 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.jsonapps/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
nodeModulesversion pinning is intentional for sandbox reliability. TheentryJstry-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
bundleAndExecutewhile preserving the existing validation logic for user/project requirements. The return type properly reflects the newEmailRenderResulttype.
194-258: LGTM!The batched rendering function is cleanly refactored to use
bundleAndExecute. The type assertion on line 257 is justified since/entry.jsis explicitly assigned on line 255.
260-365: No changes to review.These utility functions (
findComponentValueUtilandstackframeEmailsPackage) 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.2andfreestyle-sandboxes@^0.1.5both exist on npm and allow appropriate minor/patch updates via caret constraints.
afa1e43 to
ff31a3c
Compare
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
Older cmux preview screenshots (latest comment is below)Preview ScreenshotsOpen Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap Screenshot capture was skipped.
Generated by cmux preview system |
There was a problem hiding this 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
apps/backend/.envapps/backend/package.jsonapps/backend/src/lib/email-rendering.tsxapps/backend/src/lib/freestyle.tsxapps/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
usePathnameinstead ofawait 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, userunAsynchronouslyorrunAsynchronouslyWithAlertinstead
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 theanytype; 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
executeJavascriptand the addition ofbundleJavaScriptcorrectly support the new bundle-and-execute architecture described in the PR objectives.
68-69: Type definitions look reasonable.The types use
unknownappropriately instead ofany, and the structure matches the expected execution results.
101-173: LGTM: Function correctly refactored to use bundle-and-execute approach.The
renderEmailWithTemplatefunction properly constructs the files object and delegates tobundleAndExecute. The generated render logic handles arktype validation errors appropriately and structures the output correctly.
194-258: LGTM: Batch rendering correctly refactored.The
renderEmailsForTenancyBatchedfunction properly constructs files for batch rendering and delegates tobundleAndExecute. The type assertion at line 257 is necessary for TypeScript to recognize the/entry.jskey 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.
cfaf695 to
ccb604b
Compare
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ 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
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
674a0ee to
e9dc7cf
Compare
Older cmux preview screenshots (latest comment is below)Preview Screenshots⏳ Preview screenshots are being captured... Workspace and dev browser links will appear here once the preview environment is ready. Generated by cmux preview system |
There was a problem hiding this 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 explicitvercel-sandboxretry fail, returning onlyexecuteResult.errorloses 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
captureErrorpreserves 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
apps/backend/.envapps/backend/package.jsonapps/backend/src/lib/email-rendering.tsxapps/backend/src/lib/freestyle.tsxapps/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.tsxapps/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.tsxapps/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
usePathnameinstead ofawait params)
Files:
apps/backend/src/lib/email-rendering.tsxapps/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, userunAsynchronouslyorrunAsynchronouslyWithAlertinstead
Use ES6 maps instead of records wherever possible
Files:
apps/backend/src/lib/email-rendering.tsxapps/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 theanytype; 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.tsxapps/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 (
executeJavascriptandgetNodeEnvironment) 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
finallyblock (line 134) ensuring sandbox cleanup, and proper validation of required environment variables (lines 63-67). The result reading viacatcommand 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
runAsynchronouslyAndWaitUntilfor 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.stringifyfor 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.
e9dc7cf to
b014062
Compare
Older cmux preview screenshots (latest comment is below)Preview ScreenshotsOpen Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap Screenshot capture was skipped.
Generated by cmux preview system |
Preview ScreenshotsOpen Workspace (1 hr expiry) · Open Dev Browser (1 hr expiry) · Open Diff Heatmap Screenshot capture was skipped.
Generated by cmux preview system |
9f76df9 to
05dc86f
Compare
There was a problem hiding this 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
runWithFallbackin 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (6)
apps/backend/.envapps/backend/package.jsonapps/backend/src/lib/email-rendering.tsxapps/backend/src/lib/freestyle.tsxapps/backend/src/lib/js-execution.tsxapps/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 usetoast, 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. UserunAsynchronouslyorrunAsynchronouslyWithAlertinstead 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 useDate.now()for measuring elapsed (real) time; instead useperformance.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., useusePathnameinstead ofawait params)
Code defensively using?? throwErr(...)instead of non-null assertions, with good error messages explicitly stating violated assumptions
Try to avoid theanytype. When usingany, 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_(orNEXT_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
nodeModulesconstant 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
entryJswrapper correctly converts runtime exceptions into structured error results (not swallowing them), and theExecuteResultdiscriminated union provides proper type narrowing for status checks.
112-184: LGTM!The
renderEmailWithTemplatefunction properly validates required parameters, handles preview mode defaults, and cleanly integrates with the newbundleAndExecutepipeline.
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.
…t vercel prototype
05dc86f to
c09f0ad
Compare
Previously, we were using an old version of
freestyle.shand 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 thefreestyle.tsxwrapper code aroundfreestyleto ajs-execution-engine, which also offers opportunities to extend the email rendering engine abstraction in the future. Iffreestyleencounters runtime errors while running the code or fails, we retry once and then default toVercel sandbox. Note that we also introduce a sanity test: 5% of the time, we will run the code through bothfreestyleand theVercel Sandboxand log it if the results don't match.As a chore, we also up the time limit on a
failed-email-digest.test.tstest to make it less flakey.Summary by CodeRabbit
Refactor
Chores
Config
Tests
✏️ Tip: You can customize this high-level summary in your review settings.