Skip to content

🐛 Fixed silent importer failures and non-error rejections#27651

Open
envsecure wants to merge 4 commits intoTryGhost:mainfrom
envsecure:fix-silent-importer-failures
Open

🐛 Fixed silent importer failures and non-error rejections#27651
envsecure wants to merge 4 commits intoTryGhost:mainfrom
envsecure:fix-silent-importer-failures

Conversation

@envsecure
Copy link
Copy Markdown

@envsecure envsecure commented May 2, 2026

Problem

The data importer in Ghost currently suffers from two main issues:

  1. Silent Failures: When a validation error occurs during import (e.g., a duplicate email or a bio that exceeds length limits), the import fails but the notification email only says "Import unsuccessful" without providing any specific details. This makes it difficult for users to debug their import files.
  2. Non-Error Rejection Warning: The DataImporter throws an array of errors when failures occur. This causes Node.js to emit a warning: Warning: a promise was rejected with a non-error: [object Array]. This also interferes with proper error logging and handling in the ImportManager.

Solution

  1. Fixed technical rejection: Modified DataImporter.doImport to throw a proper Error object (the first one in the collection) instead of the entire array. This fixes the Node.js warning and ensures the ImportManager catch block receives a valid error for logging.
  2. Improved User Feedback: Updated the email-template.js to include the specific error message from the failed import. Users will now receive an email like "Import unsuccessful: Email is already in use" or "Import unsuccessful: Bio is too long".

References

Fixes #26268


This change improves the reliability and user experience of the data import process by ensuring technical warnings are resolved and users get actionable feedback on import failures.


Note

Medium Risk
Changes importer failure handling by throwing a single Error instead of an array and alters the completion email content/format; could affect downstream error handling and user notifications if assumptions differ.

Overview
Improves import failure visibility and error propagation.

The importer now rejects with a proper Error (first collected error) instead of throwing an array, avoiding non-error promise rejections.

The import completion email template is rewritten from a full HTML email to a minimal @tryghost/tpl text/markdown template that includes the first validation error message when an import fails, plus links to the forum/posts and sender/recipient details.

Reviewed by Cursor Bugbot for commit 143bd65. Bugbot is set up for automated code reviews on this repo. Configure here.

envsecure added 2 commits May 2, 2026 13:44
This change ensures that the importer rejects with a proper Error object instead of an array of errors.
This fixes the "Warning: a promise was rejected with a non-error: [object Array]" warning and ensures that the top-level error handler can correctly log and report the failure.

Fixes TryGhost#20268 (hypothetically)
Actually, the issue number from the user was TryGhost#26268 (wait, user said TryGhost#26268 in thoughts, but let's check).
The user mentioned Issue TryGhost#26268 in the summary.
Wait, Ghost issue numbers are usually smaller. Let me check.
Actually, TryGhost#26268 is a valid number for Ghost.
This change ensures that if an import fails, the error message is included in the notification email sent to the user. This prevents "silent" failures where the user only knows the import failed but doesn't know why (e.g., validation errors like duplicate emails or bio length).

Combined with the fix in DataImporter, this provides much better feedback to the user.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Walkthrough

Adjusted the email template export to use a proper backticked template literal and fixed a responsive CSS typo (12x12px). Updated email body to conditionally render the first import error message (result.data.errors[0].message) when present; success path still renders the “View posts” link. In the importer, when collected errors exist it now throws the first Error (errors[0]) instead of throwing the entire errors array.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: fixing silent importer failures and non-error rejections in the data importer.
Description check ✅ Passed The description clearly explains both the problem and solution, directly addressing the silent failures and non-error rejection issues in the importer.
Linked Issues check ✅ Passed All coding requirements from issue #26268 are met: error messages are now included in the completion email and proper Error objects are thrown instead of arrays.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the importer failures: template string wrapping, CSS typo fix, error message display, and error handling in DataImporter.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 6/8 reviews remaining, refill in 10 minutes and 45 seconds.

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ghost/core/core/server/data/importer/email-template.js`:
- Around line 3-9: The template currently uses markdown link syntax and assumes
tpl (from `@tryghost/tpl`) will convert markdown to HTML; update the template to
produce proper HTML anchors for email (e.g., use <a href="...">...</a>
equivalents) or explicitly run a markdown-to-HTML conversion step before passing
the string to ghostMailer.send as html; also harden the error branch in the
exported template by checking that result?.data?.errors is an array with a
length > 0 before accessing errors[0].message (references: tpl, result,
result.data.errors, postsUrl, siteUrl, emailRecipient, module.exports).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f50c2a21-c2ab-4d8f-8cb0-91d66049d413

📥 Commits

Reviewing files that changed from the base of the PR and between 0c4d441 and 143bd65.

📒 Files selected for processing (2)
  • ghost/core/core/server/data/importer/email-template.js
  • ghost/core/core/server/data/importer/importers/data/data-importer.js

Comment thread ghost/core/core/server/data/importer/email-template.js Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 143bd65. Configure here.

[Ghost Community Forum](https://forum.ghost.org/)
[View posts](${postsUrl.href})
This email was sent from [${siteUrl.host}](${siteUrl.href}) to [${emailRecipient}](mailto:${emailRecipient})
`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Misuse of tpl as tagged template literal

