Skip to content

Conversation

@kevinansfield
Copy link
Member

@kevinansfield kevinansfield commented Dec 2, 2025

no issue

Summary

  • Removed async keyword from 7 validators in the Settings model that don't use await
  • Fixes Bluebird promise warning when saving labs flags (and other settings)

Problem

When saving settings, Bluebird logged this warning:

Warning: a promise was created in a Promise.each handler at node_modules/bookshelf/lib/base/events.js:101:64 but was not returned from it

The validators (all, labs, stripe_plans, stripe_secret_key, stripe_publishable_key, stripe_connect_secret_key, stripe_connect_publishable_key) were marked async but contained only synchronous code. This caused unnecessary Promise creation inside Bookshelf's event handler chain, triggering Bluebird's warning.

Validators were marked async but didn't use await, causing Bluebird
to warn about promises created but not returned in Bookshelf's event
handlers.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 2, 2025

Walkthrough

This PR changes lifecycle control flow and return propagation across many models and fixes returned values in action helpers. Seven validator methods in ghost/core/core/server/models/settings.js were changed from async to synchronous signatures (removed async; logic unchanged). Numerous model lifecycle hooks (onCreated, onUpdated, onDestroyed) now call the base model lifecycle, capture its return value, emit the corresponding change event, and return the captured result (i.e., superclass result is propagated). Plugins in ghost/core/core/server/models/base/plugins/actions.js and .../events.js now return the results/promises from internal add/insertAction calls so callers receive those results.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Confirm the seven settings validators contain no await/async-only logic and behave correctly as synchronous functions.
  • For each modified model hook, verify the base method is invoked, its return value is captured, the change event is emitted, and the captured value is returned (preserve intended ordering and side effects).
  • Verify actions.js changes return the inner Bookshelf.model(...) promise in both transacting and non-transacting paths.
  • Verify events.js handlers now returning addAction results remain compatible with callers (no unintended promise propagation changes).
  • Run or review tests covering settings validation, lifecycle hooks, action/event insertion, and any code that relied on previous void-return behavior.

Files/areas needing extra attention:

  • ghost/core/core/server/models/settings.js (seven validator methods)
  • ghost/core/core/server/models/base/plugins/actions.js
  • ghost/core/core/server/models/base/plugins/events.js
  • Model files with modified hooks: ghost/core/core/server/models/{comment,comment-report,comment-like,email,integration,label,member,post,tag,user,webhook}

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: removing async keywords from Settings model validators that don't use await.
Description check ✅ Passed The description is directly related to the changeset, explaining the problem (Bluebird warning), the solution (removing async), and which validators were affected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/fix-labs-flag-promise-warning-01ANfstsJ5Fhw6mxbDefUDPB

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b597a71 and 78c3324.

📒 Files selected for processing (12)
  • ghost/core/core/server/models/comment-like.js (1 hunks)
  • ghost/core/core/server/models/comment-report.js (1 hunks)
  • ghost/core/core/server/models/comment.js (1 hunks)
  • ghost/core/core/server/models/email.js (1 hunks)
  • ghost/core/core/server/models/integration.js (1 hunks)
  • ghost/core/core/server/models/label.js (1 hunks)
  • ghost/core/core/server/models/member.js (1 hunks)
  • ghost/core/core/server/models/post.js (2 hunks)
  • ghost/core/core/server/models/settings.js (8 hunks)
  • ghost/core/core/server/models/tag.js (1 hunks)
  • ghost/core/core/server/models/user.js (2 hunks)
  • ghost/core/core/server/models/webhook.js (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • ghost/core/core/server/models/email.js
  • ghost/core/core/server/models/label.js
  • ghost/core/core/server/models/comment-like.js
  • ghost/core/core/server/models/member.js
  • ghost/core/core/server/models/settings.js
  • ghost/core/core/server/models/webhook.js
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24651
File: ghost/core/test/utils/urlUtils.js:53-57
Timestamp: 2025-08-11T19:39:00.428Z
Learning: In Ghost's test utilities, when fixing specific issues like async behavior, it's preferred to maintain existing error handling patterns (even if suboptimal) to keep PRs focused on their primary objective. Error handling improvements can be addressed in separate, dedicated PRs.
📚 Learning: 2025-06-18T10:56:19.906Z
Learnt from: 55sketch
Repo: TryGhost/Ghost PR: 23894
File: ghost/core/core/server/api/endpoints/comments.js:16-22
Timestamp: 2025-06-18T10:56:19.906Z
Learning: The validateCommentData function in ghost/core/core/server/api/endpoints/comments.js is not duplicated elsewhere in the codebase - it's a unique validation function that ensures either post_id or parent_id is provided for comment creation.

Applied to files:

  • ghost/core/core/server/models/comment.js
🧬 Code graph analysis (2)
ghost/core/core/server/models/user.js (4)
ghost/core/core/server/models/comment.js (3)
  • result (125-125)
  • result (269-269)
  • ghostBookshelf (1-1)
ghost/core/core/server/models/email.js (4)
  • result (70-70)
  • result (78-78)
  • result (86-86)
  • ghostBookshelf (2-2)
ghost/core/core/server/models/label.js (4)
  • result (25-25)
  • result (33-33)
  • result (41-41)
  • ghostBookshelf (1-1)
ghost/core/core/server/models/post.js (3)
  • result (395-395)
  • result (469-469)
  • ghostBookshelf (10-10)
ghost/core/core/server/models/post.js (3)
ghost/core/core/server/models/email.js (4)
  • result (70-70)
  • result (78-78)
  • result (86-86)
  • ghostBookshelf (2-2)
ghost/core/core/server/models/label.js (4)
  • result (25-25)
  • result (33-33)
  • result (41-41)
  • ghostBookshelf (1-1)
ghost/core/core/server/models/user.js (4)
  • result (130-130)
  • result (142-142)
  • result (155-155)
  • ghostBookshelf (3-3)
⏰ 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). (9)
  • GitHub Check: Acceptance tests (Node 22.18.0, sqlite3)
  • GitHub Check: Acceptance tests (Node 22.18.0, mysql8)
  • GitHub Check: Legacy tests (Node 22.18.0, mysql8)
  • GitHub Check: Legacy tests (Node 22.18.0, sqlite3)
  • GitHub Check: Unit tests (Node 22.18.0)
  • GitHub Check: Ghost-CLI tests
  • GitHub Check: Lint
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Build & Push
🔇 Additional comments (6)
ghost/core/core/server/models/comment-report.js (1)

