Skip to content

[Refactor] Update freestyle wrapper to use latest version and simplify code#1086

Closed
nams1570 wants to merge 5 commits intodevfrom
bump-freestyle-error-handling
Closed

[Refactor] Update freestyle wrapper to use latest version and simplify code#1086
nams1570 wants to merge 5 commits intodevfrom
bump-freestyle-error-handling

Conversation

@nams1570
Copy link
Collaborator

@nams1570 nams1570 commented Jan 7, 2026

In this PR, we bump freestyle to the latest version. Then, we refactor our freestyle wrapper code to use the serverless.runs.create api since the executeScript was deprecated. This PR does not change the functionality of our freestyle wrapper.

We also remove the retry mechanism since the retries further up in the stack handle the need for one. We simplify the error handling since the latest version of freestyle provides us the message directly.

Summary by CodeRabbit

  • Chores
    • Bumped a backend dependency to a newer version for improved stability.
  • Refactor
    • Modernized backend script-execution and email rendering to use the updated execution client and configuration shape.
  • Tests
    • Increased e2e test wait time for email delivery verification to reduce flakiness.

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

Copilot AI review requested due to automatic review settings January 7, 2026 01:42
@vercel
Copy link

vercel bot commented Jan 7, 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 8, 2026 0:48am
stack-dashboard Ready Ready Preview, Comment Jan 8, 2026 0:48am
stack-demo Ready Ready Preview, Comment Jan 8, 2026 0:48am
stack-docs Ready Ready Preview, Comment Jan 8, 2026 0:48am

@cmux-agent
Copy link

cmux-agent bot commented Jan 7, 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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

Freestyle client usage moves from the sandboxes API to serverless.runs.create; dependency freestyle-sandboxes is bumped. The Freestyle wrapper signature and email-rendering call sites now pass options nested as { config: { nodeModules } }, return standardized result wrappers, and simplify error handling.

Changes

Cohort / File(s) Summary
Dependency upgrade
apps/backend/package.json
Bumped freestyle-sandboxes from ^0.0.92 to ^0.1.5.
Freestyle wrapper migration
apps/backend/src/lib/freestyle.tsx
Replaced FreestyleSandboxes with FreestyleClient; added exported ExecuteScriptOptions (Partial of ServerlessRunsCreateParams without code); executeScript(script, options?) now calls this.freestyle.serverless.runs.create({ code, config, ...options }) with optional config = {}; tracing counts options.config.nodeModules; simplified error mapping to string messages; removed nested retry/no-result handling.
Email rendering updates
apps/backend/src/lib/email-rendering.tsx
Updated all freestyle.executeScript invocations to pass options as { config: { nodeModules } }; replaced direct exports of renderAll with async wrappers that return standardized { _status, _data } or { _status, _error }; removed some error-capture imports.
E2E timing tweak
apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
Increased wait time after sending test emails from 5000ms to 10000ms in two places to accommodate longer processing.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Email as EmailRendering
    participant Wrapper as FreestyleWrapper
    participant API as FreestyleServerlessAPI

    Email->>Wrapper: executeScript(code, { config: { nodeModules } })
    Wrapper->>API: serverless.runs.create({ code, config, ...options })
    activate API
    API-->>Wrapper: { status, data, error }
    deactivate API
    Note right of Wrapper `#DDFFDD`: Wrapper maps response to standardized {_status,_data} or {_status,_error}
    Wrapper-->>Email: Result.ok(data.result) or Result.error(errorMessage)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • batch sending #875 — Touches related Freestyle client/executeScript signature and batched email rendering flows; likely overlaps with wrapper and entry changes.

Poem

🐇 I nudged the scripts from old to new,
Wrapped results in tidy view,
Nested configs snug and small,
Bumped a package — hop and call,
A rabbit's patch, the system purrs anew.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 clearly and concisely summarizes the main changes: updating Freestyle to the latest version and refactoring the wrapper code. It accurately reflects the primary objectives of the PR.
Description check ✅ Passed The description provides clear context about the changes, including the Freestyle version bump, API migration from executeScript to serverless.runs.create, removal of retry logic, and error handling simplification. It sufficiently explains the PR's intent without being incomplete or off-topic.