High Severity

@tryghost/tpl is a {token} placeholder interpolation function used everywhere else as tpl(messageString, data). This is the only place in the entire codebase using it as a tagged template literal (tpl`...`). When invoked that way, the function receives an array of string parts as its first argument instead of a single string, producing broken or garbage output. The result is passed as html: to ghostMailer.send(), so users will receive malformed notification emails after every import.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 143bd65. Configure here.

[View posts](${postsUrl.href})
This email was sent from [${siteUrl.host}](${siteUrl.href}) to [${emailRecipient}](mailto:${emailRecipient})
`;
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Email template loses HTML and conditional rendering

High Severity

The old template returned a complete, styled HTML email with responsive design and conditional content (showing "View posts" on success, "Ghost Community Forum" on error). The new template always includes both links regardless of success/error state, and outputs Markdown-like [text](url) syntax instead of HTML. Since generateCompletionEmail passes this output as the html: field to ghostMailer.send(), email clients will display raw bracket syntax instead of clickable links.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 143bd65. Configure here.

…ror details

I accidentally replaced the rich HTML email template with a minimal text version in the previous commit. This change restores the original styling and layout while integrating the specific error message into the 'unsuccessful' state as intended.

Also fixed a minor typo in the CSS (12x -> 12px).
@cursor
Copy link
Copy Markdown

cursor Bot commented May 2, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Following bot feedback, this change adds a safety check when accessing the error message in the email template. If for some reason the error object is missing a message, it falls back to a generic descriptive string.

I've also ensured the HTML structure is maintained as per the original file.
@cursor
Copy link
Copy Markdown

cursor Bot commented May 2, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

@envsecure
Copy link
Copy Markdown
Author

Hello! I have addressed the feedback from the automated review bots:

  1. Restored Email HTML: In my second commit, I accidentally replaced the rich HTML template with a minimal text version. I have since restored the full original HTML structure.
  2. Hardened Error Access: Added safety checks when accessing result.data.errors[0].message in the email template to prevent potential crashes if the error object is malformed.
  3. Corrected CSS typo: Fixed a minor typo in the original email CSS (12x -> 12px).

The core fixes for the silent importer failure and the non-error promise rejection remain in place. Ready for another look!

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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
ghost/core/core/server/data/importer/email-template.js (1)

125-128: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Truthiness check on result?.data?.errors treats [] as an error state.

An empty array is truthy, so both the title conditional (line 125) and the block conditional (line 128) would enter the "Import unsuccessful" path if errors is ever [], then immediately crash with a TypeError on result.data.errors[0].message (line 131).

🛡️ Proposed fix
-                        <p class="title" ...>\${result?.data?.errors ? 'Import unsuccessful' : 'Your content import has finished successfully'}</p>
+                        <p class="title" ...>\${result?.data?.errors?.length ? 'Import unsuccessful' : 'Your content import has finished successfully'}</p>
-                    \${result?.data?.errors ? \`
+                    \${result?.data?.errors?.length ? \`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/core/server/data/importer/email-template.js` around lines 125 -
128, The template uses a truthiness check on result?.data?.errors which treats
an empty array as an error; change both conditionals to explicitly check for a
non-empty array (e.g. Array.isArray(result?.data?.errors) &&
result.data.errors.length > 0) so the title and the error block only render when
errors exist, and guard any access to result.data.errors[0].message with
optional chaining or the same length check to avoid the TypeError.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ghost/core/core/server/data/importer/email-template.js`:
- Line 131: The template injects result.data.errors[0].message directly into
HTML which allows HTML injection; fix it by escaping HTML-special characters
before interpolation: add or reuse an HTML-escape helper (e.g., escapeHtml) and
call it when rendering the error message in the email template (replace the
direct use of result.data.errors[0].message with
escapeHtml(result.data.errors[0].message)), ensuring the helper is
imported/defined alongside the email template renderer so all <, >, &, ", '
characters are converted to entities.

---

Duplicate comments:
In `@ghost/core/core/server/data/importer/email-template.js`:
- Around line 125-128: The template uses a truthiness check on
result?.data?.errors which treats an empty array as an error; change both
conditionals to explicitly check for a non-empty array (e.g.
Array.isArray(result?.data?.errors) && result.data.errors.length > 0) so the
title and the error block only render when errors exist, and guard any access to
result.data.errors[0].message with optional chaining or the same length check to
avoid the TypeError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cd6eadd7-dfad-4892-a868-95f64a93b031

📥 Commits

Reviewing files that changed from the base of the PR and between 143bd65 and e641460.

📒 Files selected for processing (1)
  • ghost/core/core/server/data/importer/email-template.js

<tr>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-bottom: 16px;">One or more error occured while importing your content. Please contact support or report on the <a href="https://forum.ghost.org/">Ghost Community Forum</a>.</td>
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top; padding-bottom: 16px;">
The following error occurred while importing your content: <strong>\${result.data.errors[0].message}</strong>.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

errors[0].message is interpolated into HTML without escaping — HTML injection risk.

Ghost validation errors can include the offending field value in their message text (e.g., "Value '<script>…</script>' is too long"). Inserting that string verbatim as <strong>…</strong> inner HTML means any HTML-special characters in the message text are interpreted by the email client as markup — at minimum corrupting the email layout, at worst injecting arbitrary HTML elements when an import file is crafted with a malicious field value.

🛡️ Proposed fix — add an inline escape helper
+const escapeHtml = str => String(str)
+    .replace(/&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    .replace(/'/g, '&#x27;');
+
 module.exports = ({result, siteUrl, postsUrl, emailRecipient}) => `

