Skip to content

fix(api-service,dashboard): fix attachment crash, context race condition, and CodeMirror decoration error#10812

Open
cursor[bot] wants to merge 2 commits intonextfrom
cursor/sentry-error-investigation-c936
Open

fix(api-service,dashboard): fix attachment crash, context race condition, and CodeMirror decoration error#10812
cursor[bot] wants to merge 2 commits intonextfrom
cursor/sentry-error-investigation-c936

Conversation

@cursor
Copy link
Copy Markdown
Contributor

@cursor cursor Bot commented Apr 21, 2026

Summary

Fixes three production Sentry errors affecting the API and Dashboard.

1. API-PC: Buffer.from(undefined) crash in attachment parsing (220 events, 1 user)

Root cause: POST /v1/events/trigger calls modifyAttachments which maps over command.payload.attachments and calls Buffer.from(attachment.file, 'base64'). When a caller sends an attachment object without a file property, attachment.file is undefined and Buffer.from throws a TypeError.

Fix: Filter out attachments with null/undefined file before the .map() call. Attachments without file data are silently skipped rather than crashing the entire trigger request.

2. API-PQ: MongoDB E11000 duplicate key error on context creation (102,004 events, 23 users)

Root cause: POST /v2/contexts does a findOne check then create (check-then-act). Under concurrent requests for the same context, both pass the findOne check and race to create, causing one to hit the unique index constraint. The raw MongoServerError bubbles up as a 500 instead of the intended 409.

Fix: Wrap the create call in a try/catch that detects E11000 (duplicate key) errors and converts them to ConflictException (409), matching the existing behavior for the non-racy path. This mirrors the pattern already used in ContextRepository.findOrCreateContext.

3. DASHBOARD-2A7: CodeMirror decoration crash on multiline variables (50 events, 2 users)

Root cause: The variable regex {{([^{}]+)}} allows newlines in matches. When a variable expression like {{\nsubscriber.name\n}} spans multiple lines, Decoration.replace().range(start, end) crosses a line boundary. CodeMirror throws: "Decorations that replace line breaks may not be specified via plugins".

Fix: Skip creating replace decorations when the matched text contains a newline, in both the variable plugin and translation plugin createDecorations methods. The translation plugin's parseTranslation already had a newline guard, but the check at the decoration level provides defense-in-depth for both plugins.

Open in Web View Automation 

What changed

This PR fixes three production bugs (220–102k events) affecting the API and Dashboard. The API attachment parsing was crashing when encountering attachments with undefined file data. Context creation had a race condition where concurrent requests could both bypass duplicate-key checks and cause MongoDB E11000 errors. CodeMirror decorations were throwing errors on multiline variable matches. Fixes: filter null attachments before conversion, wrap context creation in try/catch to translate duplicate-key errors to 409 responses, skip creating decorations for matches containing newlines.

Affected areas

  • api: Modified context creation to catch MongoDB duplicate key errors and return HTTP 409 instead of 500; modified event request parsing to filter out attachments with null/undefined file data before conversion.
  • dashboard: Updated both translation and variable plugins to skip creating CodeMirror decorations when the matched text contains newline characters.

Testing

No information provided about new tests in the PR; verification would consist of confirming these Sentry error frequencies decline in production monitoring.

… CodeMirror decoration error

- API-PC: Filter out attachments with undefined file before Buffer.from() call
- API-PQ: Catch MongoDB E11000 duplicate key error and return 409 ConflictException
- DASHBOARD-2A7: Skip variable/translation decorations that span line breaks

Fixes API-PC
Fixes API-PQ
Fixes DASHBOARD-2A7

Co-authored-by: Dima Grossman <dima@grossman.io>
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 21, 2026

Deploy preview added

Name Link
🔨 Latest commit 8f30df3
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/69eb11d6f9988f0008700098
😎 Deploy Preview https://deploy-preview-10812.dashboard-v2.novu-staging.co
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

Hey there and thank you for opening this pull request! 👋

We require pull request titles to follow specific formatting rules and it looks like your proposed title needs to be adjusted.

Your PR title is: fix(api-service,dashboard): fix attachment crash, context race condition, and CodeMirror decoration error

Requirements:

  1. Follow the Conventional Commits specification
  2. As a team member, include Linear ticket ID at the end: fixes TICKET-ID or include it in your branch name

Expected format: feat(scope): Add fancy new feature fixes NOV-123

Details:

PR title must end with 'fixes TICKET-ID' (e.g., 'fixes NOV-123') or include ticket ID in branch name

@scopsy scopsy marked this pull request as ready for review April 24, 2026 06:47
@scopsy
Copy link
Copy Markdown
Contributor

scopsy commented Apr 24, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

The PR adds defensive programming measures across API and dashboard services: wrapping context creation in error handling to translate duplicate-key failures to conflict exceptions, filtering null attachments before processing, and preventing UI decorators from rendering multiline regex matches in both translation and variable plugins.

Changes

Cohort / File(s) Summary
API Usecases
apps/api/src/app/contexts/usecases/create-context/create-context.usecase.ts, apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts
Context creation now catches DUPLICATE_KEY errors and maps them to ConflictException; event request parsing filters null attachments before conversion and storage path generation.
Dashboard Plugin Decorators
apps/dashboard/src/components/primitives/translation-plugin/plugin-view.ts, apps/dashboard/src/components/primitives/variable-plugin/plugin-view.ts
Both translation and variable plugins now skip regex matches containing newline characters before generating UI decorations, preventing multiline expression rendering.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • novuhq/novu#9393: Also modifies context creation flow—this PR adds duplicate-key error handling to create-context, while the related PR replaces upsert with find-or-create semantics for context initialization.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title follows Conventional Commits format with valid type (fix) and scopes (api-service, dashboard), uses imperative mood, and lowercase description, but uses 'api-service' which is not in the allowed scopes list (valid scopes are: api, dashboard, worker, ws, webhook, shared, dal, application-generic, js, react, react-native, nextjs, providers, framework, root). Replace 'api-service' with 'api' in the title. The corrected title should be: fix(api,dashboard): fix attachment crash, context race condition, and CodeMirror decoration error
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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