23-29: Propagating the base onCreated result is correct and keeps behavior intact

Capturing the result from ghostBookshelf.Model.prototype.onCreated.apply(this, arguments) and returning it ensures the lifecycle hook’s return (including any Promise) is correctly propagated, while model.emitChange('added', options) still runs after the base call as before. This aligns with the pattern used elsewhere in the PR and looks good.

ghost/core/core/server/models/comment.js (1)

124-130: Propagating base onCreated result for Comment looks correct

onCreated now returns the result of ghostBookshelf.Model.prototype.onCreated while keeping the existing emitChange('added') ordering, so behavior is preserved and the lifecycle chain can consume the base hook’s return value.

ghost/core/core/server/models/tag.js (1)

83-105: Tag lifecycle hooks correctly return base results after emitting events

onCreated, onUpdated, and onDestroyed now all return the result from the base model hooks while preserving the existing tag.added/edited/deleted emissions, which aligns with the PR’s lifecycle/promise propagation pattern without altering behavior.

ghost/core/core/server/models/user.js (1)

129-171: User lifecycle hooks now propagate base hook results while preserving logic

onCreated, onUpdated, and onDestroyed now return the value from ghostBookshelf.Model.prototype.onX but keep all status/activation/deactivation event logic and ordering intact, so external behavior stays the same while the lifecycle chain can correctly await the base hooks.

ghost/core/core/server/models/integration.js (1)

51-57: Integration onCreated return propagation is consistent and safe

onCreated now returns the result of the base model’s onCreated while still emitting integration.added in the same place, so behavior is preserved and the calling chain can await whatever the base hook does.

ghost/core/core/server/models/post.js (1)

394-478: Post onUpdated/onDestroyed now correctly return base hook results

Both hooks now return the result of ghostBookshelf.Model.prototype.onUpdated/onDestroyed while keeping all existing status flags and post.* event emissions in the same order, so external behavior is preserved and the lifecycle chain can properly await the base hooks’ work. This aligns with the broader promise‑return pattern in the PR.

Please re-run the tests around post lifecycle (publish/schedule/delete and bulk actions) to confirm there are no hidden assumptions about these hooks previously resolving immediately rather than propagating the base hook’s Promise.


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.

The insertAction function was creating a fire-and-forget Promise
when adding audit log entries. This triggered Bluebird's warning
about promises created but not returned in Promise.mapSeries handlers.

Fixed by returning the Promise through the chain:
- insert() now returns the Promise
- insertAction() returns insert() result when not in transaction
- addAction() returns insertAction() result
- onCreated/onUpdated/onDestroyed return addAction() result

The .catch() still swallows errors so action logging failures
won't fail the main operation.
The Settings model overrides onCreated, onUpdated, and onDestroyed
but wasn't returning the Promise from the prototype call, causing
Bluebird's warning about promises created but not returned.
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)
ghost/core/core/server/models/settings.js (1)

