Skip to content

🐛 Fixed themes with custom error templates returning HTTP 500#28779

Open
sagzy wants to merge 1 commit into
mainfrom
hopeful-brown-33b0f6
Open

🐛 Fixed themes with custom error templates returning HTTP 500#28779
sagzy wants to merge 1 commit into
mainfrom
hopeful-brown-33b0f6

Conversation

@sagzy

@sagzy sagzy commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Frontend requests that errored before the theme middleware mounted the active theme — errors thrown early in the request pipeline, or the first requests after a boot/restart — reached the theme error renderer with the theme not yet mounted into Express.

In that state:

  • setTemplate selected a theme-specific error template name (e.g. error-404) by consulting the theme's in-memory manifest (hasTemplate), independent of Express's configured views directory.

  • But res.render('error-404', …) resolved that name against Ghost's default views directory (core/server/views/), which only contains a generic error.hbs — producing:

    Error: Failed to lookup view "error-404" in views directory "/.../core/server/views/"
    

    …and an HTTP 500 instead of an error page.

The bare-minimum fallback guard (if (_.isEmpty(req.app.engines))) also permanently repointed the app's global views directory to the default and registered a minimal engine. It only ran once (afterwards app.engines is non-empty, so it's skipped), so once armed it could keep masking real status codes (e.g. a genuine 404) as 500s until a normal request finally mounted the theme — which is why this clustered right after restarts/deploys. Themes shipping only a generic error.hbs didn't hit the lookup failure, because the name error also exists in the default views dir — so this surfaced specifically for themes with templates like error-404.hbs.

Fix