✏️ 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 75caac1 and f99008a.

📒 Files selected for processing (1)
  • apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{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/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
**/*.{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/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/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/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts
🧠 Learnings (1)
📚 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 (1)
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). (13)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: docker
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
🔇 Additional comments (1)
apps/e2e/tests/backend/endpoints/api/v1/internal/failed-emails-digest.test.ts (1)

105-105: Verify that the doubled wait time is necessary.

The wait time increased from 5 seconds to 10 seconds (100% increase), which will slow down the test suite. While this may be necessary to accommodate the new Freestyle implementation's timing characteristics, please confirm that 10 seconds is the minimum reliable value based on observed behavior or if a shorter duration would suffice.


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

❤️ Share

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

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Jan 7, 2026

Greptile Summary

This PR successfully migrates the freestyle library from version 0.0.92 to 0.1.5 and refactors the wrapper code to use the new serverless.runs.create API, replacing the deprecated executeScript method. The changes maintain backward compatibility and preserve all existing error handling and retry logic.

  • Updated freestyle-sandboxes dependency to ^0.1.5
  • Refactored Freestyle.executeScript() wrapper to use new serverless.runs.create() API
  • Updated all three email rendering functions to use the new config parameter structure
  • Type definitions properly extracted from the new SDK to maintain type safety
  • No functional behavior changes—this is a pure migration to the latest API

Confidence Score: 5/5

  • This PR is safe to merge. The refactor is straightforward, maintains all existing functionality, and properly uses the new API.
  • Score of 5 reflects: (1) clean dependency upgrade with no breaking changes to the public API of the wrapper, (2) consistent refactoring across all call sites in email-rendering.tsx, (3) proper type extraction from the new SDK ensuring type safety, (4) preservation of all error handling and retry logic, (5) no new security issues introduced by the API migration.
  • No files require special attention. All changes are straightforward and well-coordinated.

Important Files Changed

Filename Overview
apps/backend/package.json Updated freestyle-sandboxes dependency from ^0.0.92 to ^0.1.5 to access the new serverless.runs.create API.
apps/backend/src/lib/freestyle.tsx Refactored to use the new serverless.runs.create API instead of deprecated executeScript. Updated type definitions and correctly mapped options to the new API parameters. The implementation properly handles the new response structure and maintains backward compatibility with existing error handling.
apps/backend/src/lib/email-rendering.tsx Updated all three executeScript calls to wrap nodeModules parameter in a config object, aligning with the new API requirements. Changes are consistent across renderEmailWithTemplate, renderEmailsWithTemplateBatched, and renderEmailsForTenancyBatched functions.

Sequence Diagram

sequenceDiagram
    participant App as Email Rendering
    participant Wrapper as Freestyle Wrapper
    participant SDK as Freestyle SDK

    App->>Wrapper: executeScript(code, {config: {nodeModules}})
    
    Note over Wrapper: New v0.1.5 API
    Wrapper->>Wrapper: Map options to API params
    Wrapper->>Wrapper: Extract code and config
    
    Wrapper->>SDK: serverless.runs.create({code, config, ...options})
    SDK->>SDK: Execute script in sandbox
    SDK-->>Wrapper: {result, logs}
    
    Wrapper->>Wrapper: Handle response
    Wrapper->>Wrapper: Retry on timeout
    Wrapper-->>App: Result.ok({result, logs})
    
    App->>App: Extract result data
    App->>App: Return HTML/text
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 PR migrates the freestyle-sandboxes dependency from version 0.0.92 to 0.1.5 and refactors the wrapper code to use the new serverless.runs.create API instead of the deprecated executeScript method. The wrapper maintains backward compatibility by keeping the same executeScript method signature.

Key Changes:

  • Updated freestyle-sandboxes from v0.0.92 to v0.1.5
  • Refactored internal API call from executeScript() to serverless.runs.create()
  • Updated parameter structure: nodeModules is now nested under a config object

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

File Description
apps/backend/package.json Bumps freestyle-sandboxes dependency from ^0.0.92 to ^0.1.5
apps/backend/src/lib/freestyle.tsx Refactors wrapper to use new API (serverless.runs.create), updates imports and types, changes parameter structure to nest options under config
apps/backend/src/lib/email-rendering.tsx Updates all three call sites to pass nodeModules within a config object to match the new API structure

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

🧹 Nitpick comments (1)
apps/backend/src/lib/freestyle.tsx (1)

46-50: Consider simplifying the redundant config assignment.

The explicit config: options?.config ?? {} on line 48 is overwritten by the spread ...options on line 49 when options contains a config field. While functionally correct, this pattern is redundant and could be simplified.

🔎 Proposed simplification
-            const response = await this.freestyle.serverless.runs.create({
-              code: script,
-              config: options?.config ?? {},
-              ...options,
-            });
+            const response = await this.freestyle.serverless.runs.create({
+              code: script,
+              config: {},
+              ...options,
+            });

This achieves the same result: if options contains config, the spread overwrites the default empty object; if not, the default is used.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a5193e and d083fc9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • 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/freestyle.tsx
  • 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/freestyle.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/freestyle.tsx
  • 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/freestyle.tsx
  • 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/freestyle.tsx
  • apps/backend/src/lib/email-rendering.tsx
🧠 Learnings (1)
📚 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 **/config/schema.ts,**/config/**/*.{ts,tsx} : 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/email-rendering.tsx
⏰ 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). (15)
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: docker
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: all-good
🔇 Additional comments (9)
apps/backend/src/lib/freestyle.tsx (5)

