Skip to content

Conversation

@aguxez
Copy link
Contributor

@aguxez aguxez commented Jan 5, 2026

Summary by CodeRabbit

  • New Features

    • Webhook subscription updates during user creation now run via a background queue for faster, non-blocking user creation.
  • Tests

    • Added tests covering the queue processor and credential creation paths, including success and failure scenarios.
  • Chores

    • Added release metadata and updated environment/shutdown settings to support the new background processing.

✏️ Tip: You can customize this high-level summary in your review settings.

@changeset-bot
Copy link

changeset-bot bot commented Jan 5, 2026

🦋 Changeset detected

Latest commit: dbc2973

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 Jan 5, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a BullMQ-based Alchemy queue and worker, enqueues webhook-update jobs from credential creation, updates shutdown to close the queue, and adds unit tests and a changeset declaring a patch release.

Changes

Cohort / File(s) Summary
Queue constants
server/queues/constants.ts
Adds QueueName and AlchemyJob constants and corresponding union types.
Alchemy queue implementation
server/queues/alchemyQueue.ts
New BullMQ Queue and Worker for alchemy jobs, AlchemyJobData type, job processor, Redis connection setup (from REDIS_URL), worker event handlers, close() to gracefully shut down.
Credential creation integration
server/utils/createCredential.ts
Signature changed to accept credentialId; replaces direct webhook address update with alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }) when webhookId exists; adds error capture for queue enqueue failures.
Server shutdown & scripts
server/index.ts, server/script/openapi.ts
Imports and calls queue close() during server shutdown; test env in script updated REDIS_URL and sets DISABLE_WORKERS="true".
Tests
server/test/queues/alchemyQueue.test.ts, server/test/utils/createCredential.test.ts
Adds unit tests for the alchemy queue processor (PATCH call, error handling, unknown job) and for createCredential (enqueue behavior, error capture, missing webhookId).
Changeset
.changeset/short-cars-return.md
Adds changeset declaring a patch release for @exactly/server with description "✨ add queue on user creation".

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant CreateCred as createCredential()
    participant Queue as alchemyQueue
    participant Redis as Redis
    participant Worker as alchemyWorker
    participant Sentry as Sentry
    participant AlchemyAPI as Alchemy API

    User->>CreateCred: create credential (account, webhookId)
    CreateCred->>Queue: add(ADD_SUBSCRIBER, {account, webhookId})
    Queue->>Redis: persist job
    Redis-->>Queue: job enqueued
    Worker->>Redis: fetch job
    Redis-->>Worker: deliver job
    Worker->>Sentry: startSpan("alchemy.processor")
    Worker->>AlchemyAPI: PATCH /webhooks/{webhookId} (addresses)
    alt API success
        AlchemyAPI-->>Worker: 200 OK
        Worker->>Sentry: add breadcrumb (completed)
    else API failure
        AlchemyAPI-->>Worker: non-OK
        Worker->>Sentry: recordException + set tag (queue failure)
        Worker-->>Queue: mark job failed
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • ✨ server: integrate sardine #594: Modifies the same server/utils/createCredential.ts (adds Sardine customer call) and may conflict with the updated signature and enqueue logic.

Suggested reviewers

  • nfmelendez
🚥 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 accurately describes the main change: introducing a queue system for webhook updates on user creation (via createCredential).

✏️ 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 feature/webhook-queue

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 robust queueing system for handling Alchemy webhook subscription updates. By integrating BullMQ, the application now processes these updates asynchronously, moving the task from the main request-response cycle to a background worker. This change enhances the system's reliability and performance by preventing blocking operations and providing built-in retry capabilities for external API calls, particularly when a new user credential is created.

Highlights

  • Introduction of Alchemy Queue: A new BullMQ queue (alchemyQueue) and its worker are implemented to manage Alchemy-related background tasks, specifically for updating webhook subscriptions.
  • Asynchronous Webhook Updates: The process of adding a subscriber to an Alchemy webhook is now offloaded from the main thread to the alchemyQueue, improving responsiveness and enabling retry mechanisms.
  • Refactoring createCredential: The createCredential utility function no longer directly calls the Alchemy API; instead, it enqueues an ADD_SUBSCRIBER job to the alchemyQueue.
  • New Queue Constants: Dedicated constants (QueueName and AlchemyJob) are introduced for better organization and type safety of queue names and job types.
  • Testing for Queue Functionality: New test files (alchemyQueue.test.ts and createCredential.test.ts) have been added to ensure the correct behavior of the Alchemy queue processor and its integration with credential creation.
  • Environment Configuration Update: The openapi.ts script now explicitly sets REDIS_URL and DISABLE_WORKERS to manage worker initialization during schema generation.
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 background job queue using BullMQ and Redis to handle Alchemy webhook subscription updates upon user creation. This change refactors the createCredential function to offload the synchronous API call to Alchemy into an asynchronous job. This improves the endpoint's response time and adds resilience through automatic retries on failure. The changes include new files for the queue, worker, and constants, along with corresponding tests. My review provides suggestions to improve the robustness of the queue worker, enhance test coverage for error scenarios, and increase code conciseness.

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: 6

Fix all issues with AI Agents 🤖
In @server/queues/alchemyQueue.ts:
- Line 61: Replace the direct process.env access in the conditional that checks
DISABLE_WORKERS inside alchemyQueue.ts: instead accept a configuration value
(e.g., a boolean like disableWorkers) injected from startup config and use that
variable in the if check instead of process.env.DISABLE_WORKERS; modify the
module API (exported initializer or constructor function) to receive the config
from the app bootstrap where env is loaded once, and update any call sites to
pass the injected config value. Ensure the unique symbol referenced is the
conditional that currently uses process.env.DISABLE_WORKERS and the module's
public initialization function (or top-level export) is updated to accept the
config.
- Around line 18-23: The code directly reads process.env to build the Redis
connection (the const connection) and uses Number() for the port; change this to
accept a Redis config object injected at module initialization (e.g., add a
factory function like createAlchemyQueue(redisConfig) or export a setter that
receives redisConfig) and construct the connection from that injected config
instead of process.env, and parse the port with Number.parseInt(redisConfig.port
?? "6379", 10) rather than Number(); update all uses of the existing connection
constant to obtain it from the factory/initializer you add.
- Around line 38-57: The span.setStatus call in processor currently uses the
magic number 2; replace this with a named Sentry status constant (e.g.,
SpanStatus or Sentry.SpanStatus/SpanStatusType) to improve clarity: import the
appropriate enum from the Sentry package used in the project and call
span.setStatus({ code: SpanStatus.InternalError }) or the equivalent named
member instead of using the literal 2; update the import and the span.setStatus
invocation in the processor function and ensure any type references (Span,
startSpan) remain compatible.

In @server/test/queues/alchemyQueue.test.ts:
- Around line 39-66: The test contains an unnecessary type assertion on the
headers expectation; in the AlchemyQueue test where you call processor and
assert fetch was called, remove the "as unknown as Record<string, string>" cast
on the expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) expression
so the matcher is used directly; keep the rest of the
expect.objectContaining(...) structure intact and run the tests to ensure
type/compile issues are resolved.

In @server/test/utils/createCredential.test.ts:
- Around line 59-71: Remove the unnecessary type assertion on the
expect.stringMatching call in the test: in the "should add a job to alchemyQueue
when credential is created" case, update the assertion that checks
alchemyQueue.add was called with AlchemyJob.ADD_SUBSCRIBER so the account
property uses expect.stringMatching(/^0x/) directly (remove the "as unknown as
string" cast); this change touches the test around createCredential,
mockContext, credentialId and the alchemyQueue.add expectation.

In @server/utils/createCredential.ts:
- Around line 54-56: The captureException call for the
alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }) promise
rejection should include the same error-level metadata used elsewhere; update
the anonymous catch handler to call captureException(error, { level: "error" })
so the AlchemyJob.ADD_SUBSCRIBER error is logged with a consistent severity
level like the Sardine capture.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e7015c and 6b2047a.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
🧠 Learnings (4)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
🧬 Code graph analysis (4)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/test/utils/createCredential.test.ts (3)
server/utils/createCredential.ts (1)
  • createCredential (24-63)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (30-30)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (30-30)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/queues/alchemyQueue.test.ts (2)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (11-16)
  • processor (38-57)
🔇 Additional comments (11)
server/script/openapi.ts (1)

25-26: LGTM: Environment configuration updated for queue support.

The Redis URL format and worker disabling flag are appropriate for the OpenAPI generation script context. These changes align with the new queue infrastructure while preventing worker initialization during spec generation.

.changeset/short-cars-return.md (1)

1-5: LGTM: Changeset follows conventions.

The changeset format and description are correct, using lowercase imperative present tense as required by coding guidelines.

server/test/utils/createCredential.test.ts (2)

9-50: LGTM: Comprehensive mock setup.

The mocks appropriately cover all dependencies required for testing the queue integration, including database, Sentry, cookies, and external services.


73-81: LGTM: Error handling properly tested.

The test correctly validates that queue failures are captured via Sentry, ensuring observability of background job issues.

server/utils/createCredential.ts (1)

1-22: LGTM: Imports properly organized.

The import changes correctly add queue dependencies and organize external libraries before relative paths as required by coding guidelines.

server/test/queues/alchemyQueue.test.ts (2)

8-32: LGTM: Mock setup properly simulates dependencies.

The mocks correctly simulate Alchemy API headers, webhook IDs, Sentry tracing, and HTTP responses needed for processor testing.


68-77: LGTM: Unknown job handling properly tested.

The test correctly validates that unknown job names are gracefully ignored while maintaining observability through Sentry spans.

server/queues/constants.ts (1)

1-11: LGTM! Well-structured constants and types.

The use of as const with derived type aliases provides excellent type safety for queue and job identifiers throughout the application. The naming conventions are clear and consistent.

server/queues/alchemyQueue.ts (3)

8-16: LGTM! Clear interface definition.

The JSDoc comments follow the lowercase convention and provide helpful context for the data structure.


25-30: Queue setup looks good once connection configuration is addressed.

The queue initialization is properly structured. The comment effectively explains the purpose of the queue.


62-87: Excellent Worker configuration and observability.

The Worker setup demonstrates good practices:

  • Rate limiting with reasonable defaults
  • Comprehensive error handling with Sentry integration
  • Breadcrumbs on lifecycle events for debugging

@aguxez aguxez force-pushed the feature/webhook-queue branch 2 times, most recently from 32c8a33 to 0dc7771 Compare January 6, 2026 12:00
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/alchemyQueue.ts:
- Around line 10-14: The code directly reads process.env to set disableWorkers
and Redis configuration; refactor to remove direct env access by creating a
factory initializer (e.g., createAlchemyQueue) that accepts an
AlchemyQueueConfig containing disableWorkers and a redis config object, use that
config to construct the Redis connection or host/port and to instantiate the
Queue (QueueName.ALCHEMY) and worker conditional logic, and update all places
using the disableWorkers variable or inline Redis creation to use the injected
config instead so configuration is loaded once at startup and passed in.