❤️ Share

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

Copy link
Copy Markdown
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.

🧹 Nitpick comments (2)
apps/dashboard/src/components/primitives/variable-plugin/plugin-view.ts (1)

63-65: Correct guard against multiline replace-decoration crash.

Skipping matches that span newlines prevents CodeMirror's "Decorations that replace line breaks may not be specified via plugins" error. One minor optimization: moving this check immediately after computing match[0] (e.g., right after Line 52 or before parseVariable) avoids the unnecessary parseVariable call for multiline matches. Not a blocker.

♻️ Optional micro-optimization
     while ((match = regex.exec(content)) !== null) {
+      if (match[0].includes('\n')) {
+        continue;
+      }
+
       const parsedVariable = parseVariable(match[0]);

       if (!parsedVariable) {
         continue;
       }

       const { fullLiquidExpression, name, filtersArray } = parsedVariable;
       const start = match.index;
       const end = start + match[0].length;

-      if (match[0].includes('\n')) {
-        continue;
-      }
-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/primitives/variable-plugin/plugin-view.ts`
around lines 63 - 65, Move the multiline-guard so we check
match[0].includes('\n') immediately after computing match[0] and before calling
parseVariable, so multiline matches are skipped without invoking parseVariable;
update the loop that iterates matches (the code using match[0] and
parseVariable) to continue early for newline-containing matches to avoid
CodeMirror's "replace line breaks" decoration crash.
apps/api/src/app/contexts/usecases/create-context/create-context.usecase.ts (1)

19-36: Optional: de-duplicate the conflict message.

The ConflictException message is identical on Line 19 and Line 36. Extracting it into a small helper or local constant avoids the two sites drifting apart in future edits.

♻️ Proposed refactor
   async execute(command: CreateContextCommand): Promise<ContextEntity> {
+    const conflictMessage = `Context with type '${command.type}' and id '${command.id}' already exists`;
+
     const existingContext = await this.contextRepository.findOne({
       _environmentId: command.environmentId,
       _organizationId: command.organizationId,
       type: command.type,
       id: command.id,
     });

     if (existingContext) {
-      throw new ConflictException(`Context with type '${command.type}' and id '${command.id}' already exists`);
+      throw new ConflictException(conflictMessage);
     }

     try {
       return await this.contextRepository.create({
         _environmentId: command.environmentId,
         _organizationId: command.organizationId,
         type: command.type,
         id: command.id,
         key: createContextKey(command.type, command.id),
         data: command.data || {},
       });
     } catch (error) {
       const isDuplicateKeyError =
         error && typeof error === 'object' && 'code' in error && error.code === ErrorCodesEnum.DUPLICATE_KEY;

       if (isDuplicateKeyError) {
-        throw new ConflictException(`Context with type '${command.type}' and id '${command.id}' already exists`);
+        throw new ConflictException(conflictMessage);
       }

       throw error;
     }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/app/contexts/usecases/create-context/create-context.usecase.ts`
around lines 19 - 36, The ConflictException message is duplicated; inside the
execute method (the CreateContextUsecase surrounding the early throw and the
catch block that checks ErrorCodesEnum.DUPLICATE_KEY) extract the string into a
single constant or small helper (e.g., const conflictMessage = `Context with
type '${command.type}' and id '${command.id}' already exists` or a
getConflictMessage(command) function) declared before the first throw, then
replace both uses of the inline message (the initial throw and the throw in the
catch) with that constant/helper so the text is maintained in one place.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/api/src/app/contexts/usecases/create-context/create-context.usecase.ts`:
- Around line 19-36: The ConflictException message is duplicated; inside the
execute method (the CreateContextUsecase surrounding the early throw and the
catch block that checks ErrorCodesEnum.DUPLICATE_KEY) extract the string into a
single constant or small helper (e.g., const conflictMessage = `Context with
type '${command.type}' and id '${command.id}' already exists` or a
getConflictMessage(command) function) declared before the first throw, then
replace both uses of the inline message (the initial throw and the throw in the
catch) with that constant/helper so the text is maintained in one place.

In `@apps/dashboard/src/components/primitives/variable-plugin/plugin-view.ts`:
- Around line 63-65: Move the multiline-guard so we check
match[0].includes('\n') immediately after computing match[0] and before calling
parseVariable, so multiline matches are skipped without invoking parseVariable;
update the loop that iterates matches (the code using match[0] and
parseVariable) to continue early for newline-containing matches to avoid
CodeMirror's "replace line breaks" decoration crash.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2abc0026-b2db-44af-9b8f-26acabc81702

📥 Commits

Reviewing files that changed from the base of the PR and between f93b101 and 8f30df3.

📒 Files selected for processing (4)
  • apps/api/src/app/contexts/usecases/create-context/create-context.usecase.ts
  • apps/api/src/app/events/usecases/parse-event-request/parse-event-request.usecase.ts
  • apps/dashboard/src/components/primitives/translation-plugin/plugin-view.ts
  • apps/dashboard/src/components/primitives/variable-plugin/plugin-view.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants