Skip to content

✨ server: add debt repayment notification#722

Draft
aguxez wants to merge 1 commit intofeature/webhook-queuefrom
debt-repay
Draft

✨ server: add debt repayment notification#722
aguxez wants to merge 1 commit intofeature/webhook-queuefrom
debt-repay

Conversation

@aguxez
Copy link
Contributor

@aguxez aguxez commented Feb 5, 2026


This is part 2 of 2 in a stack made with GitButler:

Summary by CodeRabbit

  • New Features
    • Added automated debt repayment notifications to help users stay informed about their outstanding loan positions on the platform. The system now periodically monitors all user accounts for active debt and sends timely push notifications to ensure users remain aware of their financial obligations and can take prompt action when needed.

@changeset-bot
Copy link

changeset-bot bot commented Feb 5, 2026

🦋 Changeset detected

Latest commit: 1350612

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link

coderabbitai bot commented Feb 5, 2026

Walkthrough

This change implements a debt repayment notification system for maturity checks using BullMQ queues. It introduces scheduled jobs that periodically check account debt status via market or previewer contract implementations, send push notifications to users with debt (with Redis-based deduplication), log errors, and reschedule subsequent checks.

Changes

Cohort / File(s) Summary
Versioning
.changeset/dull-candies-cheer.md
Added changeset entry for patch release documenting debt repayment notification feature.
Maturity Queue Core
server/queues/maturityQueue.ts
Implements BullMQ-based queue and worker for debt checks. Provides two implementations (previewer and market) to check account debt, aggregates per-user status, sends rate-limited push notifications via Redis gating, captures errors via Sentry, and reschedules checks. Exports queue, processor, worker initialization, and scheduling functions.
Maturity Queue Tests
server/test/queues/maturityQueue.test.ts
Comprehensive test suite validating scheduling behavior, dual debt-check implementations, Redis deduplication, push notification triggering, and rescheduling logic across previewer and market configurations.
Server Integration
server/index.ts
Integrates maturity queue initialization and shutdown into server startup and cleanup flows.
Maturity Utilities
server/utils/hasMaturity.ts, server/test/utils/hasMaturity.test.ts
Adds utility function and tests for decoding and checking maturity presence within a 64-bit encoded bitmap structure.
Infrastructure
server/utils/redis.ts
Updates Redis client initialization to set maxRetriesPerRequest to null for compatibility with BullMQ.
Cleanup
server/utils/createCredential.ts
Removes unused JSDoc comment block.

Sequence Diagram

sequenceDiagram
    participant Server as Server<br/>(Startup)
    participant Queue as BullMQ<br/>Queue
    participant Worker as BullMQ<br/>Worker
    participant Contracts as Market/<br/>Previewer<br/>Contracts
    participant Redis as Redis
    participant Sentry as Sentry
    participant PushSvc as Push<br/>Notification
    
    Server->>Queue: scheduleMaturityChecks()
    Queue->>Queue: Add CHECK_DEBTS jobs<br/>(24h, 1h windows)
    activate Worker
    Worker->>Contracts: Fetch account debt<br/>(per implementation)
    Contracts-->>Worker: Debt status aggregated<br/>per user
    Worker->>Redis: Check dedup gate<br/>hasNotified?
    alt Debt Exists & Not Notified
        Redis-->>Worker: Gate check passes
        Worker->>PushSvc: Send notification
        PushSvc-->>Worker: Notification sent
        Worker->>Redis: Set dedup key
    else Duplicate or No Debt
        Redis-->>Worker: Gate blocked or<br/>no debt
    end
    Worker->>Sentry: Log metrics &<br/>errors (on failure)
    alt Window is "1h"
        Worker->>Queue: scheduleMaturityChecks()<br/>(reschedule)
    end
    deactivate Worker
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • nfmelendez
  • cruzdanilo
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '✨ server: add debt repayment notification' is specific and directly reflects the main change—implementing a new debt repayment notification system in the server via a maturity queue worker and related utilities.

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

✨ 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 debt-repay

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.

@gemini-code-assist
Copy link

Summary of Changes

Hello @aguxez, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a critical new feature to proactively notify users about their upcoming debt maturities. It establishes a robust server-side queueing system using BullMQ to periodically check user debt positions on the blockchain. Depending on configuration, it leverages either direct market contract interactions or a previewer contract to identify at-risk users. Timely push notifications are then dispatched via OneSignal, with Redis ensuring that users receive alerts only once per window, aiming to help users manage their debts and avoid potential liquidations.

Highlights

  • New Debt Maturity Notification System: Introduced a new BullMQ queue (maturityQueue) responsible for scheduling and processing debt repayment notifications to users.
  • Flexible Debt Checking Implementation: Implemented two distinct methods for checking user debt positions on-chain: one directly interacting with market contracts and another utilizing a previewer contract, configurable via an environment variable.
  • Push Notification Integration: Integrated with OneSignal to send push notifications to users whose debts are approaching maturity, with Redis used to prevent duplicate notifications within a 24-hour window.
  • Robust Error Handling and Monitoring: Incorporated Sentry for comprehensive error tracking and breadcrumbs for monitoring the activity and performance of the new maturity queue.
  • Comprehensive Testing: Added extensive unit tests for the new maturityQueue and the hasMaturity utility function to ensure reliability and correctness.
Changelog
  • server/index.ts
    • Integrated the new maturityQueue for proper initialization and graceful shutdown of the debt notification system.
    • Ensured scheduleMaturityChecks is called on server startup, with error capture via Sentry.
  • server/queues/constants.ts
    • Defined new QueueName.MATURITY and MaturityJob.CHECK_DEBTS constants for the debt notification queue.
  • server/queues/markets.ts
    • Added a new file to define DEBT_NOTIFICATION_MARKETS, specifying which markets are relevant for debt notifications.
  • server/queues/maturityQueue.ts
    • Implemented the core logic for the maturityQueue, including job processing, debt checking against blockchain contracts, and sending push notifications.
    • Introduced a configurable implementation type ('market' or 'previewer') for debt checking.
    • Utilized Redis to manage notification idempotency, preventing repeated alerts for the same debt within a set timeframe.
    • Added Sentry integration for error reporting and activity tracking within the queue worker.
    • Provided functions for scheduling maturity checks and gracefully closing the queue and worker.
  • server/test/queues/maturityQueue.test.ts
    • Added comprehensive unit tests for the maturityQueue processor, covering various scenarios for debt detection and notification logic under both 'market' and 'previewer' implementations.
    • Included tests for job scheduling and handling of duplicate notifications.
  • server/test/utils/fixedLibrary.test.ts
    • Introduced unit tests for the hasMaturity utility function, verifying its correctness across different encoded values and maturities.
  • server/utils/createCredential.ts
    • Removed an unnecessary JSDoc comment for WebhookNotReadyError.
  • server/utils/fixedLibrary.ts
    • Added a new utility function hasMaturity to efficiently check for the presence of a specific maturity within a packed bigint representation.
  • server/utils/redis.ts
    • Updated the Redis client configuration to set maxRetriesPerRequest to null, optimizing connection behavior.
Activity
  • This pull request is the second part of a two-part stack, building upon previous changes related to the system.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a debt repayment notification system. However, a high-severity Denial of Service vulnerability was identified due to unbounded memory consumption when fetching users, along with a medium-severity logic error causing the notification system to stop rescheduling itself. Additionally, the review suggests improvements for scalability by optimizing database queries, enhancing type safety, and fixing a potential bug in the maturity calculation logic.

@sentry
Copy link

sentry bot commented Feb 5, 2026

✅ All tests passed.

Copy link

@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: 9

🤖 Fix all issues with AI agents
In `@server/queues/markets.ts`:
- Line 3: The array export DEBT_NOTIFICATION_MARKETS is missing a trailing
comma; update the declaration for DEBT_NOTIFICATION_MARKETS (the constant using
marketUSDCAddress and marketWETHAddress) to include a trailing comma after
marketWETHAddress inside the array literal so it follows the project's
trailing-comma style.

In `@server/queues/maturityQueue.ts`:
- Around line 66-74: The current cast on results to access .errors is fragile;
define a shared return type (e.g., DebtCheckResult with contractCalls, users
array, and optional errors?: Map<string, unknown>) and update both
checkDebtsPreviewerImplementation and checkDebtsMarketImplementation to return
that type so callers (like the code in maturityQueue.ts) can safely read
results.errors without casting; update the function signatures and return shapes
accordingly and remove the (results as ...) cast in the block handling
implementation === "previewer".
- Around line 15-17: The env var parsing for DEBT_NOTIFICATION_IMPLEMENTATION
currently casts any string to ImplementationType which hides invalid values;
update the initialization of implementation to read
process.env.DEBT_NOTIFICATION_IMPLEMENTATION as a raw string, validate it
against the allowed values ("market" | "previewer") (using the
ImplementationType union or a small set/array), and if it's invalid either throw
a clear error or log and fall back to a safe default; ensure you reference the
ImplementationType type and the implementation constant so the check occurs
before any code branches like implementation === "market" run.
- Around line 207-208: The call to publicClient.readContract is using a
redundant cast ``market as `0x${string}```; remove the unnecessary cast and pass
market directly (it already comes from DEBT_NOTIFICATION_MARKETS typed as
readonly [Address, Address]), and if TypeScript complains, adjust the
DEBT_NOTIFICATION_MARKETS/market declaration to be the correct Address type
rather than casting at the call site; locate the usage in maturityQueue.ts
inside the publicClient.readContract invocation and replace the casted argument
with market.

In `@server/test/queues/maturityQueue.test.ts`:
- Around line 318-341: The test "uses previewer implementation when configured"
is placed under the "market implementation" describe but validates the previewer
flow; move this it-block to the existing "previewer implementation" describe (or
to the top-level worker describe) so tests are grouped correctly—relocate the
entire it(...) that imports "../../queues/maturityQueue", uses
previewerProcessor, previewerAddress/previewerAbi, mockUsers and
mocks.readContract expectations into the "previewer implementation" describe
block and remove the original copy from the "market implementation" block.
- Line 350: Replace the hard-coded MATURITY_INTERVAL constant in
maturityQueue.test.ts with an import from the source module to avoid
duplication: remove the local declaration of MATURITY_INTERVAL and import the
exported MATURITY_INTERVAL from server/utils/fixedLibrary.ts (the symbol name is
MATURITY_INTERVAL) so tests stay in sync with the implementation; update any
references in the test file to use the imported MATURITY_INTERVAL.
- Around line 257-264: The mock for mocks.readContract is returning the same
encoded payload for every function call and should discriminate by the requested
function; update the mockImplementation for mocks.readContract to inspect the
first arg's functionName (like the existing pattern around the other mock) and
return the appropriate response for "accounts" vs "fixedBorrowPositions" (e.g.,
return [encoded, 0n, 0n] for the accounts call and the other tuple for
fixedBorrowPositions), preserving the callCount fallback behavior if needed so
the test mirrors the real call sequence and doesn't mask incorrect call
ordering.

In `@server/test/utils/fixedLibrary.test.ts`:
- Around line 62-66: The test "handles offset zero (base maturity)" is redundant
with the existing "returns true when base maturity matches"; remove the
redundant it(...) block or change it to cover a different scenario (e.g., test a
non-zero offset or the case where the maturity bit is unset) so it exercises a
unique code path in hasMaturity; locate the test by the it title "handles offset
zero (base maturity)" and update or delete it accordingly, ensuring the
remaining tests still assert hasMaturity(encoded, baseMaturity) behavior for
distinct cases.