In @server/test/queues/alchemyQueue.test.ts:
- Line 54: Remove the redundant type assertion on the headers expectation in the
test: replace the line using headers: expect.objectContaining({
"X-Alchemy-Token": "mock-token" }) as Record<string, string> with headers:
expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) (i.e., delete the
"as Record<string, string>" cast) so the matcher types from
expect.objectContaining() are used directly.
- Around line 68-77: The test should be updated to expect the processor to throw
for unknown job names (the processor's default path throws Error("Unknown job
name: unknown")). Replace the current await processor(job) with await
expect(processor(job)).rejects.toThrow("Unknown job name: unknown"); and keep
the existing assertions that startSpan was called with expect.objectContaining({
name: "alchemy.processor", op: "queue.process" }) and that fetch was not called,
so the test aligns with the processor function's behavior.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b2047a and 32c8a33.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
🧬 Code graph analysis (3)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (38-38)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/queues/alchemyQueue.test.ts (2)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (19-24)
  • processor (46-73)
🪛 GitHub Actions: test
server/queues/alchemyQueue.ts

[error] 68-68: Unknown job name: unknown. Step: nx run @exactly/server:test:vi (vitest run).

server/test/queues/alchemyQueue.test.ts

[error] 1-1: getaddrinfo EAI_AGAIN redis (DNS resolution failure for Redis) during tests. Step: nx run @exactly/server:test:vi (vitest run).

🪛 GitHub Check: test
server/queues/alchemyQueue.ts

[failure] 68-68: test/queues/alchemyQueue.test.ts > alchemyQueue worker processor > should ignore unknown job names
Error: Unknown job name: unknown
❯ queues/alchemyQueue.ts:68:17
❯ test/queues/alchemyQueue.test.ts:22:7
❯ Module.processor queues/alchemyQueue.ts:47:10
❯ test/queues/alchemyQueue.test.ts:70:11

🔇 Additional comments (11)
server/script/openapi.ts (1)

25-26: LGTM! Environment configuration updated appropriately.

The REDIS_URL now uses a proper connection string format, and the DISABLE_WORKERS flag aligns with the queue worker system introduced in this PR.

.changeset/short-cars-return.md (1)

1-5: LGTM! Changeset follows guidelines.

The changeset correctly uses a patch-level bump and the description follows the required format: lowercase imperative present tense.

server/test/utils/createCredential.test.ts (2)

9-50: LGTM! Comprehensive mock setup.

The test properly mocks all external dependencies including the queue, database, Sentry, and network calls. The use of { spy: true } for Sentry allows verification of captureException calls while preserving the module structure.


73-82: LGTM! Error handling test is correct.

The test properly validates that exceptions from queue operations are captured by Sentry, ensuring observability of background job failures.

server/utils/createCredential.ts (2)

1-22: LGTM! Imports updated for queue-based architecture.

The import changes correctly separate Sentry concerns (setUser from core, captureException from node) and introduce the necessary queue infrastructure. All imports use relative paths as required.


24-61: LGTM! Queue-based architecture improves reliability.

The refactor from direct HTTP calls to background job enqueuing is well-implemented. Using Promise.all for parallel execution of non-blocking operations is appropriate, and the queue approach enables retry logic and better observability for webhook subscriptions.

server/queues/constants.ts (1)

1-11: LGTM! Clean constant definitions.

The const objects with as const and derived type unions provide a type-safe, enum-like pattern for queue and job names. The structure is clear and easily extensible for future queues or job types.

server/test/queues/alchemyQueue.test.ts (2)

39-66: Good test coverage for the happy path.

The test correctly validates the fetch call payload, HTTP method, headers, and Sentry span invocation for the ADD_SUBSCRIBER job.


79-95: Good coverage for API failure scenario.

This test validates that non-OK responses from the Alchemy API result in proper error propagation with the expected message format.

server/queues/alchemyQueue.ts (2)

46-73: Processor implementation is well-structured.

The switch statement with a default case that throws for unknown job names is a robust pattern. The Sentry span wrapping provides good observability. The inline comment /* this is an error code */ clarifies the magic number.


77-102: Worker initialization with lifecycle handlers looks good.

The event handlers for failed, completed, and active provide appropriate observability via Sentry breadcrumbs and exception capture. The limiter configuration (max: 10, duration: 1000) provides rate limiting.

One minor note: the new Worker(...) result is discarded. If you need to gracefully shut down the worker during application termination, consider storing a reference.

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: 5

🤖 Fix all issues with AI Agents
In @server/queues/alchemyQueue.ts:
- Around line 65-69: Update the test "should ignore unknown job names" to assert
that an error is thrown instead of expecting silent ignore: call the function
that processes jobs with an unknown job.name and change the assertion to expect
a rejection/throw (e.g., use expect(...).rejects.toThrow or expect(() =>
...).toThrow) for the error message matching `Unknown job name: ${job.name}` (or
assert span.setStatus was called with code 2 and the same message) so the test
aligns with the implementation that throws the Error constructed in the default
branch (errorMessage) and calls span.setStatus.
- Around line 78-102: The Worker instance created for QueueName.ALCHEMY is not
stored, preventing graceful shutdown; assign the new Worker(...) to a variable
(e.g., alchemyWorker) where it's instantiated (the call using QueueName.ALCHEMY,
processor, connection, limiter) and export or register that reference so
shutdown logic can call alchemyWorker?.close() (or await alchemyWorker.close())
during application termination to allow in-flight jobs to finish.

In @server/test/queues/alchemyQueue.test.ts:
- Around line 68-77: The test incorrectly assumes processor(job) resolves for
unknown job names; instead replace the direct await call with a Jest rejection
assertion: call await expect(processor(job)).rejects.toThrow() (keeping the
subsequent assertions about startSpan and fetch if still relevant) so the test
expects the processor to throw on an unknown job name; reference the processor
function, the job variable (name: "unknown"), startSpan and fetch in the updated
expectation.
- Around line 39-66: Remove the unnecessary type assertion on the headers
expectation in the test: in the "should call Alchemy API to update webhook
addresses" test where you call expect(fetch).toHaveBeenCalledWith(...), delete
the trailing "as Record<string, string>" after expect.objectContaining({
"X-Alchemy-Token": "mock-token" }) so the matcher reads
expect.objectContaining({ headers: expect.objectContaining({ "X-Alchemy-Token":
"mock-token" }) }), leaving the rest of the assertion (Job<AlchemyJobData>,
AlchemyJob.ADD_SUBSCRIBER, processor, and startSpan expectations) unchanged.

In @server/test/utils/createCredential.test.ts:
- Around line 59-71: Remove the unnecessary type assertion on the
expect.stringMatching result in the test for createCredential: modify the
assertion in the alchemyQueue.add expectation to use
expect.stringMatching(/^0x/) without "as string" (target the call to
alchemyQueue.add in the "should add a job to alchemyQueue when credential is
created" test that asserts AlchemyJob.ADD_SUBSCRIBER and objectContaining {
account: expect.stringMatching(/^0x/), webhookId: "webhook-id" }).
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b2047a and 0dc7771.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
🧬 Code graph analysis (3)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (19-24)
  • processor (46-73)
server/test/utils/createCredential.test.ts (3)
server/utils/createCredential.ts (1)
  • createCredential (24-61)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (38-38)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (38-38)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
🪛 GitHub Actions: test
server/test/queues/alchemyQueue.test.ts

[error] 22-22: Test failed: Unknown job name: unknown

server/queues/alchemyQueue.ts

[error] 68-68: Unknown job name: unknown

🪛 GitHub Check: test
server/queues/alchemyQueue.ts

[failure] 68-68: test/queues/alchemyQueue.test.ts > alchemyQueue worker processor > should ignore unknown job names
Error: Unknown job name: unknown
❯ queues/alchemyQueue.ts:68:17
❯ test/queues/alchemyQueue.test.ts:22:7
❯ Module.processor queues/alchemyQueue.ts:47:10
❯ test/queues/alchemyQueue.test.ts:70:11

🔇 Additional comments (7)
server/script/openapi.ts (1)

25-26: LGTM: Environment configuration aligns with queue system requirements.

The Redis URL now uses the full connection string format required by ioredis/BullMQ, and DISABLE_WORKERS prevents background worker processes during OpenAPI spec generation. These changes are appropriate for the script context.

.changeset/short-cars-return.md (1)

1-5: LGTM: Changeset follows conventions.

The changeset format is correct, and the description follows the required lowercase imperative present tense style.

server/test/utils/createCredential.test.ts (1)

73-81: LGTM: Error handling test is comprehensive.

The test correctly verifies that queue failures are captured via Sentry's captureException, ensuring resilient error handling in the credential creation flow.

server/queues/constants.ts (1)

1-11: LGTM: Clean constant definitions following TypeScript best practices.

The use of as const with type extraction via indexed access types is idiomatic and type-safe. The naming is clear and follows conventions.

server/test/queues/alchemyQueue.test.ts (1)

79-95: LGTM: API failure test validates error propagation.

The test correctly verifies that non-OK responses from the Alchemy API result in the processor rejecting with the appropriate error message.

server/queues/alchemyQueue.ts (2)

46-73: LGTM - Processor implementation is well-structured.

The switch statement pattern with a default case that throws for unknown job names is a good defensive approach for catching typos and ensuring new job types are explicitly handled. The Sentry span tracing provides good observability.


19-24: LGTM - Interface is well-documented.

The AlchemyJobData interface provides clear documentation for the job payload structure.

@aguxez aguxez force-pushed the feature/webhook-queue branch from 0dc7771 to 63b777f Compare January 6, 2026 12:28
@sentry
Copy link

sentry bot commented Jan 6, 2026

Codecov Report

❌ Patch coverage is 80.64516% with 6 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.58%. Comparing base (5ed74c1) to head (dbc2973).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
server/queues/alchemyQueue.ts 77.77% 4 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #619      +/-   ##
==========================================
+ Coverage   78.91%   79.58%   +0.67%     
==========================================
  Files          43       46       +3     
  Lines        1987     2660     +673     
  Branches      435      715     +280     
==========================================
+ Hits         1568     2117     +549     
- Misses        269      377     +108     
- Partials      150      166      +16     
Flag Coverage Δ
github 79.58% <80.64%> (+0.67%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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: 1

🤖 Fix all issues with AI Agents
In @server/queues/alchemyQueue.ts:
- Around line 60-62: The default branch in the processor currently only sets
span.setStatus({ code: 2, message: `unknown job name: ${job.name}` }) but
doesn't throw, so unknown job names silently succeed; change the default in the
switch inside processor to throw a new Error(`unknown job name: ${job.name}`)
after setting the span status so the job fails fast (keep the span.setStatus
call), and update the test in server/test/queues/alchemyQueue.test.ts to expect
processor(job) to reject with that error.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0dc7771 and 63b777f.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/script/openapi.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/script/openapi.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/script/openapi.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/script/openapi.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/script/openapi.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/script/openapi.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
🧬 Code graph analysis (4)
server/test/utils/createCredential.test.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (33-33)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (33-33)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/queues/alchemyQueue.test.ts (2)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (14-19)
  • processor (41-65)
🔇 Additional comments (7)
server/script/openapi.ts (1)

25-26: LGTM!

The updated REDIS_URL format and DISABLE_WORKERS flag are appropriate for the OpenAPI spec generation script. Disabling workers prevents unnecessary Redis connections during build-time spec generation. Based on learnings, this script context (not application code) is acceptable for direct process.env usage.

server/queues/constants.ts (1)

1-11: LGTM!

Well-structured constants with proper type derivation using as const. The pattern of deriving types from constant objects ensures type safety and maintains a single source of truth.

.changeset/short-cars-return.md (1)

1-5: LGTM!

Changeset follows guidelines with lowercase imperative present tense description. Patch level is appropriate for this additive feature.

server/test/utils/createCredential.test.ts (1)

52-82: LGTM!

Good test coverage for the queue integration: verifies job enqueueing on credential creation and proper error capture when the queue fails.

server/test/queues/alchemyQueue.test.ts (2)

68-77: Test expectation should change if processor is updated.

This test currently expects unknown jobs to be silently ignored, which matches the current implementation. However, if the processor is updated to throw on unknown job names (as recommended in the alchemyQueue.ts review), update this test to expect the error:

it("should throw an error for unknown job names", async () => {
  const job = { name: "unknown", data: {} } as unknown as Job<AlchemyJobData>;
  await expect(processor(job)).rejects.toThrow("unknown job name: unknown");
  expect(fetch).not.toHaveBeenCalled();
});

39-66: LGTM!

Good test coverage for the processor: verifies correct API call construction for ADD_SUBSCRIBER jobs, proper Sentry span creation, and error handling when the Alchemy API fails.

Also applies to: 79-95

server/utils/createCredential.ts (1)

21-22: LGTM!

Good refactoring from synchronous HTTP calls to queue-based processing. The fire-and-forget pattern with error capture is appropriate for non-critical webhook subscription updates, and the queue provides retry capability on failures.

Also applies to: 54-54

@cruzdanilo
Copy link
Member

@cursor review

@cursor
Copy link

cursor bot commented Jan 6, 2026

PR Summary

Cursor Bugbot is generating a summary for commit 63b777f. Configure here.

@cursor
Copy link

cursor bot commented Jan 6, 2026

PR Summary

Introduces background processing for Alchemy webhook address updates and wires it into credential creation.

  • Adds bullmq queue/worker (alchemyQueue, processor) with Sentry tracing and Redis config; supports disabling workers via DISABLE_WORKERS
  • createCredential now enqueues AlchemyJob.ADD_SUBSCRIBER instead of calling Alchemy directly
  • Adds tests for queue processor and createCredential queuing behavior; updates openapi script env to set REDIS_URL and DISABLE_WORKERS

Written by Cursor Bugbot for commit 63b777f. This will update automatically on new commits. Configure here.

Copy link

@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.

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on February 2

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

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

@aguxez aguxez force-pushed the feature/webhook-queue branch from 63b777f to 03cbfe4 Compare January 7, 2026 11:05
@aguxez aguxez force-pushed the feature/webhook-queue branch from 03cbfe4 to 54b79be Compare January 7, 2026 11:12
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: 6

🤖 Fix all issues with AI agents
In @server/queues/alchemyQueue.ts:
- Line 9: The file directly reads process.env.DISABLE_WORKERS into the const
disableWorkers which violates the rule to not access process.env in application
code; change the module to accept configuration via dependency injection (e.g.,
export a factory like createAlchemyQueue(config) or accept a Config parameter)
and replace direct usage of disableWorkers with config.disableWorkers (or
equivalent) throughout the file so configuration is loaded once at startup and
passed in rather than read from process.env inside this module.
- Around line 21-26: The code directly reads process.env to build the Redis
`connection` object and uses Number() for port parsing; change this by
introducing a factory function (e.g., export function
createRedisConnection(redisConfig: { url?: string; host?: string; port?: string
| number })) that takes the preloaded config instead of accessing process.env
inside this module, and inside that function build the connection: if
redisConfig.url use new Redis(redisConfig.url, { maxRetriesPerRequest: null }),
otherwise use { host: redisConfig.host ?? "localhost", port:
Number.parseInt(String(redisConfig.port ?? "6379"), 10) }; update call sites to
pass the startup-loaded config into createRedisConnection instead of relying on
environment access here.
- Around line 41-68: The span.setStatus calls in processor (inside the
AlchemyJob.ADD_SUBSCRIBER case and the default case) use the magic number 2;
replace that literal with a named constant or the Sentry enum (e.g., import
SpanStatusCode from "@sentry/node" and use SpanStatusCode.Error) or define a
local constant like SPAN_STATUS_ERROR and use it in both span.setStatus calls to
improve readability and consistency.
- Around line 72-100: The Worker instance created for QueueName.ALCHEMY is not
stored, preventing graceful shutdown; assign it to a module-scoped variable
(e.g., let alchemyWorker: Worker | undefined = new Worker(...) or create it then
set alchemyWorker = worker) so you can call alchemyWorker?.close() during
application termination/shutdown handling; ensure the variable is exported or
accessible by your existing shutdown routine and keep existing event handlers
(.on("failed"/"completed"/"active"/"error")) attached to the stored worker.

In @server/test/queues/alchemyQueue.test.ts:
- Line 54: The test assertion includes an unnecessary type assertion on the
headers matcher; remove the trailing "as Record<string, string>" from the
headers line so the matcher uses expect.objectContaining({ "X-Alchemy-Token":
"mock-token" }) directly (update the headers property in the assertion where
expect.objectContaining is used).

In @server/test/utils/createCredential.test.ts:
- Line 67: Remove the redundant type assertion on the account matcher: locate
the object assigning account: expect.stringMatching(/^0x/) as string and simply
use account: expect.stringMatching(/^0x/); the expect.stringMatching call
already returns the correct matcher type so drop the "as string" cast to clean
up the test.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 63b777f and 54b79be.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
🧬 Code graph analysis (3)
server/test/queues/alchemyQueue.test.ts (1)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (14-19)
  • processor (41-68)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (33-33)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
🔇 Additional comments (12)
server/queues/constants.ts (1)

1-11: LGTM! Clean constants definition.

The use of as const with derived enum types is idiomatic TypeScript, providing strong type safety for queue and job names.

server/script/openapi.ts (1)

25-26: LGTM! Appropriate environment configuration for OpenAPI generation.

The explicit Redis URL and DISABLE_WORKERS flag correctly prevent worker initialization during spec generation while maintaining the zero-config local dev environment.

.changeset/short-cars-return.md (1)

1-5: LGTM! Changeset follows the required format.

The summary correctly uses lowercase imperative present tense as per coding guidelines.

server/test/utils/createCredential.test.ts (1)

1-58: LGTM! Comprehensive test coverage with proper mocking.

The test suite correctly validates both the happy path (job enqueuing) and error handling (exception capture), with well-isolated mocks.

Also applies to: 72-82

server/test/queues/alchemyQueue.test.ts (1)

1-53: LGTM! Comprehensive test coverage for queue processor.

The test suite validates all critical paths: successful webhook updates, unknown job handling, and API failure scenarios. The tests correctly expect the processor to throw errors for edge cases.

Also applies to: 55-90

server/utils/createCredential.ts (4)

6-7: LGTM!

The Sentry import changes appropriately separate setUser from @sentry/core and captureException from @sentry/node, following the modular structure of the Sentry SDK.


14-14: LGTM!

The database import addition is necessary for the database operations and follows the project's import conventions.


21-22: LGTM!

The queue-related imports are correctly added to support the new asynchronous job enqueueing approach, replacing the previous synchronous HTTP call.


54-54: Consider consistent error capture options.

The captureException call at line 54 doesn't include a level option, while the similar sardine error capture at line 56 specifies { level: "error" }. For consistency, consider adding the same error level to both non-critical async operations.

🔎 Suggested consistency improvement
-    alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }).catch(captureException),
+    alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }).catch((error: unknown) =>
+      captureException(error, { level: "error" }),
+    ),