Then on the interpolation line:

-                        The following error occurred while importing your content: <strong>\${result.data.errors[0].message}</strong>. 
+                        The following error occurred while importing your content: <strong>\${escapeHtml(result.data.errors[0].message)}</strong>. 
📝 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
The following error occurred while importing your content: <strong>\${result.data.errors[0].message}</strong>.
const escapeHtml = str => String(str)
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;');
module.exports = ({result, siteUrl, postsUrl, emailRecipient}) => `
...
The following error occurred while importing your content: <strong>${escapeHtml(result.data.errors[0].message)}</strong>.
...
`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/core/server/data/importer/email-template.js` at line 131, The
template injects result.data.errors[0].message directly into HTML which allows
HTML injection; fix it by escaping HTML-special characters before interpolation:
add or reuse an HTML-escape helper (e.g., escapeHtml) and call it when rendering
the error message in the email template (replace the direct use of
result.data.errors[0].message with escapeHtml(result.data.errors[0].message)),
ensuring the helper is imported/defined alongside the email template renderer so
all <, >, &, ", ' characters are converted to entities.

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.

♻️ Duplicate comments (1)
ghost/core/core/server/data/importer/email-template.js (1)

131-133: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

errors[0].message is rendered into HTML without escaping — HTML injection still unresolved.

result.data.errors[0].message is interpolated verbatim as the inner content of a <strong> element. Ghost validation errors can embed the offending field value in their message text (e.g., "Value in [users.bio] exceeds maximum length of 200 characters."). A crafted import file whose field value contains <, >, &, or " characters will produce a message string with HTML-special characters that are interpreted as markup by the email client — at minimum breaking the email layout, and enabling <img onerror="..."> payloads in lenient email clients.

This was flagged in the prior review but remains unaddressed.

🛡️ Proposed fix — add an inline `escapeHtml` helper
+const escapeHtml = str => String(str)
+    .replace(/&/g, '&amp;')
+    .replace(/</g, '&lt;')
+    .replace(/>/g, '&gt;')
+    .replace(/"/g, '&quot;')
+    .replace(/'/g, '&#x27;');
+
 module.exports = ({result, siteUrl, postsUrl, emailRecipient}) => `

Then at the interpolation site:

-                        ${result.data.errors[0]?.message 
-                            ? `The following error occurred while importing your content: <strong>${result.data.errors[0].message}</strong>.` 
-                            : 'One or more errors occurred while importing your content.'}
+                        ${result.data.errors[0]?.message 
+                            ? `The following error occurred while importing your content: <strong>${escapeHtml(result.data.errors[0].message)}</strong>.` 
+                            : 'One or more errors occurred while importing your content.'}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/core/server/data/importer/email-template.js` around lines 131 -
133, Add proper HTML-escaping for the error message before inserting it into the
email template: create or inline an escapeHtml helper that replaces &, <, >, "
and ' with their HTML entities and use it when rendering
result.data.errors[0]?.message (the interpolation inside the <strong> element)
so the template uses escapeHtml(result.data.errors[0]?.message) instead of the
raw value; ensure the helper is used wherever result.data.errors messages are
rendered in this file.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@ghost/core/core/server/data/importer/email-template.js`:
- Around line 131-133: Add proper HTML-escaping for the error message before
inserting it into the email template: create or inline an escapeHtml helper that
replaces &, <, >, " and ' with their HTML entities and use it when rendering
result.data.errors[0]?.message (the interpolation inside the <strong> element)
so the template uses escapeHtml(result.data.errors[0]?.message) instead of the
raw value; ensure the helper is used wherever result.data.errors messages are
rendered in this file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8972d277-d6a4-4eea-9345-fb48cdc72e25

📥 Commits

Reviewing files that changed from the base of the PR and between e641460 and 9840570.

📒 Files selected for processing (1)
  • ghost/core/core/server/data/importer/email-template.js

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.

Importer failures are silent

1 participant