In `@server/utils/fixedLibrary.ts`:
- Around line 1-2: The MATURITY_INTERVAL constant is duplicated; export
MATURITY_INTERVAL from fixedLibrary.ts so other modules reuse it as the single
source of truth: add an export for the existing MATURITY_INTERVAL symbol in
fixedLibrary.ts, remove the duplicate local definition in the other module (the
one that currently defines its own MATURITY_INTERVAL), and update that module to
import MATURITY_INTERVAL from fixedLibrary.ts; keep MASK_BASE_MATURITY
unchanged.

Comment on lines +207 to +233
publicClient.readContract({
address: market as `0x${string}`,
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Redundant type cast.

market comes from DEBT_NOTIFICATION_MARKETS which is typed as readonly [Address, Address]. The cast to `0x${string}` is unnecessary.

Proposed fix
       positionPromises.push(
         publicClient.readContract({
-          address: market as `0x${string}`,
+          address: market,
           abi: marketAbi,
           functionName: "fixedBorrowPositions",
           args: [BigInt(maturity), account as `0x${string}`],
         }),
📝 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
publicClient.readContract({
address: market as `0x${string}`,
positionPromises.push(
publicClient.readContract({
address: market,
abi: marketAbi,
functionName: "fixedBorrowPositions",
args: [BigInt(maturity), account as `0x${string}`],
}),
🤖 Prompt for AI Agents
In `@server/queues/maturityQueue.ts` around lines 207 - 208, The call to
publicClient.readContract is using a redundant cast ``market as `0x${string}```;
remove the unnecessary cast and pass market directly (it already comes from
DEBT_NOTIFICATION_MARKETS typed as readonly [Address, Address]), and if
TypeScript complains, adjust the DEBT_NOTIFICATION_MARKETS/market declaration to
be the correct Address type rather than casting at the call site; locate the
usage in maturityQueue.ts inside the publicClient.readContract invocation and
replace the casted argument with market.

const module_ = await import("../../queues/maturityQueue");
const processor = module_.processor;

const MATURITY_INTERVAL = 2_419_200;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Import MATURITY_INTERVAL from the source module.

Same duplication issue as in fixedLibrary.test.ts. Import from server/utils/fixedLibrary.ts to stay in sync.

♻️ Proposed fix
+import { MATURITY_INTERVAL } from "../../utils/fixedLibrary";
+
 // ... later in the test
-    const MATURITY_INTERVAL = 2_419_200;
     const jobMaturity = MATURITY_INTERVAL * 10;
📝 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
const MATURITY_INTERVAL = 2_419_200;
import { MATURITY_INTERVAL } from "../../utils/fixedLibrary";
// ... rest of imports and test setup ...
const jobMaturity = MATURITY_INTERVAL * 10;
🤖 Prompt for AI Agents
In `@server/test/queues/maturityQueue.test.ts` at line 350, Replace the hard-coded
MATURITY_INTERVAL constant in maturityQueue.test.ts with an import from the
source module to avoid duplication: remove the local declaration of
MATURITY_INTERVAL and import the exported MATURITY_INTERVAL from
server/utils/fixedLibrary.ts (the symbol name is MATURITY_INTERVAL) so tests
stay in sync with the implementation; update any references in the test file to
use the imported MATURITY_INTERVAL.

Comment on lines 62 to 66
it("handles offset zero (base maturity)", () => {
const baseMaturity = 1_000_000;
const encoded = BigInt(baseMaturity) | (1n << 32n);
expect(hasMaturity(encoded, baseMaturity)).toBe(true);
});
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Redundant test case.

This test is functionally identical to "returns true when base maturity matches" (Lines 18-22). Both verify offset zero with bit 0 set. Consider removing or differentiating.

🤖 Prompt for AI Agents
In `@server/test/utils/fixedLibrary.test.ts` around lines 62 - 66, The test
"handles offset zero (base maturity)" is redundant with the existing "returns
true when base maturity matches"; remove the redundant it(...) block or change
it to cover a different scenario (e.g., test a non-zero offset or the case where
the maturity bit is unset) so it exercises a unique code path in hasMaturity;
locate the test by the it title "handles offset zero (base maturity)" and update
or delete it accordingly, ensuring the remaining tests still assert
hasMaturity(encoded, baseMaturity) behavior for distinct cases.

Comment on lines 1 to 2
const MATURITY_INTERVAL = 2_419_200;
const MASK_BASE_MATURITY = (1n << 32n) - 1n;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider exporting MATURITY_INTERVAL to avoid duplication.

This constant is also defined in server/queues/maturityQueue.ts (line 19). Exporting it from here would maintain a single source of truth.

Proposed fix
-const MATURITY_INTERVAL = 2_419_200;
+export const MATURITY_INTERVAL = 2_419_200;
 const MASK_BASE_MATURITY = (1n << 32n) - 1n;
📝 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
const MATURITY_INTERVAL = 2_419_200;
const MASK_BASE_MATURITY = (1n << 32n) - 1n;
export const MATURITY_INTERVAL = 2_419_200;
const MASK_BASE_MATURITY = (1n << 32n) - 1n;
🤖 Prompt for AI Agents
In `@server/utils/fixedLibrary.ts` around lines 1 - 2, The MATURITY_INTERVAL
constant is duplicated; export MATURITY_INTERVAL from fixedLibrary.ts so other
modules reuse it as the single source of truth: add an export for the existing
MATURITY_INTERVAL symbol in fixedLibrary.ts, remove the duplicate local
definition in the other module (the one that currently defines its own
MATURITY_INTERVAL), and update that module to import MATURITY_INTERVAL from
fixedLibrary.ts; keep MASK_BASE_MATURITY unchanged.

Copy link

@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: 3

🤖 Fix all issues with AI agents
In `@server/queues/maturityQueue.ts`:
- Around line 308-324: The delay expressions for the two scheduled adds can be
negative if now is past the target time; update the delay calculation used in
getMaturityQueue().add (for MaturityJob.CHECK_DEBTS jobs with jobId
`check-debts-${nextMaturity}-24h` and `check-debts-${nextMaturity}-1h`) to clamp
to zero (e.g., compute the rawDelay as (nextMaturity - 24*3600 - now) * 1000 and
(nextMaturity - 3600 - now) * 1000 and then use Math.max(0, rawDelay) or
equivalent) so BullMQ never receives a negative delay.
- Line 31: Remove the duplicate MATURITY_INTERVAL constant from
server/queues/maturityQueue.ts and instead import the exported MATURITY_INTERVAL
from server/utils/fixedLibrary.ts (ensure fixedLibrary.ts exports
MATURITY_INTERVAL if not already). Update the top of maturityQueue.ts to import
{ MATURITY_INTERVAL } and delete the local const declaration so all code in
functions like the maturity processing logic uses the single shared constant.

In `@server/test/utils/fixedLibrary.test.ts`:
- Line 5: Replace the hard-coded MATURITY_INTERVAL constant in the test with an
import from the module that defines it: export MATURITY_INTERVAL from the source
module (fixedLibrary.ts) if not already exported, then in the test file remove
the local declaration and import { MATURITY_INTERVAL } from that module so the
test uses the canonical value from fixedLibrary rather than a duplicated
literal.

Comment on lines +308 to +337
await getMaturityQueue().add(
MaturityJob.CHECK_DEBTS,
{ maturity: nextMaturity, window: "24h" },
{
jobId: `check-debts-${nextMaturity}-24h`,
delay: (nextMaturity - 24 * 3600 - now) * 1000,
},
);

await getMaturityQueue().add(
MaturityJob.CHECK_DEBTS,
{ maturity: nextMaturity, window: "1h" },
{
jobId: `check-debts-${nextMaturity}-1h`,
delay: (nextMaturity - 3600 - now) * 1000,
},
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Delay calculation may produce negative values.

If now is already past nextMaturity - 24 * 3600 (or nextMaturity - 3600), the delay becomes negative. BullMQ treats negative delays as immediate execution, which may cause unintended job execution timing.

Consider adding a guard:

Proposed fix
   await getMaturityQueue().add(
     MaturityJob.CHECK_DEBTS,
     { maturity: nextMaturity, window: "24h" },
     {
       jobId: `check-debts-${nextMaturity}-24h`,
-      delay: (nextMaturity - 24 * 3600 - now) * 1000,
+      delay: Math.max(0, (nextMaturity - 24 * 3600 - now) * 1000),
     },
   );

   await getMaturityQueue().add(
     MaturityJob.CHECK_DEBTS,
     { maturity: nextMaturity, window: "1h" },
     {
       jobId: `check-debts-${nextMaturity}-1h`,
-      delay: (nextMaturity - 3600 - now) * 1000,
+      delay: Math.max(0, (nextMaturity - 3600 - now) * 1000),
     },
   );
🤖 Prompt for AI Agents
In `@server/queues/maturityQueue.ts` around lines 308 - 324, The delay expressions
for the two scheduled adds can be negative if now is past the target time;
update the delay calculation used in getMaturityQueue().add (for
MaturityJob.CHECK_DEBTS jobs with jobId `check-debts-${nextMaturity}-24h` and
`check-debts-${nextMaturity}-1h`) to clamp to zero (e.g., compute the rawDelay
as (nextMaturity - 24*3600 - now) * 1000 and (nextMaturity - 3600 - now) * 1000
and then use Math.max(0, rawDelay) or equivalent) so BullMQ never receives a
negative delay.


import hasMaturity from "../../utils/fixedLibrary";

const MATURITY_INTERVAL = 2_419_200;
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Import MATURITY_INTERVAL from source module.

Once MATURITY_INTERVAL is exported from server/utils/fixedLibrary.ts, import it here to maintain a single source of truth and avoid drift.

Proposed fix
-import hasMaturity from "../../utils/fixedLibrary";
+import hasMaturity, { MATURITY_INTERVAL } from "../../utils/fixedLibrary";
 
-const MATURITY_INTERVAL = 2_419_200;
🤖 Prompt for AI Agents
In `@server/test/utils/fixedLibrary.test.ts` at line 5, Replace the hard-coded
MATURITY_INTERVAL constant in the test with an import from the module that
defines it: export MATURITY_INTERVAL from the source module (fixedLibrary.ts) if
not already exported, then in the test file remove the local declaration and
import { MATURITY_INTERVAL } from that module so the test uses the canonical
value from fixedLibrary rather than a duplicated literal.

Copy link

@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: 2

🤖 Fix all issues with AI agents
In `@server/queues/maturityQueue.ts`:
- Around line 310-312: The close() function currently awaits closing
maturityWorker and _maturityQueue but leaves their references intact, causing
initializeWorker() to no-op on subsequent calls; after awaiting the closures in
close(), reset the module-level references (maturityWorker and _maturityQueue)
to undefined (or null) so initializeWorker() can create new instances on
re-initialization.

In `@server/utils/redis.ts`:
- Line 5: The shared Redis client exported currently with "new
Redis(process.env.REDIS_URL, { maxRetriesPerRequest: null })" must not be used
on request-path handlers because null causes indefinite retries; create two
clients instead: keep a background client (e.g., backgroundRedis) with
maxRetriesPerRequest: null for queues/workers, and add a separate request-safe
client (e.g., redisClient or requestRedis) with a bounded retry strategy (set
maxRetriesPerRequest to a finite number) and a connectTimeout to be used by
request handlers referenced in authentication.ts, registration.ts and
hooks/block.ts; update exports so request handlers import the bounded client
while queue code uses the background client.

Comment on lines +310 to +312
export async function close() {
await Promise.all([maturityWorker?.close() ?? Promise.resolve(), _maturityQueue?.close() ?? Promise.resolve()]);
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Reset queue/worker refs after close.

Without resetting these references, subsequent initializeWorker() calls in the same process will no-op (tests, hot reloads).

♻️ Proposed fix
 export async function close() {
   await Promise.all([maturityWorker?.close() ?? Promise.resolve(), _maturityQueue?.close() ?? Promise.resolve()]);
+  maturityWorker = undefined;
+  _maturityQueue = undefined;
 }
🤖 Prompt for AI Agents
In `@server/queues/maturityQueue.ts` around lines 310 - 312, The close() function
currently awaits closing maturityWorker and _maturityQueue but leaves their
references intact, causing initializeWorker() to no-op on subsequent calls;
after awaiting the closures in close(), reset the module-level references
(maturityWorker and _maturityQueue) to undefined (or null) so initializeWorker()
can create new instances on re-initialization.

if (!process.env.REDIS_URL) throw new Error("missing redis url");

export default new Redis(process.env.REDIS_URL);
export default new Redis(process.env.REDIS_URL, { maxRetriesPerRequest: null });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all imports of the shared Redis client
rg "from.*utils/redis|import.*redis.*from.*utils" --type=ts --type=tsx -n

Repository: exactly/exa

Length of output: 82


🏁 Script executed:

#!/bin/bash
# Also check for direct imports in the server directory
cd server && find . -name "*.ts" -o -name "*.tsx" | head -20

Repository: exactly/exa

Length of output: 424


🏁 Script executed:

#!/bin/bash
# Get an overview of the Redis client export
cat server/utils/redis.ts

Repository: exactly/exa

Length of output: 236


🏁 Script executed:

#!/bin/bash
# Corrected search for imports of the Redis client
rg "from.*utils/redis|import.*redis.*from" server --type ts -n -A 2

Repository: exactly/exa

Length of output: 983


🏁 Script executed:

#!/bin/bash
# Search for common Redis method calls to understand usage patterns
rg "redis\.(get|set|push|pop|lpush|rpush|blpop|brpop|subscribe|publish)" server --type ts -n -B 2 -A 1 | head -80

Repository: exactly/exa

Length of output: 1842


🏁 Script executed:

#!/bin/bash
# Check the context of hooks/block.ts to understand if it's called in request path
sed -n '50,70p' server/hooks/block.ts

Repository: exactly/exa

Length of output: 917


🏁 Script executed:

#!/bin/bash
# Check for any timeout configurations on the Redis client
rg "connectTimeout|commandTimeout|lazyConnect|retryStrategy" server --type ts

Repository: exactly/exa

Length of output: 37


🏁 Script executed:

#!/bin/bash
# Look at how block.ts is used/imported
rg "from.*hooks/block|hooks/block" server --type ts -n

Repository: exactly/exa

Length of output: 299


🏁 Script executed:

#!/bin/bash
# Check how redis is used in block.ts to confirm it's in request handler
rg "redis\." server/hooks/block.ts -n -B 1 -A 1 | head -40

Repository: exactly/exa

Length of output: 1311


Split Redis client: use indefinite retries only for background queues.

The shared Redis client is used in request-path handlers (server/api/auth/authentication.ts, server/api/auth/registration.ts, server/hooks/block.ts) where maxRetriesPerRequest: null causes indefinite retries during outages. Create a separate client with reasonable retry limits for request handlers, or add connectTimeout and configure a bounded retry strategy for the shared instance.

🤖 Prompt for AI Agents
In `@server/utils/redis.ts` at line 5, The shared Redis client exported currently
with "new Redis(process.env.REDIS_URL, { maxRetriesPerRequest: null })" must not
be used on request-path handlers because null causes indefinite retries; create
two clients instead: keep a background client (e.g., backgroundRedis) with
maxRetriesPerRequest: null for queues/workers, and add a separate request-safe
client (e.g., redisClient or requestRedis) with a bounded retry strategy (set
maxRetriesPerRequest to a finite number) and a connectTimeout to be used by
request handlers referenced in authentication.ts, registration.ts and
hooks/block.ts; update exports so request handlers import the bounded client
while queue code uses the background client.

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