Likely an incorrect or invalid review comment.

server/queues/alchemyQueue.ts (3)

1-6: LGTM!

The imports are correctly structured with proper type imports and necessary dependencies for the queue implementation.


11-19: LGTM!

The AlchemyJobData interface is well-documented with clear JSDoc comments explaining each field's purpose.


28-33: LGTM!

The queue is correctly instantiated with proper documentation explaining its purpose for offloading webhook subscription updates and enabling retries.

@aguxez aguxez force-pushed the feature/webhook-queue branch from 54b79be to 61cb4bb Compare January 7, 2026 11:32
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/test/queues/alchemyQueue.test.ts:
- Line 54: The test contains an unnecessary type assertion on the headers
matcher: remove the trailing "as Record<string, string>" from the
expect.objectContaining call (the matcher returned by expect.objectContaining({
"X-Alchemy-Token": "mock-token" }) is already correctly typed); update the line
using the headers matcher (the expression starting with
expect.objectContaining({ "X-Alchemy-Token": "mock-token" })) to omit the cast
and run tests to confirm no type errors remain.

In @server/test/utils/createCredential.test.ts:
- Line 67: Remove the redundant type assertion on the account matcher: replace
the property value "expect.stringMatching(/^0x/) as string" with just
"expect.stringMatching(/^0x/)" in the createCredential.test.ts test (the object
property named account), since expect.stringMatching already returns the correct
matcher type.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 54b79be and 61cb4bb.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
🧠 Learnings (6)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
🧬 Code graph analysis (4)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (33-33)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (14-19)
  • processor (41-68)
server/test/utils/createCredential.test.ts (3)
server/utils/createCredential.ts (1)
  • createCredential (24-61)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (33-33)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
🔇 Additional comments (9)
server/script/openapi.ts (1)

25-26: LGTM! Environment configuration aligns with new queue infrastructure.

The Redis URL now provides a proper connection string for local development, and the DISABLE_WORKERS flag enables control over worker initialization during spec generation.

.changeset/short-cars-return.md (1)

1-5: LGTM! Changeset follows guidelines.

The description uses lowercase imperative present tense as required by the coding guidelines.

server/test/utils/createCredential.test.ts (1)

1-82: LGTM! Test coverage is comprehensive.

The tests appropriately cover both the success path (job enqueuing) and error path (exception capture), with thorough mocking of dependencies.

server/queues/constants.ts (1)

1-11: LGTM! Clean constants definition.

The constants follow TypeScript best practices with as const assertions and proper type derivation. Naming conventions are correct throughout.

server/test/queues/alchemyQueue.test.ts (1)

1-90: LGTM! Comprehensive test coverage for the queue processor.

The tests cover all critical paths: successful processing, unknown job handling, and API failure scenarios. The mocking strategy is thorough and appropriate.

server/utils/createCredential.ts (1)

46-59: Queue-based approach for webhook subscription looks good.

The switch from synchronous HTTP PATCH to asynchronous queue processing improves reliability by enabling retries on API failures and avoids blocking the main request flow. The Promise.all correctly handles the concurrent operations with appropriate error capture.

server/queues/alchemyQueue.ts (3)

60-64: Unknown job handling now correctly throws - good fix.

The default case properly sets the span status and throws an error, ensuring unknown jobs fail fast rather than silently succeeding. This addresses the previous feedback about silent failures.


98-99: Error handler addition prevents process crashes - good fix.

The .on("error") handler correctly captures Redis connection failures and internal errors, preventing uncaught exceptions from crashing the process. This follows the pattern used elsewhere in the codebase (e.g., analytics.on("error", ...) in segment.ts).


41-68: Processor implementation is well-structured.

The processor correctly:

  • Uses Sentry tracing with appropriate span attributes
  • Handles the ADD_SUBSCRIBER job type with proper API call
  • Throws on non-ok responses with status details
  • Throws on unknown job names for fail-fast behavior

@aguxez aguxez force-pushed the feature/webhook-queue branch 3 times, most recently from f9b7be5 to 5e59af2 Compare January 7, 2026 13:53
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 @.changeset/short-cars-return.md:
- Line 5: Update the changelog summary in .changeset/short-cars-return.md by
removing the redundant "server:" prefix so the summary reads "add queue on user
creation" (i.e., replace "server: add queue on user creation" with "add queue on
user creation").

In @server/test/queues/alchemyQueue.test.ts:
- Line 54: Remove the redundant type assertion by deleting "as Record<string,
string>" from the headers matcher so it reads headers: expect.objectContaining({
"X-Alchemy-Token": "mock-token" }); leave the expect.objectContaining() usage
intact (in the test that sets headers) since Vitest will infer the matcher types
correctly.

In @server/test/utils/createCredential.test.ts:
- Line 67: The test includes a redundant type assertion on the account property:
remove the unnecessary "as string" cast and rely on expect.stringMatching(/^0x/)
directly (i.e., replace "account: expect.stringMatching(/^0x/) as string" with
"account: expect.stringMatching(/^0x/)" in the test using
expect.stringMatching).
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 61cb4bb and 5e59af2.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (6)
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
🧬 Code graph analysis (4)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (15-20)
  • processor (42-69)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/utils/createCredential.test.ts (4)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/utils/createCredential.ts (1)
  • createCredential (24-61)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
🔇 Additional comments (5)
server/script/openapi.ts (1)

25-26: LGTM!

The REDIS_URL change to a full URL format is more explicit, and adding DISABLE_WORKERS prevents the queue worker from starting during OpenAPI spec generation, which is appropriate for this tooling script.

server/test/utils/createCredential.test.ts (1)

73-81: LGTM!

The exception handling test correctly verifies that queue failures are captured via Sentry's captureException, ensuring observability for background job enqueue failures.

server/queues/constants.ts (1)

1-11: LGTM!

Clean constants definition following TypeScript best practices. The use of as const for literal types and indexed access types for enums ensures type safety throughout the queue system.

server/test/queues/alchemyQueue.test.ts (2)

68-71: LGTM!

The test correctly validates that the processor throws an error for unknown job names, aligning with the processor's default case behavior.


73-89: LGTM!

Excellent addition of the API failure test case. This ensures the processor correctly handles and reports non-ok responses from the Alchemy API, improving resilience and observability.

@aguxez aguxez force-pushed the feature/webhook-queue branch from 5e59af2 to fdc90ad Compare January 7, 2026 18:15
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: 4

🤖 Fix all issues with AI agents
In @server/queues/alchemyQueue.ts:
- Line 10: Replace the magic number 2 used for SENTRY_SPAN_ERROR_CODE with the
OpenTelemetry constant to clarify intent: import StatusCode from
'@opentelemetry/api' (or the equivalent exported enum) and set const
SENTRY_SPAN_ERROR_CODE = StatusCode.ERROR; alternatively, add a one-line comment
above SENTRY_SPAN_ERROR_CODE documenting that 2 corresponds to OpenTelemetry
STATUS_CODE_ERROR if you prefer not to add the import. Ensure you update any
references to SENTRY_SPAN_ERROR_CODE accordingly.
- Line 6: The utils module currently exports headers that read
process.env.ALCHEMY_WEBHOOKS_KEY directly; refactor so environment loading
happens at startup and the key is injected: change server/utils/alchemy.ts to
export a function (e.g., getAlchemyHeaders(apiKey: string) or
makeHeadersFactory) instead of a constant, remove any direct process.env access,
and update server/queues/alchemyQueue.ts to call that function with the webhook
key loaded during application init (via your startup bootstrap or c.var/context)
so the webhook key is read once at startup and passed in rather than accessed
from process.env inside the module.

In @server/utils/createCredential.ts:
- Around line 54-57: Align error handling by making the alchemyQueue.add catch
consistent with the customer call: replace the current catch(captureException)
on the alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }) call
with catch((error: unknown) => captureException(error, { level: "error" })), so
both failure handlers use the same explicit error-level pattern.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdc90ad and 816cb7d.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (8)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Node.js best practices using ESLint `plugin:n/recommended` configuration

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Hono's built-in error handling by throwing `new HTTPException()` for expected errors; unhandled errors will be caught and logged automatically

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
🧬 Code graph analysis (4)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (15-20)
  • processor (42-69)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/test/utils/createCredential.test.ts (4)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/utils/createCredential.ts (1)
  • createCredential (24-61)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
🔇 Additional comments (13)
server/script/openapi.ts (1)

28-29: LGTM! Environment configuration aligns with queue infrastructure.

The Redis URL format and DISABLE_WORKERS flag properly support the new BullMQ queue while preventing worker initialization during OpenAPI spec generation.

.changeset/short-cars-return.md (1)

1-5: LGTM! Changeset follows conventions.

The changeset format and description follow the required conventions (lowercase, imperative present tense, patch level).

server/queues/constants.ts (1)

1-11: LGTM! Clean constant definitions with proper TypeScript patterns.

The use of as const assertions combined with derived types provides excellent type safety while maintaining a single source of truth for the constant values.

server/queues/alchemyQueue.ts (4)

15-20: LGTM! Well-documented interface.

The AlchemyJobData interface is clearly defined with helpful JSDoc comments explaining each field's purpose.


29-34: LGTM! Queue is properly configured.

The queue documentation clearly explains its purpose for offloading webhook updates and enabling retries.


42-69: LGTM! Processor has solid error handling.

The processor function properly:

  • Wraps execution in Sentry spans for observability
  • Validates API responses and throws descriptive errors
  • Handles unknown job names defensively
  • Sets appropriate span status codes on failures

73-102: LGTM! Worker configuration includes proper observability.

The worker initialization includes:

  • Appropriate rate limiting (10 jobs per second)
  • Comprehensive event handlers for tracking job lifecycle
  • Proper error capture via Sentry

Note: The configuration issue with disableWorkers is already flagged in a separate comment.

server/test/utils/createCredential.test.ts (3)

1-50: LGTM! Comprehensive test setup with proper mocking.

The test file properly mocks all external dependencies including:

  • Queue operations
  • Database access
  • Cookie handling
  • Sentry tracking
  • External API calls

This ensures tests are isolated and deterministic.


59-71: LGTM! Test properly verifies job enqueuing behavior.

The test confirms that:

  • The correct job type (ADD_SUBSCRIBER) is used
  • Job data includes the derived account address
  • The webhookId is properly passed to the queue

73-81: LGTM! Test verifies error handling, but confirm fail-open design is intentional.

The test correctly verifies that queue failures are captured via Sentry. However, this confirms that credential creation succeeds even when the webhook subscription fails (fail-open behavior).

