Skip to content

Fixed unhandled promise rejection in Redis adapter _get timeout path#27007

Closed
muratcorlu wants to merge 5 commits into
TryGhost:mainfrom
muratcorlu:fix/redis-adapter-get-unhandled-rejection
Closed

Fixed unhandled promise rejection in Redis adapter _get timeout path#27007
muratcorlu wants to merge 5 commits into
TryGhost:mainfrom
muratcorlu:fix/redis-adapter-get-unhandled-rejection

Conversation

@muratcorlu
Copy link
Copy Markdown
Contributor

@muratcorlu muratcorlu commented Mar 27, 2026

When getTimeoutMilliseconds was configured, _get wrapped the cache read in a new Promise but did not handle rejections from the inner cache.get() call. If Redis rejected the command (e.g. with enableOfflineQueue: false), the rejection went unhandled, which crashes Ghost.

Fixed by adding a .catch() that clears the timeout and resolves with null, treating a Redis error during a timed get as a cache miss.

Got some code for us? Awesome 🎊!

Please take a minute to explain the change you're making:

  • Why are you making it?
  • What does it do?
  • Why is this something Ghost users or developers need?

Please check your PR against these items:

  • I've read and followed the Contributor Guide
  • I've explained my change
  • I've written an automated test to prove my change works

We appreciate your contribution! 🙏

muratcorlu and others added 2 commits March 28, 2026 00:15
When getTimeoutMilliseconds was configured, _get wrapped the cache read
in a new Promise but did not handle rejections from the inner cache.get()
call. If Redis rejected the command (e.g. with enableOfflineQueue: false),
the rejection went unhandled, which crashes Ghost.

Fixed by adding a .catch() that clears the timeout and resolves with null,
treating a Redis error during a timed get as a cache miss.
@muratcorlu muratcorlu changed the title 🐛 Fixed unhandled promise rejection in Redis adapter _get timeout path Fixed unhandled promise rejection in Redis adapter _get timeout path Mar 27, 2026
Copy link
Copy Markdown

@codeCraft-Ritik codeCraft-Ritik left a comment

Choose a reason for hiding this comment

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

Great catch! Handling unhandled rejections in this scenario is really important for stability

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 30, 2026

Walkthrough

The _get method in the Redis cache adapter was updated to explicitly handle promise rejection from the cache. When the cache get operation fails, the method now clears its timeout and resolves to null, making error handling consistent with the timeout scenario. A corresponding unit test was added to verify this rejection behavior, stubbing the cache to return a rejected promise and asserting the method returns null.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing an unhandled promise rejection in the Redis adapter's _get method when timeout is configured.
Description check ✅ Passed The description is directly related to the changeset, explaining the bug (unhandled rejection when Redis rejects a command with timeout configured), the fix (adding .catch() to clear timeout and resolve with null), and the impact.
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

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 (1)
ghost/core/core/server/adapters/lib/redis/AdapterCacheRedis.js (1)

184-187: Consider logging the swallowed error for observability.

The fix correctly prevents the unhandled rejection, but silently swallowing the error may complicate debugging Redis connectivity issues. Elsewhere in this file, errors are consistently logged via logging.error(err). Consider at least a debug-level log here to maintain observability.

🔧 Proposed fix to add debug logging
-                }).catch(() => {
+                }).catch((err) => {
+                    debug('get', key, 'error', err.message);
                     clearTimeout(timer);
                     resolve(null);
                 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/core/server/adapters/lib/redis/AdapterCacheRedis.js` around lines
184 - 187, The catch block currently swallows the Redis error (catch(() => {
clearTimeout(timer); resolve(null); })), losing observability; change it to
capture the error (catch(err) or catch(error)) and log it via the same logger
used elsewhere (e.g., logging.error(error) or logging.debug(error)) before
clearing the timeout and resolving null so failures are recorded; update the
catch in AdapterCacheRedis.js where timer is cleared and resolve(null) is called
to include the logging call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ghost/core/core/server/adapters/lib/redis/AdapterCacheRedis.js`:
- Around line 184-187: The catch block currently swallows the Redis error
(catch(() => { clearTimeout(timer); resolve(null); })), losing observability;
change it to capture the error (catch(err) or catch(error)) and log it via the
same logger used elsewhere (e.g., logging.error(error) or logging.debug(error))
before clearing the timeout and resolving null so failures are recorded; update
the catch in AdapterCacheRedis.js where timer is cleared and resolve(null) is
called to include the logging call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5026d771-028d-47ea-934a-c643b4eef37d

📥 Commits

Reviewing files that changed from the base of the PR and between c29df56 and e1ef8e8.

📒 Files selected for processing (2)
  • ghost/core/core/server/adapters/lib/redis/AdapterCacheRedis.js
  • ghost/core/test/unit/server/adapters/lib/redis/adapter-cache-redis.test.js

Copy link
Copy Markdown

@codeCraft-Ritik codeCraft-Ritik left a comment

Choose a reason for hiding this comment

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

Great fix! Handling unhandled Redis rejections is critical for system stability

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Apr 9, 2026

@muratcorlu
Copy link
Copy Markdown
Contributor Author

No more relevant with latest changes, closing this.

@muratcorlu muratcorlu closed this Apr 22, 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