6-11: LGTM: Type extraction and definitions.

The type extraction using Parameters<...>[0] correctly captures the serverless runs API signature, and making it Partial<Omit<..., 'code'>> appropriately allows callers to pass optional configuration while ensuring code is handled separately.


14-14: LGTM: Client type updated consistently.


26-30: LGTM: Constructor correctly instantiates FreestyleClient.


32-32: LGTM: Function signature updated to accept new options type.


38-38: LGTM: Tracing attributes updated for new config structure.

Correctly reads nodeModules count from the nested options.config.nodeModules path.

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

139-139: LGTM: Call site correctly updated for new API shape.

The options are now passed as { config: { nodeModules } }, which correctly matches the new ExecuteScriptOptions type defined in freestyle.tsx.


243-243: LGTM: Call site correctly updated for new API shape.

Consistent with the other call sites, options are passed as { config: { nodeModules } }.


365-365: LGTM: Call site correctly updated for new API shape.

All three call sites in this file have been consistently updated to use the new nested config structure.

apps/backend/package.json (1)

81-81: Version is valid and has no known security advisories.

The library version 0.1.5 is the latest release on npm, published a week ago by legitimate maintainers, with no security deprecations or known vulnerabilities in the registry.

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.
@nams1570 nams1570 force-pushed the bump-freestyle-error-handling branch from d083fc9 to 0b550a1 Compare January 7, 2026 01:51
@cmux-agent
Copy link

cmux-agent bot commented Jan 7, 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: 0

🧹 Nitpick comments (2)
apps/backend/src/lib/freestyle.tsx (2)

38-38: Consider consistent optional chaining for TypeScript narrowing.

The logic is correct, but TypeScript may not narrow the type properly when the condition uses options?.config?.nodeModules and the consequence uses options.config.nodeModules (without ?). For better type safety and consistency, consider using optional chaining in both places.

🔎 Proposed refinement
-        'freestyle.nodeModules.count': options?.config?.nodeModules ? Object.keys(options.config.nodeModules).length.toString() : '0',
+        'freestyle.nodeModules.count': options?.config?.nodeModules ? Object.keys(options?.config?.nodeModules).length.toString() : '0',

53-53: Add explanatory comment for any usage.

Per coding guidelines, when using the any type, leave a comment explaining why it's used and why the type system fails. In this case, the standard Error type doesn't include the code property, which is a Node.js-specific extension for network errors.

As per coding guidelines, which require comments explaining any usage.