Verify this fail-open design is intentional. The current implementation allows user registration to succeed even if the Alchemy webhook subscription fails. Consider whether this could lead to users missing critical notifications, or if retry logic in the queue worker is sufficient.

server/test/queues/alchemyQueue.test.ts (1)

1-90: LGTM! Test coverage for the queue processor is well-structured.

The tests correctly cover the main execution paths:

  • Successful webhook address update
  • Unknown job name rejection
  • API failure handling

The mock setup is appropriate, and the mockResolvedValueOnce in the third test correctly overrides the module-level fetch mock.

server/utils/createCredential.ts (2)

21-22: Queue-based webhook subscription looks good.

The shift from synchronous API call to queue-based processing is a sound architectural improvement. Fire-and-forget with error logging is appropriate here since webhook subscription can be retried and shouldn't block user signup.

Also applies to: 54-54


14-14: The database import pattern is correct and follows project conventions. Utility functions in server/utils/ consistently import the database directly (as seen in panda.ts and ramps/bridge.ts), which is the established pattern across the codebase. The coding guideline specifying c.var.db applies specifically to route handlers, not utility functions. This utility receives the context parameter but accesses the database through direct import, which is the standard approach for utilities that are called from handlers.

Likely an incorrect or invalid review comment.

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/alchemyQueue.ts:
- Line 9: Module-level reads of process.env (e.g., the const disableWorkers and
any other env checks in this module) must be removed and configuration injected;
change the module to export an initialization function (e.g.,
initializeAlchemyQueue or createAlchemyQueue) that accepts a config object
containing disableWorkers and any other flags currently read at module scope,
move any logic that currently uses process.env (lines referenced 22-27) into
that initializer, and update callers to load/process env once at application
startup and pass that config into the new initializer so the module no longer
performs direct process.env access at import time.

In @server/test/utils/createCredential.test.ts:
- Around line 33-35: The test mock for identify is typed to accept a string but
the real call in createCredential uses an object; update the mock in
createCredential.test (the vi.mock for "../../utils/segment") so identify's
signature matches the actual call (e.g., vi.fn<(args: { userId: string }) =>
void>() or a generic any signature) so identify({ userId: account }) in
createCredential.ts is type-safe and tests reflect real usage.
- Around line 23-25: The mock for setSignedCookie in
server/test/utils/createCredential.test.ts currently types the function as
taking (c: Context, name: string, value: string) but the real usage in
createCredential.ts passes additional args (auth secret and an options object
with expires, httpOnly, domain, sameSite, secure, partitioned). Update the mock
signature for setSignedCookie to include those extra parameters (e.g., add a
secret: string and options: { expires?: Date | number; httpOnly?: boolean;
domain?: string; sameSite?: string; secure?: boolean; partitioned?: boolean })
so the vi.fn type matches the actual call site (setSignedCookie) for proper type
safety and clarity.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fdc90ad and f4b36a5.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/script/openapi.ts
  • server/queues/constants.ts
  • server/test/utils/createCredential.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (8)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Node.js best practices using ESLint `plugin:n/recommended` configuration

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Hono's built-in error handling by throwing `new HTTPException()` for expected errors; unhandled errors will be caught and logged automatically

Applied to files:

  • server/utils/createCredential.ts
🧬 Code graph analysis (4)
server/test/utils/createCredential.test.ts (3)
server/utils/createCredential.ts (1)
  • createCredential (24-61)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (15-20)
  • processor (42-69)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
🔇 Additional comments (10)
server/script/openapi.ts (1)

28-29: LGTM! Environment setup aligns with queue infrastructure.

The Redis URL now uses the proper connection string format, and disabling workers during OpenAPI generation is appropriate to prevent background processing in this context.

.changeset/short-cars-return.md (1)

1-5: LGTM! Changeset follows conventions.

The changeset correctly declares a patch release with a lowercase imperative description as required by the coding guidelines.

server/test/utils/createCredential.test.ts (1)

52-82: Good test coverage for queue behavior.

The tests appropriately verify both the success path (job enqueued with correct payload) and error path (exception captured on queue failure). The use of expect.objectContaining and expect.stringMatching provides flexible assertions without over-specifying implementation details.

server/queues/constants.ts (1)

1-11: LGTM! Idiomatic TypeScript constant definitions.

The use of as const with derived enum types is an idiomatic pattern that provides type safety while avoiding TypeScript enums. The naming conventions follow the coding guidelines correctly.

server/test/queues/alchemyQueue.test.ts (3)

39-66: LGTM! Comprehensive test for the ADD_SUBSCRIBER flow.

The test thoroughly validates the API call construction including URL, method, headers, and body payload. The Sentry span verification ensures observability is properly instrumented.


68-71: LGTM! Validates error handling for unknown job types.

This test ensures the processor fails gracefully when receiving an unexpected job name, which is important for debugging and preventing silent failures.


73-89: LGTM! Validates error handling for API failures.

The test appropriately verifies that non-ok responses from the Alchemy API are caught and thrown with a descriptive error message, which is crucial for debugging production issues.

server/queues/alchemyQueue.ts (2)

42-69: Processor implementation looks solid.

The processor correctly:

  • Uses Sentry tracing for observability
  • Handles the ADD_SUBSCRIBER job type with proper error responses
  • Validates API response status
  • Throws errors for unknown job types

76-76: Rate limiter configuration aligns with internal queue constraints but cannot be verified against Alchemy's API limits.

Alchemy does not publish a public per-second RPS limit for the PATCH update-webhook-addresses endpoint. The rate limiter (10 requests/second) appears reasonable as a job queue throttle; however, the actual constraint is Alchemy's monthly Compute Unit budget. Additionally, the current implementation sends one address per request; Alchemy supports up to 500 addresses per request, so consider batching for efficiency.

server/utils/createCredential.ts (1)

54-54: Excellent architectural improvement: synchronous HTTP replaced with queue.

The change from a direct HTTP PATCH to queue-based processing (alchemyQueue.add) provides:

  • Non-blocking execution (avoids blocking the request thread)
  • Automatic retry capability on failures
  • Better observability through BullMQ and Sentry integration
  • Improved reliability and resilience

The error handling with .catch(captureException) appropriately captures queue submission failures.

@aguxez aguxez force-pushed the feature/webhook-queue branch from f4b36a5 to 64ca136 Compare January 12, 2026 14:38
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: 6

🤖 Fix all issues with AI agents
In @.changeset/short-cars-return.md:
- Line 5: The changeset summary line "server: add queue on user creation" must
be changed to start with a gitmoji and omit the scope; replace that line with a
gitmoji followed by the message (for example "🚀 Add queue on user creation") so
the summary follows the "<emoji> <message>" format and contains no "server:"
scope.

In @server/queues/alchemyQueue.ts:
- Line 9: The direct access to process.env in alchemyQueue.ts (const
disableWorkers) violates guidelines; replace it by reading DISABLE_WORKERS from
the central configuration object or injected context instead of process.env:
import or accept a Config/Context (e.g., config.disableWorkers or
context.disableWorkers) where alchemyQueue is initialized, use that value to set
the disableWorkers variable or pass it into the queue initializer (instead of
process.env.DISABLE_WORKERS), and update callers to provide the config during
startup so env is only read once at application bootstrap.
- Around line 22-27: The code directly reads process.env for Redis config when
creating the connection (the connection constant in alchemyQueue.ts uses
process.env.REDIS_URL, REDIS_HOST, REDIS_PORT); replace these direct env
accesses with values provided by the centralized config module (e.g., import
RedisConfig or getConfig and use RedisConfig.url / RedisConfig.host /
RedisConfig.port) or accept the Redis connection options via dependency
injection (pass a redisConfig or connection object into the queue
initializer/function), and update the connection construction to use those
injected/config values so tests can mock the config and the code no longer
references process.env directly.
- Around line 73-101: The Worker created for QueueName.ALCHEMY is not stored,
preventing graceful shutdown; assign the new Worker(...) to a named variable
(e.g., alchemyWorker or workerAlchemy) and either export it or add it to
whatever lifecycle/worker registry is used by the process termination logic so
the shutdown code can call worker.close(); ensure the stored instance is the
same one that receives the .on(...) handlers (attach handlers to the variable)
and update any shutdown hook to call close() on this variable to allow graceful
termination.

In @server/test/utils/createCredential.test.ts:
- Around line 64-70: The test uses a verbose double-cast for the mocked queue
((alchemyQueue as unknown as { add: Mock }).add); replace it with
vi.mocked(alchemyQueue).add to use the test util consistently (similar to
captureException) so the expectation becomes vi.mocked(alchemyQueue).add
toHaveBeenCalledWith(AlchemyJob.ADD_SUBSCRIBER, expect.objectContaining({...})).
Ensure you import/use vi.mocked and keep the same matcher for account and
webhookId.

In @server/utils/createCredential.ts:
- Around line 54-57: The two promises use inconsistent catch styles:
alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }) uses
.catch(captureException) while customer(...) uses .catch((error) =>
captureException(error, { level: "error" })); make them consistent by changing
the alchemyQueue.add catch to the explicit wrapper form (i.e., .catch((error) =>
captureException(error, { level: "error" })) ) so both calls use the same error
handling pattern and explicit level metadata.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4b36a5 and 64ca136.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (9)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/api/**/*.ts : The database client (`db`) should be passed via Hono's context (`c.var.db`), not imported directly into route handlers

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Hono's built-in error handling by throwing `new HTTPException()` for expected errors; unhandled errors will be caught and logged automatically

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
🧬 Code graph analysis (4)
server/test/utils/createCredential.test.ts (2)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (15-20)
  • processor (42-69)
server/utils/createCredential.ts (3)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/e2e.ts (1)
  • captureException (56-59)
🔇 Additional comments (11)
server/queues/constants.ts (1)

1-11: LGTM!

Clean and idiomatic TypeScript pattern for type-safe constants. The as const assertions enable proper type narrowing, and the derived union types provide good type safety.

server/queues/alchemyQueue.ts (1)

42-69: Processor implementation looks solid.

The processor correctly handles the ADD_SUBSCRIBER job type, sets span status on errors, and throws on unknown job names. The Sentry span tracing provides good observability.

server/script/openapi.ts (1)

28-29: LGTM!

The environment variable additions are appropriate for the OpenAPI generation script. DISABLE_WORKERS="true" correctly prevents worker initialization during spec generation, and the Redis URL uses a proper connection string format for local development.

server/test/utils/createCredential.test.ts (1)

52-82: Test coverage for queue integration looks good.

The tests properly verify that createCredential enqueues jobs with the correct payload and handles queue failures by capturing exceptions. The mock setup is comprehensive.

server/test/queues/alchemyQueue.test.ts (5)

1-6: LGTM!

Imports are correctly organized with external libraries first, then relative imports. Type-only import is properly used for Job.


17-25: LGTM!

The Sentry mock correctly implements the startSpan callback pattern, providing a mock span object with setStatus for error path testing.


39-66: LGTM!

The test comprehensively validates the ADD_SUBSCRIBER job processing, including the correct API endpoint, HTTP method, headers, request body structure, and Sentry tracing setup.


68-71: LGTM!

Properly tests the error path for unknown job names, ensuring the processor throws with an informative message.


73-89: LGTM!

Correctly validates error handling for failed Alchemy API responses. Using mockResolvedValueOnce ensures this override applies only to this specific test.

server/utils/createCredential.ts (2)

6-7: LGTM!

Import sources are correctly split: setUser from @sentry/core and captureException from @sentry/node.


14-14: The coding guidelines specify "No direct database access in handlers - use c.var.db", which applies specifically to handlers, not utility functions. createCredential is a utility function, not a handler, and correctly uses a direct database import consistent with other utility functions in server/utils/ (e.g., panda.ts, ramps/bridge.ts). No changes needed.

@aguxez aguxez force-pushed the feature/webhook-queue branch from 64ca136 to 2d0f995 Compare January 12, 2026 20:12
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: 5

🤖 Fix all issues with AI agents
In @.changeset/short-cars-return.md:
- Line 5: Update the changeset summary line that currently reads "server: add
queue on user creation" to start with a gitmoji and omit the scope (the package
is already declared on line 2); replace it with an emoji-prefixed message like
"✨ add queue on user creation" (or another appropriate gitmoji) so the summary
follows the "<emoji> <message>" format.

In @server/queues/alchemyQueue.ts:
- Line 9: The file reads process.env directly (e.g., disableWorkers constant and
usages of REDIS_URL / REDIS_HOST / REDIS_PORT), which violates the config
guideline; refactor the module to accept a config object or DI context (e.g.,
pass a Config type into the function that builds the queue/worker setup such as
the alchemy queue initializer) instead of reading process.env inside this file,
replace usages of disableWorkers and any direct REDIS_* references with values
from that injected config, and ensure the centralized configuration loader (run
once at startup) populates those fields and is passed into this module.
- Around line 73-101: The Worker instance for QueueName.ALCHEMY is not saved,
preventing graceful shutdown; assign the new Worker(...) to a variable (e.g.,
alchemyWorker) where created, keep the same options and event handlers, and
ensure that this reference is accessible to your app shutdown logic (export it
or push to a central workers list). Then implement application lifecycle hooks
that call await alchemyWorker.close() (and handle undefined) on SIGTERM/SIGINT
to gracefully stop the worker and avoid leaving jobs in an inconsistent state.
- Line 48: The destructuring currently aliases webhookId to hookId (const {
account, webhookId: hookId } = job.data), which uses an abbreviation; change
this to keep the full name (const { account, webhookId } = job.data) and then
update all subsequent references of hookId in the surrounding function
(methods/handlers in alchemyQueue.ts) to use webhookId so variable names are
consistent with coding guidelines.