131-139: Optional: Consider removing unnecessary await in future PR.

Lines 134 and 137 use await on the now-synchronous validators. While harmless (await will convert synchronous returns to resolved promises), the await is technically unnecessary. However, removing it would complicate the code since validators could be either sync or async, and keeping this PR focused on fixing the Bluebird warning is appropriate.

Based on learnings, keeping PRs focused on their primary objective is preferred.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfdc1ee and 90efe1f.

📒 Files selected for processing (1)
  • ghost/core/core/server/models/settings.js (8 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: kevinansfield
Repo: TryGhost/Ghost PR: 24651
File: ghost/core/test/utils/urlUtils.js:53-57
Timestamp: 2025-08-11T19:39:00.428Z
Learning: In Ghost's test utilities, when fixing specific issues like async behavior, it's preferred to maintain existing error handling patterns (even if suboptimal) to keep PRs focused on their primary objective. Error handling improvements can be addressed in separate, dedicated PRs.
Learnt from: sam-lord
Repo: TryGhost/Ghost PR: 25303
File: ghost/core/core/server/services/email-service/BatchSendingService.js:19-19
Timestamp: 2025-10-30T17:13:26.190Z
Learning: In ghost/core/core/server/services/email-service/BatchSendingService.js and similar files in the Ghost codebase, prefer using `{...options}` spread syntax without explicit guards like `...(options || {})` when spreading potentially undefined objects, as the maintainer prefers cleaner syntax over defensive patterns when the behavior is safe.
🧬 Code graph analysis (1)
ghost/core/core/server/models/settings.js (2)
ghost/core/core/server/models/post.js (5)
  • options (1070-1070)
  • options (1197-1197)
  • options (1288-1288)
  • options (1341-1341)
  • options (1365-1365)
ghost/core/core/server/models/user.js (8)
  • options (451-451)
  • options (535-535)
  • options (635-635)
  • options (706-706)
  • options (728-728)
  • options (1051-1051)
  • options (1083-1083)
  • options (1152-1152)
⏰ 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). (9)
  • GitHub Check: Acceptance tests (Node 22.18.0, mysql8)
  • GitHub Check: Legacy tests (Node 22.18.0, sqlite3)
  • GitHub Check: Acceptance tests (Node 22.18.0, sqlite3)
  • GitHub Check: Legacy tests (Node 22.18.0, mysql8)
  • GitHub Check: Unit tests (Node 22.18.0)
  • GitHub Check: Lint
  • GitHub Check: Ghost-CLI tests
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Build & Push
🔇 Additional comments (8)
ghost/core/core/server/models/settings.js (8)

317-336: LGTM! Correctly removed async from synchronous validator.

The all validator contains only synchronous validation logic and doesn't use await or return promises, so removing async is correct and eliminates unnecessary Promise creation.


337-347: LGTM! Correctly removed async from synchronous validator.

The labs validator contains only synchronous validation logic (JSON parsing and flag checking), so removing async is correct.


348-382: LGTM! Correctly removed async from synchronous validator.

The stripe_plans validator performs only synchronous operations (JSON parsing, loops, and validation checks), so removing async is correct.


385-398: LGTM! Correctly removed async from synchronous validator.

The stripe_secret_key validator performs only synchronous regex validation, so removing async is correct.


399-412: LGTM! Correctly removed async from synchronous validator.

The stripe_publishable_key validator performs only synchronous regex validation, so removing async is correct.


413-426: LGTM! Correctly removed async from synchronous validator.

The stripe_connect_secret_key validator performs only synchronous regex validation, so removing async is correct.


427-440: LGTM! Correctly removed async from synchronous validator.

The stripe_connect_publishable_key validator performs only synchronous regex validation, so removing async is correct.


110-129: No issues found with the lifecycle hook order change.

The lifecycle hooks now emit events before returning the super call. Event listeners in CacheManager and Stripe service register once and respond to these events regardless of their timing relative to the parent class's lifecycle. The tests confirm both event types fire correctly (e.g., settings.added and settings.description.added). This order change is safe and actually improves promise handling for async code consuming these lifecycle methods.

Models that override onCreated, onUpdated, or onDestroyed were calling
the prototype method but not returning the Promise, causing Bluebird
warnings about promises created but not returned.

Fixed models:
- comment-like.js
- comment-report.js
- comment.js
- email.js
- integration.js
- label.js
- member.js
- post.js
- tag.js
- user.js
- webhook.js
Updated all model event handlers (onCreated, onUpdated, onDestroyed)
to call the prototype method first, then emit custom events, to ensure
the prototype's setup (like model._changed) happens before events fire.

This preserves the original order of operations while still returning
the Promise from the prototype method to fix the Bluebird warning.

refs #25582
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.

3 participants