🔎 Suggested comment
+            // Cast to any because Node.js errors have a non-standard 'code' property
+            // that's not included in the standard Error type
             if (e instanceof Error && (e as any).code === "ETIMEDOUT") {
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d083fc9 and 0b550a1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/backend/package.json
  • apps/backend/src/lib/email-rendering.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/freestyle.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/freestyle.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/freestyle.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/freestyle.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/freestyle.tsx
🧬 Code graph analysis (1)
apps/backend/src/lib/freestyle.tsx (2)
apps/backend/src/utils/telemetry.tsx (1)
  • traceSpan (13-27)
packages/stack-shared/src/utils/telemetry.tsx (1)
  • traceSpan (13-27)
⏰ 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: build (22.x)
  • 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: lint_and_build (latest)
  • GitHub Check: all-good
  • 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: Vercel Agent Review
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: docker
🔇 Additional comments (3)
apps/backend/src/lib/freestyle.tsx (3)

8-11: Well-structured type extraction from the SDK.

The approach of deriving ExecuteScriptOptions from the SDK's method signature ensures type safety and stays in sync with the library. Making all fields Partial is appropriate since code is the only required parameter and is provided separately.


46-50: Object construction and response handling are correct; no changes needed.

The object construction order is correct. Since config is set AFTER spreading options, it properly overrides any undefined config value with an empty object. All call sites in email-rendering.tsx (lines 139, 243, 365) correctly handle the response structure by accessing executeResult.data.result and checking the error status first, confirming compatibility with the new API response shape.


52-58: Verify error handling compatibility with the new API, and add explanation for any type usage.

The error handling logic has been preserved from the old implementation. However, I cannot definitively confirm timeout behavior without Freestyle SDK documentation or test cases.

Two concerns:

  1. ETIMEDOUT detection: The (e as any).code === "ETIMEDOUT" check uses a standard Node.js error pattern that socket/HTTP libraries follow, so this should remain compatible. However, the code violates the guideline against using the any type without explanation. Add a comment explaining why type narrowing cannot be used here and how errors would be caught at runtime.

  2. Nested error unwrapping: The outer catch block's multi-level unwrapping (e.error.error) aligns with the documented workaround in line 60's comment. Without Freestyle SDK documentation or integration tests covering error scenarios, verification of continued compatibility cannot be conclusively determined.

Consider adding integration tests that exercise error paths with the new API to confirm the error format has not changed.

return Result.ok(Result.orThrow(await Result.retry(async () => {
try {
return Result.ok(await this.freestyle.executeScript(script, options));
// New API: freestyle.serverless.runs.create({ code, config, ... })
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// New API: freestyle.serverless.runs.create({ code, config, ... })

try {
return Result.ok(await this.freestyle.executeScript(script, options));
// New API: freestyle.serverless.runs.create({ code, config, ... })
// Returns { result, logs } on success
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// Returns { result, logs } on success

Comment on lines 59 to 67
} catch (e: unknown) {
// for whatever reason, Freestyle's errors are sometimes returned in JSON.parse(e.error.error).error (lol)
const wrap1 = e && typeof e === "object" && "error" in e ? e.error : e;
const wrap2 = wrap1 && typeof wrap1 === "object" && "error" in wrap1 ? wrap1.error : wrap1;
const wrap3 = wrap2 && typeof wrap2 === "string" ? Result.or(parseJson(wrap2), wrap2) : wrap2;
const wrap4 = wrap3 && typeof wrap3 === "object" && "error" in wrap3 ? wrap3.error : wrap3;
return Result.error(`${wrap4}`);
}
});
Copy link
Contributor

Choose a reason for hiding this comment

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

remove the outer try-catch

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.
@cmux-agent
Copy link

cmux-agent bot commented Jan 7, 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 changed the title [Refactor] Migrate freestyle to latest version [Refactor] Update freestyle wrapper to use latest version and simplify code Jan 7, 2026
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/freestyle.tsx:
- Around line 49-51: The existing misleading comment about nested Freestyle
errors should be removed or updated: delete the line "// Freestyle's errors are
sometimes nested in JSON.parse(e.error.error).error" and either remove the
comment entirely or replace it with a short note that Freestyle >=0.1.5 returns
top-level error and message fields; adjust around the error-handling block that
constructs message from e (the e instanceof Error ? e.message : String(e) line)
and returns Result.error(message) to reflect the simplified error shape.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b550a1 and 285ea6c.

📒 Files selected for processing (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/freestyle.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/freestyle.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/freestyle.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/freestyle.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/freestyle.tsx
🧬 Code graph analysis (1)
apps/backend/src/lib/freestyle.tsx (2)
apps/backend/src/utils/telemetry.tsx (1)
  • traceSpan (13-27)
packages/stack-shared/src/utils/telemetry.tsx (1)
  • traceSpan (13-27)
⏰ 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: docker
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • 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: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: setup-tests
  • GitHub Check: lint_and_build (latest)
🔇 Additional comments (3)
apps/backend/src/lib/freestyle.tsx (3)

8-11: LGTM! Well-structured type definition.

The type derivation is clean and well-documented. Excluding 'code' and making fields optional provides good flexibility for callers.


42-46: Object construction is correct; Copilot's concern doesn't apply.

The concern from the past review about config: undefined overwriting the default is not valid for this code. Since the config: options?.config ?? {} assignment on line 45 occurs after the spread on line 43, it correctly overwrites any undefined value from the spread. JavaScript object literals apply properties in order, so the last assignment wins.


38-38: Correct update for nested config structure.

The access pattern correctly reflects the new API structure where nodeModules is nested under config. The optional chaining properly handles cases where config is undefined.

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.
@cmux-agent
Copy link

cmux-agent bot commented Jan 7, 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: 3

🤖 Fix all issues with AI agents
In @apps/backend/src/lib/email-rendering.tsx:
- Around line 367-371: The return is casting execution.data to an array without
validation; add defensive checks similar to the earlier validation at line 251:
verify execution.data is an Array and every item is an object with required
string properties (html and text, optional subject and notificationCategory)
before returning Result.ok; if the check fails return Result.error with a clear
message (e.g., "Invalid email rendering data") so callers don’t receive an
unsafe cast.
- Around line 147-150: The code returns Result.ok by blindly casting
executeResult.data to the expected shape; add defensive runtime validation of
executeResult.data before casting: check that executeResult.data exists and that
html, text, subject, and notificationCategory are present and of the expected
types (strings), and if any check fails return Result.error with a clear message
indicating which field is missing/invalid rather than casting; update the branch
that currently does Result.ok(executeResult.data as { html: string, text:
string, subject: string, notificationCategory: string }) to perform these checks
and only call Result.ok with the validated object.
- Around line 248-251: The code currently casts executeResult.data to the
expected array shape without validation; add defensive checks (e.g.,
Array.isArray(executeResult.data) and that every item has string properties html
and text and optional string subject/notificationCategory) before calling
Result.ok in place of the direct cast used around executeResult and Result.ok;
if validation fails return Result.error with a helpful message. Implement this
as a small type-guard/validator function (e.g., isRenderedEmailArray or
validateRenderedEmails) and use it where executeResult is handled so you never
assume the shape when calling Result.ok.
🧹 Nitpick comments (3)
apps/backend/src/lib/email-rendering.tsx (3)

118-128: Consider preserving error stack traces.

The error serialization using String(e) may lose valuable debugging information like stack traces. Consider using e.stack || String(e) or a more detailed error serialization to aid in debugging runtime failures in the sandboxed environment.

💡 Proposed enhancement for error serialization
       export default async () => {
         try {
           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.stack || e.message) : String(e) };
         }
       };

219-228: Consider preserving error stack traces.

Same as the earlier entry.js wrapper, using String(e) may lose valuable debugging information. Consider using e.stack || String(e) or more detailed error serialization.

💡 Proposed enhancement for error serialization
       export default async () => {
         try {
           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.stack || e.message) : String(e) };
         }
       };

337-344: Consider preserving error stack traces.

Same recommendation as the previous entry.js wrappers: consider using e.stack || String(e) for better debugging information.

💡 Proposed enhancement for error serialization
       export default async function() {
         try {
           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.stack || e.message) : String(e) };
         }
       };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 285ea6c and 75caac1.