In @server/utils/createCredential.ts:
- Around line 55-58: The alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account,
webhookId }) call logs errors without contextual metadata unlike the neighboring
customer(...) call; update that promise rejection handler to call
captureException with queue-specific context (e.g., { job:
AlchemyJob.ADD_SUBSCRIBER, accountId: account.id, webhookId, level: "error" })
so failures from alchemyQueue.add include consistent, useful debugging info;
keep the existing captureException usage but wrap it in a .catch((error:
unknown) => captureException(error, { ... })) handler next to the customer(...)
pattern.
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 64ca136 and 2d0f995.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/utils/createCredential.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (16)
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Hono's built-in error handling by throwing `new HTTPException()` for expected errors; unhandled errors will be caught and logged automatically

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commit messages must strictly adhere to the format: `<emoji> <scope>: <message>` where emoji is a gitmoji unicode character, scope is a mandatory short name (app, server, contracts, docs, common, dependencies, github, config, or repo), and message is a short lowercase description

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use gitmoji format for commits: `<emoji> <scope>: <message>` with scopes: `app`, `server`, `contracts`, `common`, `docs`

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Every commit must have a scope; scope must be in lowercase and must be one of: app, server, contracts, docs, common, dependencies, github, config, or repo

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Do not use conventional commit prefixes like `feat:`, `fix:`, or `docs(...)`; do not use title case or sentence case in the commit subject line; do not create commits without a scope

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Commit message scope and message must be in lowercase; the entire subject line should be as short as possible with front-loaded keywords and no filler words

Applied to files:

  • .changeset/short-cars-return.md
🧬 Code graph analysis (4)
server/utils/createCredential.ts (3)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/e2e.ts (1)
  • captureException (57-60)
server/test/utils/createCredential.test.ts (3)
server/utils/createCredential.ts (1)
  • createCredential (24-62)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (15-20)
  • processor (42-69)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
🔇 Additional comments (8)
server/script/openapi.ts (1)

28-29: LGTM!

The updated Redis URL format and DISABLE_WORKERS flag are appropriate for OpenAPI generation. Disabling workers during spec generation prevents unnecessary BullMQ worker initialization, and the full Redis URL format aligns with standard connection string conventions.

server/test/utils/createCredential.test.ts (2)

59-71: LGTM!

Good test coverage for the queue integration. The test correctly verifies that alchemyQueue.add is called with the expected job name and payload structure, including the account address pattern and webhookId.


73-81: LGTM!

Correctly tests the error capture path. The test validates that when alchemyQueue.add fails, the error is properly captured via Sentry without breaking the credential creation flow.

server/queues/constants.ts (1)

1-11: LGTM!

Well-structured constants file using the as const assertion pattern for type-safe string literals. The derived enum types enable compile-time safety when referencing queue names and job types across the codebase.

server/test/queues/alchemyQueue.test.ts (2)

39-66: LGTM!

Comprehensive test for the happy path. Correctly verifies:

  • The PATCH request to Alchemy's webhook API
  • Request payload structure with webhook_id, addresses_to_add, and addresses_to_remove
  • Sentry span creation for observability

68-89: LGTM!

Good coverage of error scenarios:

  • Unknown job names are properly rejected with descriptive errors
  • API failures propagate the status code and response text, enabling proper retry handling by BullMQ
server/utils/createCredential.ts (1)

6-7: LGTM on Sentry import split.

Appropriately uses @sentry/node for captureException in server-side code while keeping setUser from core.

server/queues/alchemyQueue.ts (1)

42-69: Processor implementation is well-structured.

The processor correctly uses Sentry tracing, handles the job type via switch statement, and properly throws on API errors or unknown job types. The span status is appropriately set on failure.

@aguxez aguxez force-pushed the feature/webhook-queue branch from 2d0f995 to 4276511 Compare January 14, 2026 13:30
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: 1

🤖 Fix all issues with AI agents
In `@server/test/utils/createCredential.test.ts`:
- Around line 59-81: Add a test that simulates mockContext.state.webhookId being
undefined and calls createCredential( mockContext, credentialId ); assert that
alchemyQueue.add is not called (spy/expect not to have been called) and that
captureException is not invoked; reference the createCredential function, the
alchemyQueue.add method, and the webhookId field in mockContext.state to locate
where to set up the scenario and verify behavior.
♻️ Duplicate comments (12)
server/queues/alchemyQueue.ts (5)

9-9: Direct process.env access violates coding guidelines.

Per guidelines: "Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context."

This has been flagged in previous reviews. Consider refactoring to accept configuration via dependency injection.


22-27: Redis configuration uses direct process.env access.

Same issue as line 9. The Redis connection parameters should be loaded from a centralized configuration module and passed via dependency injection.


10-10: Add documentation for OpenTelemetry status code constant.

The magic number is correctly extracted to a constant, but it would benefit from a brief comment explaining its meaning.

📝 Suggested documentation
-const SENTRY_SPAN_ERROR_CODE = 2;
+// OpenTelemetry STATUS_CODE_ERROR (https://opentelemetry.io/docs/specs/otel/trace/api/#set-status)
+const SENTRY_SPAN_ERROR_CODE = 2;

48-48: Prefer full variable name over abbreviation.

Per coding guidelines: "Do not use abbreviations or cryptic names." Keep webhookId instead of renaming to hookId.


73-101: Store Worker reference for graceful shutdown.

The Worker instance is created but not assigned to a variable, preventing graceful shutdown via worker.close() during application termination.

♻️ Suggested fix
+export let alchemyWorker: Worker<AlchemyJobData> | undefined;
+
 if (!disableWorkers) {
-  new Worker(QueueName.ALCHEMY, processor, {
+  alchemyWorker = new Worker(QueueName.ALCHEMY, processor, {
     connection,
     limiter: { max: 10, duration: 1000 },
     // cspell:ignore autorun
     autorun: true,
   })

Then call alchemyWorker?.close() during shutdown.

.changeset/short-cars-return.md (1)

5-5: Missing gitmoji prefix and redundant scope in changeset summary.

Per repository conventions, changeset summaries should follow <emoji> <message> format. The scope is already indicated by the package name on line 2.

📝 Proposed fix
-server: add queue on user creation
+✨ add queue on user creation
server/test/queues/alchemyQueue.test.ts (1)

54-54: Remove unnecessary type assertion.

The as Record<string, string> cast is redundant—expect.objectContaining() handles matcher types correctly in Vitest.

♻️ Proposed fix
-        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) as Record<string, string>,
+        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }),
server/test/utils/createCredential.test.ts (3)

23-25: Incomplete mock signature for setSignedCookie.

The mock only accepts (c, name, value) but the actual usage passes five arguments including authSecret and an options object. Update the signature for type safety.

♻️ Proposed fix
 vi.mock("hono/cookie", () => ({
-  setSignedCookie: vi.fn<(c: Context, name: string, value: string) => Promise<void>>(),
+  setSignedCookie: vi.fn<(c: Context, name: string, value: string, secret: string, options?: object) => Promise<void>>(),
 }));

33-35: Fix the mock signature for identify.

The mock types identify as (userId: string) => void, but createCredential.ts calls identify({ userId: account }) with an object. This mismatch could allow incorrect calls to pass tests.

🐛 Proposed fix
 vi.mock("../../utils/segment", () => ({
-  identify: vi.fn<(userId: string) => void>(),
+  identify: vi.fn<(payload: { userId: string }) => void>(),
 }));

64-70: Simplify mocked queue assertion and remove unnecessary type cast.

The double cast (alchemyQueue as unknown as { add: Mock }).add is verbose—use vi.mocked() for consistency with line 80. Also, as string on line 67 is redundant since expect.stringMatching() returns the correct matcher type.