When the active theme isn't mounted, the error renderer now renders Ghost's self-contained built-in error template rather than a theme template. Rendering a theme error page requires more than the right views dir — it needs the full theme engine and the per-request/global template data (@site, @custom, member, …) that update-global-template-options / update-local-template-options populate during the normal pipeline. None of that is set up in the pre-mount window, so a themed error page there would render with broken/missing data (and could throw in {{ghost_head}}/{{#get}}). Theme error templates are now only rendered when the theme is genuinely mounted — the existing, working path where that data is already in place.

The built-in template is also referenced by absolute path instead of via req.app.set('views', defaultViews), so the fallback can no longer repoint the app-wide views directory and break theme template lookups on later requests.

Note: this means that in the brief pre-mount window (first requests after a boot, before the theme is mounted), errors render Ghost's generic built-in error page rather than the theme's styled one. Once any normal request mounts the theme, all subsequent errors render the themed page as before. This is the conservative resolution suggested in review — see discussion.

Testing

Added regression tests in error-handler.test.js driving the real themeErrorRenderer over the post-boot/pre-mount sequence:

  • A theme that ships error-404 renders the built-in template (not a 500) when unmounted.
  • Repeated early errors no longer 500 and never repoint the global views dir (the exact reproduction of the reported bug).
  • The no-active-theme fallback renders the built-in template without repointing views.
  • The theme's error template still renders when the theme is mounted (no regression to the working path).

Verified the two behavioural regression tests fail against the original code and pass with this change. Full ghost/core frontend unit suite (1889 tests) and lint are green.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e31351ff-d113-4bba-aa41-fb4e36b08608

📥 Commits

Reviewing files that changed from the base of the PR and between 2ea377b and fa9c2c8.

📒 Files selected for processing (2)
  • ghost/core/core/frontend/web/middleware/error-handler.js
  • ghost/core/test/unit/frontend/web/middleware/error-handler.test.js
🚧 Files skipped from review as they are similar to previous changes (2)
  • ghost/core/core/frontend/web/middleware/error-handler.js
  • ghost/core/test/unit/frontend/web/middleware/error-handler.test.js

Walkthrough

The error handler middleware (themeErrorRenderer) is modified to check active-theme state via themeEngine.getActive() before rendering errors. When a theme is not mounted or unavailable, the renderer falls back to an absolute defaultViews/error.hbs path without mutating req.app's views directory. The hbs engine is registered only when req.app.engines is empty. Four regression tests cover scenarios where the active theme is mounted, unmounted, undefined, or reused across multiple requests, verifying both themed and fallback error rendering paths.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main fix: resolving HTTP 500 errors from themes with custom error templates that weren't mounted yet.
Description check ✅ Passed The description comprehensively details the bug, root causes, solution, and testing approach, directly relating to the changeset modifications.
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.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hopeful-brown-33b0f6

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.

@nx-cloud

nx-cloud Bot commented Jun 22, 2026

Copy link
Copy Markdown

🤖 Nx Cloud AI Fix

Ensure the fix-ci command is configured to always run in your CI pipeline to get automatic fixes in future runs. For more information, please see https://nx.dev/ci/features/self-healing-ci


View your CI Pipeline Execution ↗ for commit 2ea377b

Command Status Duration Result
nx run ghost:test:ci:integration ✅ Succeeded 2m 9s View ↗
nx run ghost:test:ci:integration:no-coverage ✅ Succeeded 2m 9s View ↗
nx run ghost:test:ci:e2e ✅ Succeeded 7m 48s View ↗
nx run ghost:test:ci:e2e:no-coverage ✅ Succeeded 6m 33s View ↗
nx build @tryghost/activitypub ✅ Succeeded 3s View ↗
nx build @tryghost/portal ✅ Succeeded 1s View ↗
nx build @tryghost/comments-ui ✅ Succeeded <1s View ↗
nx build @tryghost/signup-form ✅ Succeeded <1s View ↗
Additional runs (10) ✅ Succeeded ... View ↗

💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗


☁️ Nx Cloud last updated this comment at 2026-06-22 14:49:19 UTC

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3bc5acbbaf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ghost/core/core/frontend/web/middleware/error-handler.js Outdated
@sagzy sagzy force-pushed the hopeful-brown-33b0f6 branch from 3bc5acb to 2ea377b Compare June 22, 2026 14:25

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ghost/core/core/frontend/web/middleware/error-handler.js`:
- Around line 112-115: The condition checking `_.isEmpty(req.app.engines)` is
too broad and will prevent the `hbs` engine from being registered if any other
engine is already registered. Instead of checking if the engines object is
empty, specifically check whether the `hbs` engine exists in `req.app.engines`
before calling `createHbsEngine()`. Replace the isEmpty check with a condition
that explicitly verifies if the `hbs` engine is not already registered (for
example, checking if `req.app.engines.hbs` is undefined or falsy), and only
proceed with engine registration if that specific check passes.
🪄 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: 731c863b-12a7-42b2-8843-57512bbace04

📥 Commits

Reviewing files that changed from the base of the PR and between 3bc5acb and 2ea377b.

📒 Files selected for processing (2)
  • ghost/core/core/frontend/web/middleware/error-handler.js
  • ghost/core/test/unit/frontend/web/middleware/error-handler.test.js

Comment thread ghost/core/core/frontend/web/middleware/error-handler.js Outdated
no ref

- frontend errors thrown before the theme middleware mounts the active
  theme (errors early in the request pipeline, or the first requests after
  a boot) reached the error renderer with the theme not yet mounted
- in that state the renderer picked a theme-specific error template name
  (e.g. error-404) from the theme's manifest, but Express resolved it
  against Ghost's default views directory — where it does not exist —
  producing `Failed to lookup view "error-404"` and an HTTP 500 instead of
  the theme's error page
- the bare-minimum fallback also permanently repointed the app's `views`
  directory to the default, so once armed it could keep masking real
  status codes (e.g. a 404) as 500s until a normal request mounted the theme
- now, when the active theme isn't mounted, we render Ghost's self-contained
  built-in error template — its engine, views dir and the @site/@custom/
  member template data that theme error pages rely on aren't set up yet — and
  only render theme error templates when the theme is genuinely mounted
- the fallback references the built-in template by absolute path instead of
  mutating the app-wide views directory, so it can no longer break theme
  template lookups on subsequent requests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sagzy sagzy force-pushed the hopeful-brown-33b0f6 branch from 2ea377b to fa9c2c8 Compare June 22, 2026 14:35
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.

1 participant