📒 Files selected for processing (2)
  • apps/backend/src/lib/email-rendering.tsx
  • apps/backend/src/lib/freestyle.tsx
🚧 Files skipped from review as they are similar to previous 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
**/*.{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 (1)
📚 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/email-rendering.tsx
⏰ 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: restart-dev-and-test
  • GitHub Check: docker
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle prod)
  • GitHub Check: E2E Tests (Node 22.x, Freestyle mock)
  • GitHub Check: all-good
  • GitHub Check: setup-tests-with-custom-base-port
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test-with-custom-base-port
🔇 Additional comments (4)
apps/backend/src/lib/email-rendering.tsx (4)

247-247: Consistent config structure.

This follows the same config nesting pattern as line 146. The earlier verification comment applies here as well.


366-366: Consistent config structure.

This follows the same config nesting pattern as the earlier changes. Good consistency across all three rendering functions.


146-146: Config structure correctly matches Freestyle API.

The options structure { config: { nodeModules } } aligns with the Freestyle serverless.runs.create API, which expects nodeModules to be nested within a config object.


4-4: Error tracking is preserved through structured error handling, not removed.

The captureError import was removed from email-rendering.tsx, but error tracking is properly handled in the calling code. The renderEmailsForTenancyBatched function returns a Result type with status and error fields. In email-queue-step.tsx, all calls check if (result.status === "error") and invoke captureError("email-rendering-failed", result.error) to track errors. This is a cleaner separation of concerns—the rendering library returns structured errors, and the caller is responsible for error tracking based on the result.

Comment on lines 147 to +150
if (executeResult.status === "error") {
return Result.error(`${executeResult.error}`);
}
if (!executeResult.data.result) {
const noResultError = new StackAssertionError("No result from Freestyle", {
executeResult,
templateOrDraftComponent,
themeComponent,
options,
});
captureError("freestyle-no-result", noResultError);
throw noResultError;
}
return Result.ok(executeResult.data.result as { html: string, text: string, subject: string, notificationCategory: string });
return Result.ok(executeResult.data as { html: string, text: string, subject: string, notificationCategory: string });
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add defensive validation before type casting.

The code checks for error status but doesn't validate that executeResult.data actually contains the expected { html, text, subject, notificationCategory } structure before casting. According to coding guidelines, prefer defensive coding with explicit error messages.

🛡️ Proposed defensive validation
   if (executeResult.status === "error") {
     return Result.error(`${executeResult.error}`);
   }
-  return Result.ok(executeResult.data as { html: string, text: string, subject: string, notificationCategory: string });
+  const data = executeResult.data as any;
+  if (!data || typeof data.html !== 'string' || typeof data.text !== 'string') {
+    throw new StackAssertionError("Freestyle returned invalid email rendering result", { data });
+  }
+  return Result.ok(data as { html: string, text: string, subject?: string, notificationCategory?: string });

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In @apps/backend/src/lib/email-rendering.tsx around lines 147 - 150, The code
returns Result.ok by blindly casting executeResult.data to the expected shape;
add defensive runtime validation of executeResult.data before casting: check
that executeResult.data exists and that html, text, subject, and
notificationCategory are present and of the expected types (strings), and if any
check fails return Result.error with a clear message indicating which field is
missing/invalid rather than casting; update the branch that currently does
Result.ok(executeResult.data as { html: string, text: string, subject: string,
notificationCategory: string }) to perform these checks and only call Result.ok
with the validated object.

Comment on lines 248 to +251
if (executeResult.status === "error") {
return Result.error(executeResult.error);
}
if (!executeResult.data.result) {
const noResultError = new StackAssertionError("No result from Freestyle", {
executeResult,
templateOrDraftComponent,
themeComponent,
inputs,
});
captureError("freestyle-no-result", noResultError);
throw noResultError;
}
return Result.ok(executeResult.data.result as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
return Result.ok(executeResult.data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add defensive validation before type casting.

Similar to line 150, this casts the result without validating the data structure. Add defensive checks to ensure executeResult.data is an array with the expected shape.

🛡️ Proposed defensive validation
   if (executeResult.status === "error") {
     return Result.error(executeResult.error);
   }
-  return Result.ok(executeResult.data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
+  const data = executeResult.data as any;
+  if (!Array.isArray(data)) {
+    throw new StackAssertionError("Freestyle returned non-array result for batched rendering", { data });
+  }
+  return Result.ok(data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
📝 Committable suggestion

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

Suggested change
if (executeResult.status === "error") {
return Result.error(executeResult.error);
}
if (!executeResult.data.result) {
const noResultError = new StackAssertionError("No result from Freestyle", {
executeResult,
templateOrDraftComponent,
themeComponent,
inputs,
});
captureError("freestyle-no-result", noResultError);
throw noResultError;
}
return Result.ok(executeResult.data.result as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
return Result.ok(executeResult.data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
if (executeResult.status === "error") {
return Result.error(executeResult.error);
}
// Cast to any for runtime validation; the type system cannot verify executeResult.data structure at compile time
const data = executeResult.data as any;
if (!Array.isArray(data)) {
throw new StackAssertionError("Freestyle returned non-array result for batched rendering", { data });
}
return Result.ok(data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
🤖 Prompt for AI Agents
In @apps/backend/src/lib/email-rendering.tsx around lines 248 - 251, The code
currently casts executeResult.data to the expected array shape without
validation; add defensive checks (e.g., Array.isArray(executeResult.data) and
that every item has string properties html and text and optional string
subject/notificationCategory) before calling Result.ok in place of the direct
cast used around executeResult and Result.ok; if validation fails return
Result.error with a helpful message. Implement this as a small
type-guard/validator function (e.g., isRenderedEmailArray or
validateRenderedEmails) and use it where executeResult is handled so you never
assume the shape when calling Result.ok.

Comment on lines 367 to +371
if (execution.status === "error") {
return Result.error(execution.error);
}
if (!execution.data.result) {
const noResultError = new StackAssertionError("No result from Freestyle", {
execution,
requests,
});
captureError("freestyle-no-result", noResultError);
throw noResultError;
}

return Result.ok(execution.data.result as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
return Result.ok(execution.data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add defensive validation before type casting.

Same as line 251, add defensive checks to ensure the result is a valid array before casting.

🛡️ Proposed defensive validation
   if (execution.status === "error") {
     return Result.error(execution.error);
   }
 
-  return Result.ok(execution.data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
+  const data = execution.data as any;
+  if (!Array.isArray(data)) {
+    throw new StackAssertionError("Freestyle returned non-array result for tenancy batched rendering", { data });
+  }
+  return Result.ok(data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
📝 Committable suggestion

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

Suggested change
if (execution.status === "error") {
return Result.error(execution.error);
}
if (!execution.data.result) {
const noResultError = new StackAssertionError("No result from Freestyle", {
execution,
requests,
});
captureError("freestyle-no-result", noResultError);
throw noResultError;
}
return Result.ok(execution.data.result as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
return Result.ok(execution.data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
if (execution.status === "error") {
return Result.error(execution.error);
}
const data = execution.data as any;
if (!Array.isArray(data)) {
throw new StackAssertionError("Freestyle returned non-array result for tenancy batched rendering", { data });
}
return Result.ok(data as Array<{ html: string, text: string, subject?: string, notificationCategory?: string }>);
🤖 Prompt for AI Agents
In @apps/backend/src/lib/email-rendering.tsx around lines 367 - 371, The return
is casting execution.data to an array without validation; add defensive checks
similar to the earlier validation at line 251: verify execution.data is an Array
and every item is an object with required string properties (html and text,
optional subject and notificationCategory) before returning Result.ok; if the
check fails return Result.error with a clear message (e.g., "Invalid email
rendering data") so callers don’t receive an unsafe cast.

@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 closed this Jan 8, 2026
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.

2 participants