♻️ Proposed fix
-    expect((alchemyQueue as unknown as { add: Mock }).add).toHaveBeenCalledWith(
+    expect(vi.mocked(alchemyQueue.add)).toHaveBeenCalledWith(
       AlchemyJob.ADD_SUBSCRIBER,
       expect.objectContaining({
-        account: expect.stringMatching(/^0x/) as string,
+        account: expect.stringMatching(/^0x/),
         webhookId: "webhook-id",
       }),
     );
server/utils/createCredential.ts (2)

55-55: Guard against undefined webhookId before enqueuing.

The webhookId is assigned at module load from server/hooks/activity.ts and may be undefined if ALCHEMY_ACTIVITY_ID is not set or initialization hasn't completed. Enqueuing a job with webhookId: undefined will cause the processor to fail silently.

As per coding guidelines, throw HTTPException for expected errors:

🐛 Proposed fix
+import { HTTPException } from "hono/http-exception";
+
 // Inside createCredential, before the Promise.all:
+  if (!webhookId) throw new HTTPException(500, { message: "alchemy webhook not configured" });
   await Promise.all([
     setSignedCookie(c, "credential_id", credentialId, authSecret, {

Alternatively, validate ALCHEMY_ACTIVITY_ID as a required environment variable at startup to fail fast.


55-58: Inconsistent error capture options.

Line 55 passes captureException directly without options, while line 57 uses { level: "error" }. For consistent error reporting and easier debugging, consider adding context to the queue error.

♻️ Suggested improvement
-    alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }).catch(captureException),
+    alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId }).catch((error: unknown) =>
+      captureException(error, { level: "error" }),
+    ),
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2d0f995 and 4276511.

📒 Files selected for processing (7)
  • .changeset/short-cars-return.md
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/utils/createCredential.test.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/test/queues/alchemyQueue.test.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (17)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/queues/alchemyQueue.ts
  • server/test/queues/alchemyQueue.test.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Node.js best practices using ESLint `plugin:n/recommended` configuration

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/api/**/*.ts : Never instantiate or directly access a database client from within a route handler; it must be provided via context

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Hono's built-in error handling by throwing `new HTTPException()` for expected errors; unhandled errors will be caught and logged automatically

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commit messages must strictly adhere to the format: `<emoji> <scope>: <message>` where emoji is a gitmoji unicode character, scope is a mandatory short name (app, server, contracts, docs, common, dependencies, github, config, or repo), and message is a short lowercase description

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use gitmoji format for commits: `<emoji> <scope>: <message>` with scopes: `app`, `server`, `contracts`, `common`, `docs`

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries

Applied to files:

  • .changeset/short-cars-return.md
🧬 Code graph analysis (3)
server/test/utils/createCredential.test.ts (3)
server/utils/createCredential.ts (1)
  • createCredential (24-62)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (34-34)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (15-20)
  • processor (42-69)
🔇 Additional comments (6)
server/queues/constants.ts (1)

1-11: LGTM!

Clean implementation using the as const pattern for type-safe constants. The naming follows conventions and the types are correctly derived.

server/queues/alchemyQueue.ts (1)

42-69: Processor implementation is well-structured.

The switch statement correctly handles the ADD_SUBSCRIBER case and throws for unknown job names (addressing previous concerns about silent failures). Error handling for API failures is also properly implemented.

server/script/openapi.ts (1)

28-29: LGTM!

The REDIS_URL now uses proper URL format, and DISABLE_WORKERS=true correctly prevents Worker initialization during OpenAPI spec generation (avoiding Redis connection attempts in the build context).

server/test/queues/alchemyQueue.test.ts (1)

34-90: Good test coverage for the processor.

The test suite covers the essential scenarios:

  1. Successful webhook address update with correct API call verification
  2. Error handling for unknown job names
  3. Error handling for API failures

The mock setup is appropriate and vi.clearAllMocks() ensures test isolation.

server/utils/createCredential.ts (2)

6-7: LGTM!

The import reorganization correctly separates setUser from @sentry/core and captureException from @sentry/node. The new queue-related imports follow the relative path convention per coding guidelines.

Also applies to: 14-14, 21-22


37-46: LGTM!

The database insert uses Drizzle ORM query builder as required by coding guidelines, with proper async/await handling.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@aguxez aguxez force-pushed the feature/webhook-queue branch 2 times, most recently from b0a759e to 05d805c Compare January 15, 2026 22:53
@aguxez aguxez force-pushed the feature/webhook-queue branch from 05d805c to bc4a358 Compare January 15, 2026 23:06
@aguxez aguxez force-pushed the feature/webhook-queue branch from bc4a358 to 8b4a018 Compare January 15, 2026 23:15
@aguxez aguxez force-pushed the feature/webhook-queue branch 3 times, most recently from 028ca68 to f56af9b Compare January 16, 2026 15:44
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/alchemyQueue.ts`:
- Around line 12-19: The JSDoc comments for the AlchemyJobData interface use
sentence-case; update them to follow the project's lowercase comment style by
changing phrases like "The likely Ethereum address of the account to subscribe."
and "The Alchemy webhook ID to update." to fully lowercase variants (and do the
same for the comments at lines 69-70) so the interface name AlchemyJobData and
surrounding doc comments conform to the lowercase comment guideline.
- Around line 38-65: The processor is currently an async arrow function; per
style guide, convert it to a named async function declaration (e.g., async
function processor(job: Job<AlchemyJobData>) { ... }) while preserving the
existing logic inside the startSpan call and all references to
AlchemyJob.ADD_SUBSCRIBER, startSpan, span.setStatus, and thrown Errors; ensure
exports remain the same (export const processor -> export { processor } or
export function processor) and keep types/signatures and behavior unchanged.

In `@server/test/utils/createCredential.test.ts`:
- Around line 93-98: Remove the unnecessary type assertion in the test
expectation: in the assertion using expect.objectContaining({ level: "error",
extra: expect.objectContaining({ job: AlchemyJob.ADD_SUBSCRIBER, webhookId:
"webhook-id", }) as Record<string, unknown>, }) remove the trailing "as
Record<string, unknown>" so the matcher uses its own inferred type; update the
line in createCredential.test.ts accordingly and run the tests to confirm
nothing else needs adjusting.
♻️ Duplicate comments (5)
server/test/queues/alchemyQueue.test.ts (2)

1-6: Ensure Redis isn’t touched during unit tests. Importing alchemyQueue constructs a Redis connection immediately; set DISABLE_WORKERS (and a dummy REDIS_URL) before the import or mock the module to avoid CI failures.
As per coding guidelines.

Please verify test setup with:

#!/bin/bash
# Check whether DISABLE_WORKERS/REDIS_URL are set in test setup or configs
rg -n "DISABLE_WORKERS|REDIS_URL" vitest.config.* server/test

49-54: Remove redundant Record<string, string> cast. The matcher already satisfies the type.
As per coding guidelines.

♻️ Proposed simplification
-        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) as Record<string, string>,
+        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }),
server/test/utils/createCredential.test.ts (1)

75-80: Drop the redundant matcher cast. expect.stringMatching already yields a valid matcher type here.
As per coding guidelines.

♻️ Proposed simplification
-        account: expect.stringMatching(/^0x/) as string,
+        account: expect.stringMatching(/^0x/),
server/queues/alchemyQueue.ts (2)

9-24: Avoid direct process.env access in application modules. Load configuration once at startup and inject it into this module instead of reading DISABLE_WORKERS and REDIS_URL here.
As per coding guidelines.


22-24: Producer calls may hang with maxRetriesPerRequest: null. This can block user-facing requests when Redis is unavailable; consider a bounded retry strategy for producer connections (or separate producer/worker connections).

BullMQ ioredis maxRetriesPerRequest recommendation for producers vs workers
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0a759e and f56af9b.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • .changeset/short-cars-return.md
  • server/index.ts
  • server/package.json
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/test/utils/pax.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/test/utils/pax.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/test/utils/pax.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/test/utils/pax.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/test/utils/pax.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/utils/createCredential.test.ts
  • server/script/openapi.ts
  • server/test/utils/pax.test.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (19)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use gitmoji format for commits: `<emoji> <scope>: <message>` with scopes: `app`, `server`, `contracts`, `common`, `docs`

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commit messages must strictly adhere to the format: `<emoji> <scope>: <message>` where emoji is a gitmoji unicode character, scope is a mandatory short name (app, server, contracts, docs, common, dependencies, github, config, or repo), and message is a short lowercase description

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/script/openapi.ts
  • server/queues/alchemyQueue.ts
  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
📚 Learning: 2026-01-15T19:10:07.248Z
Learnt from: mainqueg
Repo: exactly/exa PR: 637
File: server/utils/persona.ts:155-160
Timestamp: 2026-01-15T19:10:07.248Z
Learning: In server/utils/persona.ts, the PERSONA_API_VERSION constant is intentionally set to "2023-01-05" to match the API version configured for the production API key in Persona's admin dashboard. This ensures consistent response behavior across different environments and should not be updated without also updating the production API key configuration.

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
  • server/package.json
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL

Applied to files:

  • server/package.json
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/queues/alchemyQueue.ts
  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Node.js best practices using ESLint `plugin:n/recommended` configuration

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/api/**/*.ts : Never instantiate or directly access a database client from within a route handler; it must be provided via context

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2026-01-15T19:06:40.947Z
Learnt from: mainqueg
Repo: exactly/exa PR: 637
File: server/hooks/persona.ts:186-195
Timestamp: 2026-01-15T19:06:40.947Z
Learning: In server/hooks/persona.ts, the webhook handler has intentionally different error handling for Manteca vs Panda flows: the Manteca path (payload.template === "manteca") lets addDocument errors propagate to enable webhook retries, while the Panda path uses .catch() to swallow addDocument errors and prevent retries since the user creation is already committed.

Applied to files:

  • server/utils/createCredential.ts
  • server/index.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Hono's built-in error handling by throwing `new HTTPException()` for expected errors; unhandled errors will be caught and logged automatically

Applied to files:

  • server/utils/createCredential.ts
🧬 Code graph analysis (5)
server/test/utils/createCredential.test.ts (2)
server/utils/createCredential.ts (1)
  • createCredential (24-68)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (5)
server/test/utils/createCredential.test.ts (1)
  • webhookId (46-48)
server/hooks/activity.ts (1)
  • webhookId (48-48)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (30-30)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/e2e.ts (1)
  • captureException (58-61)
server/index.ts (1)
server/queues/alchemyQueue.ts (1)
  • alchemyWorker (67-67)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (15-20)
  • processor (38-65)
🔇 Additional comments (9)
server/script/openapi.ts (1)

28-29: LGTM!

The environment configuration changes are appropriate for OpenAPI generation:

  • REDIS_URL now uses a valid URL format matching the queue system's expectations.
  • DISABLE_WORKERS prevents background workers from starting during spec generation.
.changeset/short-cars-return.md (1)

1-5: LGTM!

The changeset follows the required format with the gitmoji prefix and lowercase imperative summary.

server/test/utils/pax.test.ts (1)

10-10: LGTM!

Using globalThis instead of global is the correct modern approach for accessing the global object, as it's the ECMAScript standard and works consistently across environments.

server/queues/constants.ts (1)

1-11: LGTM!

The constants follow TypeScript best practices:

  • as const preserves literal types for type safety.
  • Derived union types (QueueNameEnum, AlchemyJobEnum) are extensible when new values are added.
  • Pattern is preferable to TypeScript enums for tree-shaking and type inference.
server/package.json (1)

54-54: Document the reason for pinning ioredis to 5.9.1.

The version was pinned to exactly 5.9.1, which is 7 days older than the current latest 5.9.2 (released Jan 15, 2026). If this downgrade is intentional (e.g., due to a regression in 5.9.2), please add a comment explaining the reason. If unintentional, consider updating to the latest version.

server/index.ts (2)

18-18: Import looks good. The added queue worker import is appropriate for the updated shutdown flow.


288-293: Graceful shutdown now covers the worker. Awaiting alchemyWorker?.close() alongside other services is the right lifecycle behavior.

server/utils/createCredential.ts (2)

6-22: Imports align with the queue-based flow. Using captureException from @sentry/node and queue modules is consistent with the new design.


55-60: Queue enqueue is well-guarded and contextualized. The conditional plus enriched captureException metadata is solid.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@aguxez aguxez force-pushed the feature/webhook-queue branch from f56af9b to 222172e Compare January 16, 2026 16:15
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/package.json`:
- Line 54: The package.json change pins "ioredis" to 5.9.1 which unnecessarily
downgrades and removes semver flexibility; either revert this dependency to the
previous flexible version (e.g., change "ioredis": "5.9.1" back to "ioredis":
"^5.9.2") or, if the pin is intentional, add a clear comment in package.json or
the PR explaining the exact reason for pinning to 5.9.1 and why ioredis-mock
compatibility was insufficient; locate the "ioredis" entry in package.json to
make the change or add the explanatory comment.

In `@server/test/queues/alchemyQueue.test.ts`:
- Line 8: Change the comment "// Mock dependencies" to lowercase to follow the
style guide: replace it with "mock dependencies" in the alchemyQueue.test.ts
file (look for the top-of-file comment string "// Mock dependencies") so the
test file's comment text uses lowercase styling.

In `@server/test/utils/createCredential.test.ts`:
- Line 13: Replace the comment string "// Mock dependencies" with a lowercase
version "// mock dependencies" wherever it appears in the test (both occurrences
flagged in the review) so the comment text matches the project's style guide;
search for the exact comment token and update it to the lowercase form.
♻️ Duplicate comments (4)
server/test/utils/createCredential.test.ts (1)

75-81: Drop redundant matcher type assertions.
Vitest matchers already satisfy the expected types without casts.

♻️ Proposed fix
-        account: expect.stringMatching(/^0x/) as string,
+        account: expect.stringMatching(/^0x/),
-        }) as Record<string, unknown>,
+        }),

Also applies to: 91-99

server/test/queues/alchemyQueue.test.ts (1)

49-54: Remove unnecessary matcher type assertion.
Vitest’s matcher typing already covers this.

♻️ Proposed fix
-        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) as Record<string, string>,
+        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }),
server/queues/alchemyQueue.ts (2)

9-23: Avoid direct process.env reads in server modules.
Load configuration once at startup and inject it into this module (disableWorkers and Redis connection info) rather than reading environment variables here. As per coding guidelines.


23-30: Bound Redis retries for producer calls to avoid request hangs.
alchemyQueue.add() is invoked from user-facing flows; with maxRetriesPerRequest: null, Redis outages can stall those requests indefinitely. Consider separate connections (bounded retries for producer, unlimited for worker) or a small retry cap.

BullMQ / ioredis: recommended maxRetriesPerRequest values for producers vs workers
📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f56af9b and 222172e.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (10)
  • .changeset/short-cars-return.md
  • server/index.ts
  • server/package.json
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
  • server/script/openapi.ts
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
🧠 Learnings (24)
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/index.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
📚 Learning: 2026-01-15T19:06:40.947Z
Learnt from: mainqueg
Repo: exactly/exa PR: 637
File: server/hooks/persona.ts:186-195
Timestamp: 2026-01-15T19:06:40.947Z
Learning: In server/hooks/persona.ts, the webhook handler has intentionally different error handling for Manteca vs Panda flows: the Manteca path (payload.template === "manteca") lets addDocument errors propagate to enable webhook retries, while the Panda path uses .catch() to swallow addDocument errors and prevent retries since the user creation is already committed.

Applied to files:

  • server/index.ts
  • server/utils/createCredential.ts
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use gitmoji format for commits: `<emoji> <scope>: <message>` with scopes: `app`, `server`, `contracts`, `common`, `docs`

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commit messages must strictly adhere to the format: `<emoji> <scope>: <message>` where emoji is a gitmoji unicode character, scope is a mandatory short name (app, server, contracts, docs, common, dependencies, github, config, or repo), and message is a short lowercase description

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Node.js best practices using ESLint `plugin:n/recommended` configuration

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/api/**/*.ts : Never instantiate or directly access a database client from within a route handler; it must be provided via context

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : All code comments must be lowercase; special tags (TODO, FIXME) are uppercase followed by a single space and lowercase comment, without colons or dashes, and should include a ticket/issue reference when available

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:22.339Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/markdown.mdc:0-0
Timestamp: 2025-12-23T19:57:22.339Z
Learning: Applies to **/*.md : All code comments must be lowercase

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Prefer self-documenting code; avoid comments explaining 'what' the code does; comments should explain 'why' code is written a certain way

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/queues/alchemyQueue.ts
  • server/utils/createCredential.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx} : Use PascalCase for TypeScript types and interfaces

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Hono's built-in error handling by throwing `new HTTPException()` for expected errors; unhandled errors will be caught and logged automatically

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/package.json
  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:56:56.405Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/dev.mdc:0-0
Timestamp: 2025-12-23T19:56:56.405Z
Learning: Server (exactly/server) uses Hono (framework), Node.js, Drizzle ORM (database), and PostgreSQL

Applied to files:

  • server/package.json
📚 Learning: 2026-01-15T19:10:07.248Z
Learnt from: mainqueg
Repo: exactly/exa PR: 637
File: server/utils/persona.ts:155-160
Timestamp: 2026-01-15T19:10:07.248Z
Learning: In server/utils/persona.ts, the PERSONA_API_VERSION constant is intentionally set to "2023-01-05" to match the API version configured for the production API key in Persona's admin dashboard. This ensures consistent response behavior across different environments and should not be updated without also updating the production API key configuration.

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
🧬 Code graph analysis (5)
server/index.ts (1)
server/queues/alchemyQueue.ts (1)
  • alchemyWorker (67-67)
server/test/utils/createCredential.test.ts (2)
server/utils/createCredential.ts (1)
  • createCredential (24-68)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/queues/alchemyQueue.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (2)
  • AlchemyJobData (15-20)
  • processor (38-65)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
server/utils/createCredential.ts (5)
server/test/utils/createCredential.test.ts (1)
  • webhookId (46-48)
server/hooks/activity.ts (1)
  • webhookId (48-48)
server/queues/alchemyQueue.ts (1)
  • alchemyQueue (30-30)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/test/e2e.ts (1)
  • captureException (58-61)
🔇 Additional comments (10)
server/script/openapi.ts (1)

28-29: LGTM!

The REDIS_URL now uses proper URL format for local development, and DISABLE_WORKERS="true" correctly prevents worker initialization during OpenAPI spec generation. Both changes align with the new queue infrastructure.

server/index.ts (2)

18-18: LGTM!

Import correctly added for the new queue worker.


288-293: LGTM!

The shutdown sequence correctly includes alchemyWorker?.close() with proper handling for when the worker is undefined. Using Promise.allSettled ensures all cleanup tasks are attempted even if some fail.

.changeset/short-cars-return.md (1)

1-5: LGTM!

Changeset follows the required format with gitmoji prefix and lowercase imperative message. Patch level is appropriate for this feature addition.

server/test/utils/pax.test.ts (3)

10-10: LGTM!

Using globalThis instead of global is the correct ECMAScript standard for accessing the global object, improving cross-environment consistency.


66-66: Consistent with the beforeEach mock.


101-101: Consistent with the beforeEach mock.

server/queues/constants.ts (1)

1-11: Centralized queue/job constants look good.
Clear single source of truth for queue/job names.

server/utils/createCredential.ts (1)

6-22: Queue-based enqueue + contextual error capture looks solid.

Also applies to: 55-61

server/queues/alchemyQueue.ts (1)

38-61: Fail-fast processor behavior is good.
Explicitly throwing on non-OK API responses and unknown job names prevents silent failures.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

import { type AlchemyJobData, processor } from "../../queues/alchemyQueue";
import { AlchemyJob } from "../../queues/constants";

// Mock dependencies
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

Use lowercase comment text to match the style guide.
As per coding guidelines.

♻️ Proposed fix
-// Mock dependencies
+// mock dependencies
📝 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
// Mock dependencies
// mock dependencies
🤖 Prompt for AI Agents
In `@server/test/queues/alchemyQueue.test.ts` at line 8, Change the comment "//
Mock dependencies" to lowercase to follow the style guide: replace it with "mock
dependencies" in the alchemyQueue.test.ts file (look for the top-of-file comment
string "// Mock dependencies") so the test file's comment text uses lowercase
styling.

addJob: vi.fn().mockResolvedValue({}),
}));

// Mock dependencies
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

Use lowercase comment text to match the style guide.
As per coding guidelines.

♻️ Proposed fix
-// Mock dependencies
+// mock dependencies
-// Mock global fetch to avoid actual network calls
+// mock global fetch to avoid actual network calls

Also applies to: 55-55

🤖 Prompt for AI Agents
In `@server/test/utils/createCredential.test.ts` at line 13, Replace the comment
string "// Mock dependencies" with a lowercase version "// mock dependencies"
wherever it appears in the test (both occurrences flagged in the review) so the
comment text matches the project's style guide; search for the exact comment
token and update it to the lowercase form.

@aguxez aguxez force-pushed the feature/webhook-queue branch from 222172e to 76d5baa Compare January 16, 2026 16:56
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: 1

🤖 Fix all issues with AI agents
In `@server/script/openapi.ts`:
- Around line 28-29: Replace the direct assignments to process.env.REDIS_URL and
process.env.DISABLE_WORKERS in server/script/openapi.ts by reading those values
from the centralized configuration/bootstrap and passing them into the OpenAPI
script via dependency injection or a specific OpenAPI script config override;
remove the two lines setting process.env.REDIS_URL and
process.env.DISABLE_WORKERS, add parameters or a config object (sourced from
your existing bootstrap/config module) to the OpenAPI entry point so the script
uses config.REDIS_URL and config.DISABLE_WORKERS (or equivalent) instead of
mutating process.env directly.
♻️ Duplicate comments (6)
server/test/queues/alchemyQueue.test.ts (2)

8-8: Use lowercase comment text.

Per coding guidelines, all code comments must be lowercase.

-// Mock dependencies
+// mock dependencies

53-53: Remove unnecessary type assertion.

The as Record<string, string> cast is redundant—expect.objectContaining() handles matcher types correctly in Vitest.

♻️ Proposed fix
-        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) as Record<string, string>,
+        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }),
server/test/utils/createCredential.test.ts (3)

13-13: Use lowercase comment text.

Per coding guidelines, all code comments must be lowercase.

-// Mock dependencies
+// mock dependencies
-// Mock global fetch to avoid actual network calls
+// mock global fetch to avoid actual network calls

Also applies to: 55-55


78-78: Remove unnecessary type assertion.

The as string cast is redundant—expect.stringMatching() returns the correct matcher type for Vitest.

♻️ Proposed fix
-        account: expect.stringMatching(/^0x/) as string,
+        account: expect.stringMatching(/^0x/),

98-98: Remove unnecessary type assertion.

The as Record<string, unknown> cast is redundant.

♻️ Proposed fix
-        }) as Record<string, unknown>,
+        }),
server/queues/alchemyQueue.ts (1)

9-9: Direct process.env access violates coding guidelines.

Per guidelines: "Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context."

Both DISABLE_WORKERS and REDIS_URL should be loaded from a centralized configuration module. Based on learnings, configuration should be loaded once at startup.

Also applies to: 22-23

📜 Review details

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 222172e and 76d5baa.

📒 Files selected for processing (9)
  • .changeset/short-cars-return.md
  • server/index.ts
  • server/queues/alchemyQueue.ts
  • server/queues/constants.ts
  • server/script/openapi.ts
  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/utils/createCredential.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/.changeset/*.md

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

Use a lowercase sentence in the imperative present tense for changeset summaries

Files:

  • .changeset/short-cars-return.md
server/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/server.mdc)

server/**/*.ts: Use c.var object to pass strongly-typed data between Hono middleware and route handlers; do not use c.set
All request validation (headers, body, params) must be handled by @hono/valibot-validator middleware; do not perform manual validation inside route handlers
Use Hono's built-in error handling by throwing new HTTPException() for expected errors; unhandled errors will be caught and logged automatically
Enforce Node.js best practices using ESLint plugin:n/recommended configuration
Enforce Drizzle ORM best practices using ESLint plugin:drizzle/all configuration, including requiring where clauses for update and delete operations
Use Drizzle ORM query builder for all database interactions; do not write raw SQL queries unless absolutely unavoidable
All authentication and authorization logic must be implemented in Hono middleware
Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context
Avoid long-running, synchronous operations; use async/await correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
**/*.{js,ts,tsx,jsx,sol}

📄 CodeRabbit inference engine (AGENTS.md)

Follow linter/formatter (eslint, prettier, solhint) strictly with high strictness level. No any type.

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Omit redundant type names in variable declarations - let the type system explain itself

**/*.{ts,tsx}: Use PascalCase for TypeScript types and interfaces
Use valibot for all runtime validation of API inputs, environment variables, and other data; define schemas once and reuse them
Infer TypeScript types from valibot schemas using type User = v.Input<typeof UserSchema> instead of manually defining interfaces

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,js,jsx}: Omit contextual names - don't repeat class/module names in members
Omit meaningless words like 'data', 'state', 'manager', 'engine', 'value' from variable and function names unless they add disambiguation

**/*.{ts,tsx,js,jsx}: Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations
Prefer const for all variable declarations by default; only use let if the variable's value will be reassigned
Declare each variable on its own line with its own const or let keyword, not multiple declarations on one line
Use camelCase for TypeScript variables and functions
Always use import type { ... } for type imports
Use relative paths for all imports within the project; avoid tsconfig path aliases
Follow eslint-plugin-import order: react, external libraries, then relative paths
Use object and array destructuring to access and use properties
Use object method shorthand syntax when a function is a property of an object
Prefer optional chaining (?.), nullish coalescing (??), object and array spreading (...), and for...of loops over traditional syntax
Do not use abbreviations or cryptic names; write out full words like error, parameters, request instead of err, params, req
Use Number.parseInt() instead of the global parseInt() function when parsing numbers
All classes called with new must use PascalCase
Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe() instead of the deprecated new Buffer()
Use @ts-expect-error instead of @ts-ignore; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like - or :
Do not include the type in a variable's name; let the static type system do its job (e.g., use const user: User not const userObject: User)
Do not repeat the name of a class or module within its members; omit contextual names (e.g., use `class User { getProfil...

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

server/**/*.{ts,tsx}: Server API: implement schema-first approach using OpenAPI via hono with validation via valibot middleware
Server database: drizzle schema is source of truth. Migrations required. No direct database access in handlers - use c.var.db

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/style.mdc)

For files with a single default export, name the file identically to the export; for files with multiple exports, use camelCase with a strong preference for a single word

Files:

  • server/test/queues/alchemyQueue.test.ts
  • server/queues/constants.ts
  • server/index.ts
  • server/test/utils/createCredential.test.ts
  • server/test/utils/pax.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
🧠 Learnings (23)
📚 Learning: 2025-12-31T00:23:55.034Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 610
File: .changeset/ready-experts-fly.md:1-2
Timestamp: 2025-12-31T00:23:55.034Z
Learning: In the exactly/exa repository, allow and require empty changeset files (containing only --- separators) when changes are not user-facing and do not warrant a version bump. This is needed because CI runs changeset status --since origin/main and requires a changeset file to exist. Ensure such empty changesets are used only for non-user-facing changes and document the rationale in the commit or changelog notes.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2026-01-09T16:24:11.460Z
Learnt from: cruzdanilo
Repo: exactly/exa PR: 627
File: .changeset/breezy-planes-repeat.md:5-5
Timestamp: 2026-01-09T16:24:11.460Z
Learning: In the exactly/exa repository, changeset summaries in .changeset/*.md must start with a gitmoji and follow the format '<emoji> <message>' (no scope). This should mirror the commit message format '<emoji> <scope>: <message>' but omit the '<scope>:' portion. Ensure the message is clear and describes the change.

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: Select the most precise gitmoji that best communicates the intent of the change (e.g., ✨ for new feature, 🐛 for bug fix, ♻️ for refactoring, ⚡️ for performance improvement, 📝 for documentation, 🚀 for deployment)

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Use gitmoji format for commits: `<emoji> <scope>: <message>` with scopes: `app`, `server`, `contracts`, `common`, `docs`

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commit messages must strictly adhere to the format: `<emoji> <scope>: <message>` where emoji is a gitmoji unicode character, scope is a mandatory short name (app, server, contracts, docs, common, dependencies, github, config, or repo), and message is a short lowercase description

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:14.465Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/git.mdc:0-0
Timestamp: 2025-12-23T19:57:14.465Z
Learning: All commits must start with a single gitmoji from the official gitmoji list; this is not optional and the emoji visually communicates the intent of the change

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/.changeset/*.md : Use a lowercase sentence in the imperative present tense for changeset summaries

Applied to files:

  • .changeset/short-cars-return.md
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Do not access `process.env` directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/index.ts
  • server/queues/alchemyQueue.ts
  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Avoid long-running, synchronous operations; use `async/await` correctly and be mindful of CPU-intensive tasks to prevent blocking the event loop

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/index.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : All code comments must be lowercase; special tags (TODO, FIXME) are uppercase followed by a single space and lowercase comment, without colons or dashes, and should include a ticket/issue reference when available

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:22.339Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/markdown.mdc:0-0
Timestamp: 2025-12-23T19:57:22.339Z
Learning: Applies to **/*.md : All code comments must be lowercase

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `ts-expect-error` instead of `ts-ignore`; follow it immediately with a single-line lowercase comment explaining why the error is expected, without separators like `-` or `:`

Applied to files:

  • server/test/queues/alchemyQueue.test.ts
  • server/test/utils/createCredential.test.ts
  • server/utils/createCredential.ts
  • server/queues/alchemyQueue.ts
📚 Learning: 2026-01-15T19:06:40.947Z
Learnt from: mainqueg
Repo: exactly/exa PR: 637
File: server/hooks/persona.ts:186-195
Timestamp: 2026-01-15T19:06:40.947Z
Learning: In server/hooks/persona.ts, the webhook handler has intentionally different error handling for Manteca vs Panda flows: the Manteca path (payload.template === "manteca") lets addDocument errors propagate to enable webhook retries, while the Panda path uses .catch() to swallow addDocument errors and prevent retries since the user creation is already committed.

Applied to files:

  • server/index.ts
  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Use Hono's built-in error handling by throwing `new HTTPException()` for expected errors; unhandled errors will be caught and logged automatically

Applied to files:

  • server/utils/createCredential.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/**/*.ts : Enforce Node.js best practices using ESLint `plugin:n/recommended` configuration

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-23T19:57:35.503Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/server.mdc:0-0
Timestamp: 2025-12-23T19:57:35.503Z
Learning: Applies to server/api/**/*.ts : Never instantiate or directly access a database client from within a route handler; it must be provided via context

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Do not use abbreviations or cryptic names; write out full words like `error`, `parameters`, `request` instead of `err`, `params`, `req`

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Prefer self-documenting code; avoid comments explaining 'what' the code does; comments should explain 'why' code is written a certain way

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx} : Use PascalCase for TypeScript types and interfaces

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2025-12-30T15:03:28.449Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: .cursor/rules/style.mdc:0-0
Timestamp: 2025-12-30T15:03:28.449Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Prefer function declarations for all multi-line functions; use function expressions or arrow functions only for single-line implementations

Applied to files:

  • server/queues/alchemyQueue.ts
📚 Learning: 2026-01-15T19:10:07.248Z
Learnt from: mainqueg
Repo: exactly/exa PR: 637
File: server/utils/persona.ts:155-160
Timestamp: 2026-01-15T19:10:07.248Z
Learning: In server/utils/persona.ts, the PERSONA_API_VERSION constant is intentionally set to "2023-01-05" to match the API version configured for the production API key in Persona's admin dashboard. This ensures consistent response behavior across different environments and should not be updated without also updating the production API key configuration.

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Zero config local dev environment: no `.env` files, mock all external services

Applied to files:

  • server/script/openapi.ts
📚 Learning: 2025-12-23T19:58:16.574Z
Learnt from: CR
Repo: exactly/exa PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-23T19:58:16.574Z
Learning: Server stack: use hono, node.js, drizzle orm, postgres

Applied to files:

  • server/script/openapi.ts
🧬 Code graph analysis (3)
server/index.ts (1)
server/queues/alchemyQueue.ts (1)
  • alchemyWorker (67-67)
server/test/utils/createCredential.test.ts (3)
.maestro/maestro.d.ts (1)
  • Response (19-19)
server/utils/createCredential.ts (1)
  • createCredential (24-68)
server/queues/constants.ts (1)
  • AlchemyJob (7-9)
server/queues/alchemyQueue.ts (1)
server/queues/constants.ts (2)
  • QueueName (1-3)
  • AlchemyJob (7-9)
🪛 GitHub Actions: test
server/queues/alchemyQueue.ts

[error] 30-30: Type 'Redis' is not assignable to type 'ConnectionOptions'. (TS2322) during 'nx run @exactly/mobile:"test:ts"' (tsc)

🪛 GitHub Check: test
server/queues/alchemyQueue.ts

[failure] 73-73:
Type 'Redis' is not assignable to type 'ConnectionOptions'.


[failure] 30-30:
Type 'Redis' is not assignable to type 'ConnectionOptions'.

🔇 Additional comments (10)
.changeset/short-cars-return.md (1)

1-5: LGTM.

Changeset format and summary look good.

server/index.ts (1)

18-18: Worker shutdown is correctly wired into the close path.

The optional close with a safe fallback is a solid addition to graceful shutdown.

Also applies to: 288-293

server/test/utils/pax.test.ts (1)

10-10: Switching to globalThis.fetch in tests looks good.

Also applies to: 66-66, 101-101

server/queues/constants.ts (1)

1-11: LGTM — clear, typed queue/job constants.

server/test/queues/alchemyQueue.test.ts (1)

33-89: LGTM! Good test coverage for the processor.

The test suite covers the critical paths:

  • Successful ADD_SUBSCRIBER job with correct API call verification
  • Error throwing for unknown job names
  • Error handling when Alchemy API fails
server/utils/createCredential.ts (1)

55-61: LGTM! Queue integration with proper guards.

The implementation correctly:

  • Guards against undefined webhookId with a ternary
  • Enqueues the job asynchronously without blocking credential creation
  • Captures exceptions with rich context (level, job type, account, webhookId)
server/test/utils/createCredential.test.ts (1)

103-111: LGTM! Good edge case coverage.

This test correctly verifies that when webhookId is undefined:

  • No job is added to the queue
  • No exception is captured

This aligns with the guarded behavior in createCredential.ts.

server/queues/alchemyQueue.ts (3)

71-101: LGTM! Worker lifecycle and error handling.

The worker implementation correctly:

  • Guards initialization with DISABLE_WORKERS flag
  • Stores the worker reference for graceful shutdown
  • Includes rate limiting (limiter: { max: 10, duration: 1000 })
  • Handles all essential events: failed, completed, active, error
  • Captures exceptions and breadcrumbs for observability

38-65: LGTM! Processor implementation with proper error handling.

The processor correctly:

  • Wraps execution in Sentry spans for tracing
  • Handles the ADD_SUBSCRIBER job type
  • Throws on unknown job names (fail-fast pattern)
  • Propagates API errors with status and response text

22-30: Remove incorrect type error claim; address direct process.env access instead.

The code correctly passes an ioredis instance to BullMQ's Queue constructor. BullMQ v5 explicitly accepts ioredis instances as the connection parameter, and maxRetriesPerRequest: null is a required setting for BullMQ worker clients. The proposed type fix is invalid and based on a misunderstanding of BullMQ's API.

The actual issue is direct process.env access at lines 9 and 22, which violates the coding guideline: "Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context." Move these to a centralized configuration module loaded at startup.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

server/index.ts Outdated
Comment on lines 290 to 295
Promise.allSettled([
closeSentry(),
closeSegment(),
database.$client.end(),
alchemyWorker?.close() ?? Promise.resolve(),
])

This comment was marked as outdated.

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: 1

🤖 Fix all issues with AI agents
In `@server/utils/createCredential.ts`:
- Around line 55-61: Update the test mock and imports so they match the
production usage of Sentry: in the test file createCredential.test.ts replace
the import of captureException from "@sentry/node" with an import from
"@sentry/core" and expand the vi.mock for "@sentry/core" to export both setUser
and captureException (e.g., setUser: vi.fn(), captureException: vi.fn()) so the
production call to captureException in createCredential.ts is properly stubbed
during tests.
♻️ Duplicate comments (8)
server/script/openapi.ts (1)

28-29: Avoid mutating process.env in this script; use centralized config/DI.
New env assignments should come from the existing config bootstrap and be passed into the OpenAPI generator rather than mutating globals here. Based on learnings, this should be moved out of server/**/*.ts application code.

server/test/utils/createCredential.test.ts (3)

13-13: Lowercase comment text to match style rules.
As per coding guidelines.

♻️ Proposed fix
-// Mock dependencies
+// mock dependencies
...
-// Mock global fetch to avoid actual network calls
+// mock global fetch to avoid actual network calls

Also applies to: 55-55


78-78: Drop redundant as string in matcher.
expect.stringMatching() already yields a compatible matcher type.

♻️ Proposed fix
-        account: expect.stringMatching(/^0x/) as string,
+        account: expect.stringMatching(/^0x/),

98-98: Remove unnecessary Record<string, unknown> cast.
The matcher type is sufficient without the assertion.

♻️ Proposed fix
-        }) as Record<string, unknown>,
+        }),
server/test/queues/alchemyQueue.test.ts (3)

1-6: Prevent Redis connections during tests.
Importing alchemyQueue at module load can create a Worker and attempt Redis connections (matches the EAI_AGAIN redis failure). Set DISABLE_WORKERS before the module is evaluated (e.g., in a Vitest setup file) or mock the BullMQ Worker so no real Redis connection is attempted.

Run this to confirm whether DISABLE_WORKERS is already set anywhere in test setup:

#!/bin/bash
rg -n "DISABLE_WORKERS"

8-8: Lowercase comment text to match style rules.
As per coding guidelines.

♻️ Proposed fix
-// Mock dependencies
+// mock dependencies

53-53: Remove redundant header type assertion.
expect.objectContaining() already handles the matcher type.

♻️ Proposed fix
-        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) as Record<string, string>,
+        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }),
server/queues/alchemyQueue.ts (1)

9-10: Direct process.env access violates coding guidelines.

Lines 9 and 22-23 directly access process.env.DISABLE_WORKERS and process.env.REDIS_URL. Per coding guidelines: "Do not access process.env directly in application code; load all configuration and secrets once at startup and pass them through dependency injection or context."

Additionally, the magic number 2 for SENTRY_SPAN_ERROR_CODE should be documented.

♻️ Suggested improvements
-const SENTRY_SPAN_ERROR_CODE = 2;
+// opentelemetry status code for error (see https://opentelemetry.io/docs/specs/otel/trace/api/#set-status)
+const SENTRY_SPAN_ERROR_CODE = 2;

For the process.env access, consider creating a factory function that accepts configuration:

interface AlchemyQueueConfig {
  disableWorkers: boolean;
  redisUrl: string;
}

export function initializeAlchemyQueue(config: AlchemyQueueConfig) {
  const connection = new Redis(config.redisUrl, { maxRetriesPerRequest: null });
  // ... rest of initialization
}

@aguxez aguxez force-pushed the feature/webhook-queue branch from b7b66fc to dbc2973 Compare January 19, 2026 16:49
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: 1

🤖 Fix all issues with AI agents
In `@server/utils/createCredential.ts`:
- Around line 55-61: The current conditional in createCredential.ts skips
enqueuing AlchemyJob.ADD_SUBSCRIBER when the module-level webhookId is still
undefined (set asynchronously in activity.ts), causing missed subscriptions
during startup; modify createCredential logic so that when webhookId is
undefined it does not silently drop the job but instead either (A) persist a
pending-subscription record (e.g., push the credential id/account into a
temporary pending list or DB table) and expose a function like
processPendingAlchemySubscriptions() that activity.ts calls once webhookId is
initialized to enqueue AlchemyJob.ADD_SUBSCRIBER for each pending item, or (B)
throw a retriable error to defer credential creation until webhookId is ready;
implement with references to webhookId,
alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER,...), and activity.ts initialization
to ensure missed ADD_SUBSCRIBER jobs are reconciled after webhookId is set.
♻️ Duplicate comments (8)
server/queues/alchemyQueue.ts (2)

22-23: Direct process.env.REDIS_URL access.

This violates the coding guideline: "Do not access process.env directly in application code." The Redis connection configuration should be loaded from a centralized config module at startup. This was flagged in previous reviews.


9-10: Direct process.env access and magic number.

  1. process.env.DISABLE_WORKERS violates the guideline to load configuration at startup and pass via DI/context.
  2. The value 2 for SENTRY_SPAN_ERROR_CODE is an OpenTelemetry STATUS_CODE_ERROR. Consider adding a comment or importing from @opentelemetry/api for clarity.
📚 Proposed documentation fix for magic number
 const disableWorkers = process.env.DISABLE_WORKERS === "true";
-const SENTRY_SPAN_ERROR_CODE = 2;
+// opentelemetry status code for error (see https://opentelemetry.io/docs/specs/otel/trace/api/#set-status)
+const SENTRY_SPAN_ERROR_CODE = 2;
server/script/openapi.ts (1)

28-29: Direct process.env mutations extend env mutation pattern.

These new assignments continue the pattern of direct env mutation. As flagged in a previous review, consider moving these values into centralized config bootstrap. However, this is a script file for OpenAPI generation, so the impact is limited to build-time tooling.

server/test/queues/alchemyQueue.test.ts (2)

53-53: Remove unnecessary type assertion.

The as Record<string, string> cast is redundant—expect.objectContaining() handles matcher types correctly in Vitest.

♻️ Proposed fix
-        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }) as Record<string, string>,
+        headers: expect.objectContaining({ "X-Alchemy-Token": "mock-token" }),

8-8: Use lowercase comment text.

Per coding guidelines, all code comments must be lowercase.

✍️ Proposed fix
-// Mock dependencies
+// mock dependencies
server/test/utils/createCredential.test.ts (3)

14-14: Use lowercase comment text per style guide.

Comments should be lowercase as per coding guidelines.

♻️ Proposed fix
-// Mock dependencies
+// mock dependencies
-// Mock global fetch to avoid actual network calls
+// mock global fetch to avoid actual network calls

Also applies to: 57-57


80-80: Remove unnecessary type assertion.

The as string cast is redundant—expect.stringMatching() returns the correct matcher type for Vitest.

♻️ Proposed fix
-        account: expect.stringMatching(/^0x/) as string,
+        account: expect.stringMatching(/^0x/),

97-100: Remove the unnecessary type assertion.

The as Record<string, unknown> cast is redundant—the matcher type is sufficient.

♻️ Proposed fix
         extra: expect.objectContaining({
           job: AlchemyJob.ADD_SUBSCRIBER,
           webhookId: "webhook-id",
-        }) as Record<string, unknown>,
+        }),

Comment on lines +55 to +61
webhookId
? alchemyQueue
.add(AlchemyJob.ADD_SUBSCRIBER, { account, webhookId })
.catch((error: unknown) =>
captureException(error, { level: "error", extra: { job: AlchemyJob.ADD_SUBSCRIBER, account, webhookId } }),
)
: undefined,
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
# Check if there's any retry or reconciliation logic for missed webhook subscriptions
echo "=== Checking for retry/reconciliation mechanisms ==="
rg -n -B 2 -A 5 "webhookId|ADD_SUBSCRIBER" server/queues/ server/hooks/activity.ts 2>/dev/null || echo "(No matches or files not found)"

echo -e "\n=== Check if webhookId is ever retried or reconciled ==="
rg -n "retry|reconcile|missing.*webhook|sync.*subscriber" server/ 2>/dev/null || echo "(No matches)"

echo -e "\n=== Check createCredential.ts context around webhookId handling ==="
cat -n server/utils/createCredential.ts 2>/dev/null | head -80

Repository: exactly/exa

Length of output: 7191


Conditional guard prevents errors, but silent skip during startup race causes missed subscriptions.

The webhookId check properly prevents queuing jobs with undefined IDs and error handling includes useful context. However, verification reveals a critical gap: webhookId is a module-level variable initialized asynchronously in activity.ts (lines 248–252). Credentials created during server startup—before the webhook is fetched/created—will silently skip enqueuing ADD_SUBSCRIBER jobs. Unlike early failures after webhookId is set, these early subscriptions have no recovery mechanism.

No retry or reconciliation logic exists to handle missed subscriptions. Consider:

  • Retrying ADD_SUBSCRIBER for credentials created before webhookId initialization completes
  • Or deferring credential creation until webhook is ready
🤖 Prompt for AI Agents
In `@server/utils/createCredential.ts` around lines 55 - 61, The current
conditional in createCredential.ts skips enqueuing AlchemyJob.ADD_SUBSCRIBER
when the module-level webhookId is still undefined (set asynchronously in
activity.ts), causing missed subscriptions during startup; modify
createCredential logic so that when webhookId is undefined it does not silently
drop the job but instead either (A) persist a pending-subscription record (e.g.,
push the credential id/account into a temporary pending list or DB table) and
expose a function like processPendingAlchemySubscriptions() that activity.ts
calls once webhookId is initialized to enqueue AlchemyJob.ADD_SUBSCRIBER for
each pending item, or (B) throw a retriable error to defer credential creation
until webhookId is ready; implement with references to webhookId,
alchemyQueue.add(AlchemyJob.ADD_SUBSCRIBER,...), and activity.ts initialization
to ensure missed ADD_SUBSCRIBER jobs are reconciled after webhookId is set.

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