Skip to content

Conversation

@edulelis
Copy link
Collaborator

@edulelis edulelis commented Oct 21, 2025

Summary by CodeRabbit

  • New Features

    • Unified queue system supporting Redis and QStash with automatic runtime selection.
    • Distributed job processing via a dedicated worker service for better scalability and reliability.
  • Infrastructure

    • Dockerized worker service and standalone build support for production deployments.
  • Configuration

    • New environment options for queue system selection and worker behavior.
  • Documentation

    • Added queue usage guides, best practices, troubleshooting, and API reference for queue operations.

@vercel
Copy link

vercel bot commented Oct 21, 2025

@edulelis is attempting to deploy a commit to the Inbox Zero OSS Program Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 21, 2025

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 unified, backend-agnostic queue system (Upstash QStash + Redis/BullMQ) with runtime selection via QUEUE_SYSTEM, queue manager and providers, worker registry and standalone queue-worker service, migrated enqueue points and route verification, Docker/standalone build changes, documentation, and tests.

Changes

Cohort / File(s) Summary
Queue Core & Types
apps/web/utils/queue/queue-manager.ts, apps/web/utils/queue/types.ts
New queue manager and types: factory-backed selection (BullMQ or QStash), public enqueue/bulk helpers, worker/createQueue stubs/warnings, system info and exported BullMQ types.
Queue Handlers & Routing
apps/web/utils/queue/queues.ts
New strongly-typed job payloads, QUEUE_HANDLERS mapping, queue handler implementations (digest, ai-clean, categorize-senders, scheduled actions, etc.), queue name/index helpers and distribution logic.
Queue Signature & Publish Helpers
apps/web/utils/queue-signature.ts, apps/web/utils/queue/publish.ts
New verifyQueueSignatureAppRouter wrapper (dispatches worker vs QStash verification) and publishFlowControlled helper to route publish vs enqueue based on QUEUE_SYSTEM.
Queue Providers
apps/web/utils/queue/providers/qstash-manager.ts, apps/web/utils/queue/providers/bullmq-manager.ts
New QStashManager and BullMQManager implementing QueueManager API: enqueue/bulkEnqueue semantics, per-account handling (ai-clean), HTTP worker integration (BullMQManager).
Queue Tests
apps/web/utils/queue/providers/qstash-manager.test.ts, apps/web/utils/queue/providers/bullmq-manager.test.ts, apps/web/utils/queue/queue-manager.test.ts
Unit tests for QStashManager, BullMQManager (HTTP worker), and queue-manager system detection.
Worker Registry & Instrumentation
apps/web/utils/queue/worker.ts, apps/web/instrumentation.ts
Worker registry for BullMQ workers (register/unregister/shutdown), global dev guard and startBullMQWorkers to lazily start workers in dev when enabled.
App Route Changes
apps/web/app/api/queue/[queueName]/route.ts, apps/web/app/api/*/*/route.ts (multiple routes: clean, ai/digest, resend, scheduled-actions, categorize/senders)
New generic queue POST route and migrations: replace Upstash verify wrapper with verifyQueueSignatureAppRouter, route request parsing to NextRequest, conditional internal auth for Redis mode, and updated enqueue/publish calls.
Enqueue Callsite Migrations
apps/web/utils/actions/clean.ts, apps/web/utils/digest/index.ts, apps/web/utils/upstash/categorize-senders.ts, apps/web/utils/scheduled-actions/scheduler.ts, apps/web/app/api/clean/route.ts, apps/web/app/api/resend/*
Replaced direct QStash publishing with enqueueJob / bulkEnqueueJobs / publishFlowControlled branches; scheduling now supports notBefore and a QUEUE_SYSTEM fallback; logging and payload shapes adjusted.
Queue-Worker Service
apps/queue-worker/* (package.json, src/env.ts, src/http.ts, src/processor.ts, src/queue.ts, src/server.ts, tsconfig.json)
New standalone queue-worker app: Fastify HTTP API (/v1/jobs, /v1/jobs/bulk), request validation, job processing (processJob), BullMQ queue integration, and env schema.
Docker & Build
docker-compose.yml, docker/Dockerfile.prod, .dockerignore, apps/web/next.config.ts
Add worker service to docker-compose, multi-stage Dockerfile with standalone Next build and worker.js injection, .dockerignore updates, and conditional standalone output / external package addition (bullmq).
Env & Config
apps/web/env.ts, apps/web/package.json, package.json, biome.json
Added QUEUE_SYSTEM, ENABLE_WORKER_QUEUES, WORKER_BASE_URL, WORKER_SIGNING_SECRET; added/pinned bullmq and other deps; added shx dev dep; biome excludes worker.js.
Docs & Rules
.cursor/rules/queues.mdc, .cursor/rules/queues-guidelines.mdc, ARCHITECTURE.md
New queue documentation and guidelines describing supported backends, flows, auth, and recommended patterns.

Sequence Diagram(s)

sequenceDiagram
    participant Web as Web App
    participant QM as Queue Manager
    participant WorkerSvc as Queue Worker (HTTP)
    participant Backend as QStash / Redis

    Note over Web,QM: Enqueue decision based on QUEUE_SYSTEM
    Web->>QM: enqueueJob(queueName, data)
    QM->>QM: inspect QUEUE_SYSTEM
    alt QUEUE_SYSTEM == "redis"
        QM->>WorkerSvc: POST /v1/jobs (job payload + headers)
        WorkerSvc->>Backend: BullMQ enqueue => Redis
    else QUEUE_SYSTEM == "upstash"
        QM->>Backend: QStash publishJSON/enqueueJSON
    end

    Note over Backend,WorkerSvc: Worker processes (Redis path)
    Backend->>WorkerSvc: Job delivered (Bull worker)
    WorkerSvc->>Web: POST targetPath (with x-worker-signature / auth)
    Web->>Web: verifyQueueSignatureAppRouter -> route handler
    Web-->>WorkerSvc: 200 OK
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas to focus during review:

  • apps/web/utils/queue/providers/qstash-manager.ts — per-account grouping and parallelism/dedup logic.
  • apps/web/utils/queue/queues.ts — complex handler implementations (digest batching, db transactions).
  • apps/queue-worker/src/http.ts & src/processor.ts & src/queue.ts — API contracts, auth headers/signature generation, and BullMQ job option mappings.
  • apps/web/app/api/queue/[queueName]/route.ts — dual auth paths (QStash vs internal/worker), request handling and security.
  • docker/Dockerfile.prod and docker-compose.yml — standalone build integration and environment propagation.

Possibly related PRs

Poem

🐇 I hopped from QStash hills to Redis plains,
I carried queued carrots through HTTP rains.
Workers now chatter, signatures tight,
Jobs march in order by day and by night.
A rabbit's small hop, but the queues sing bright.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.28% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'WIP: Flexible queue system with BullMQ and Qstash' clearly describes the main objective: introducing a flexible queue system supporting both BullMQ and Qstash backends.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
apps/web/utils/actions/clean.ts (1)

116-131: Pagination bug: missing pageToken causes repeated first page (potential infinite loop)

Subsequent calls don’t pass nextPageToken, so the loop can re-fetch page 1 forever when maxEmails is undefined.

Apply:

-const { threads, nextPageToken: pageToken } =
-  await emailProvider.getThreadsWithQuery({
+const { threads, nextPageToken: pageToken } =
+  await emailProvider.getThreadsWithQuery({
     query: {
       ...(daysOld > 0 && {
         before: new Date(Date.now() - daysOld * ONE_DAY_MS),
       }),
       labelIds:
         type === "inbox"
           ? [GmailLabel.INBOX]
           : [GmailLabel.INBOX, GmailLabel.UNREAD],
       excludeLabelNames: [inboxZeroLabels.processed.name],
     },
-    maxResults: Math.min(maxEmails || 100, 100),
+    maxResults: Math.min(maxEmails || 100, 100),
+    pageToken: nextPageToken,
   });
apps/web/utils/scheduled-actions/scheduler.ts (1)

167-205: Cancellation gap for BullMQ jobs is confirmed — implement queue-level removal

The verification confirms the review comment's core issue: cancelScheduledActions only updates DB status to CANCELLED but provides no mechanism to cancel queued BullMQ jobs. The queue-manager exports no removeJob or cancelJob function; only QStash messages are explicitly cancelled (lines 195-215).

The code is partially protected by execute/route.ts gating execution on DB status (checking CANCELLED at line 67-72), but this is insufficient because:

  • The handler itself (handleScheduledActionJob at queues.ts:95) is a stub without DB status validation
  • Jobs could be triggered through alternative paths
  • Queue-level cancellation should be standard practice

Required fixes:

  1. Add removeJob(queueName, jobId) to queue-manager exports and BullMQManager implementation
  2. Call it from cancelScheduledActions for each action with a stored jobId (after QStash cancellation)
  3. Replace the handleScheduledActionJob stub with actual implementation that validates DB status
apps/web/env.ts (1)

16-125: Critical: Missing QStash signing keys will break API routes at runtime

The removal of QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY from env.ts will cause runtime failures. Six QStash endpoints using verifySignatureAppRouter require these keys to be defined in the environment schema:

  • apps/web/app/api/user/categorize/senders/batch/route.ts
  • apps/web/app/api/ai/digest/route.ts
  • apps/web/app/api/scheduled-actions/execute/route.ts
  • apps/web/app/api/resend/digest/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/clean/gmail/route.ts

Additionally, apps/web/app/api/queue/[queueName]/route.ts handles QStash jobs but lacks signature verification entirely, violating the requirement that all QStash endpoints must use verifySignatureAppRouter.

Add QSTASH_CURRENT_SIGNING_KEY and QSTASH_NEXT_SIGNING_KEY to the server section of env.ts, and add signature verification to the queue route handler.

🧹 Nitpick comments (12)
apps/web/app/api/clean/route.ts (1)

266-287: Update log messages to reflect the unified queue system.

The log messages on lines 266 and 287 still reference "Qstash" specifically, but the code now uses a backend-agnostic queue system that could be either QStash or Redis.

Apply this diff to update the log messages:

-    logger.info("Publishing to Qstash", {
+    logger.info("Enqueueing clean job", {
       emailAccountId,
       threadId,
       maxRatePerSecond,
       markDone,
     });

     await Promise.all([
       enqueueJob("clean-gmail", cleanGmailBody),
       updateThread({
         emailAccountId,
         jobId,
         threadId,
         update: {
           archive: markDone,
           status: "applying",
           // label: "",
         },
       }),
     ]);

-    logger.info("Published to Qstash", { emailAccountId, threadId });
+    logger.info("Clean job enqueued", { emailAccountId, threadId });
   };
apps/web/utils/upstash/categorize-senders.ts (1)

19-51: Update comment to reflect backend-agnostic queue system.

The comment on line 19 specifically mentions "QStash queue", but the implementation now uses a unified queue system that could be either QStash or Redis.

Apply this diff:

 /**
- * Publishes sender categorization tasks to QStash queue in batches
+ * Publishes sender categorization tasks to the queue system in batches
  * Splits large arrays of senders into chunks of BATCH_SIZE to prevent overwhelming the system
  */
apps/web/utils/actions/clean.ts (2)

147-167: Add per-job deduplication to prevent duplicate processing

Set a stable jobId per thread to dedupe across retries/re-queues.

Apply:

 return {
   data: {
     emailAccountId,
     threadId: thread.id,
     markedDoneLabelId,
     processedLabelId,
     jobId: job.id,
     action,
     instructions,
     skips,
   } satisfies CleanThreadBody,
-  opts: {
-    // Add any job-specific options here
-  },
+  opts: {
+    jobId: `${job.id}:${thread.id}`,
+  },
 };

179-181: Harden after() execution with error handling

Ensure background errors don’t get swallowed.

Apply:

-after(() => process());
+after(() => {
+  void process().catch((error) =>
+    logger.error("Clean process failed", { error }),
+  );
+});
apps/web/utils/scheduled-actions/scheduler.ts (2)

11-12: Rename logger scope/messages to backend-agnostic wording

Logs still say “QStash…”. Use generic “queue system” to reflect Redis/QStash.

Apply:

-const logger = createScopedLogger("qstash-scheduled-actions");
+const logger = createScopedLogger("scheduled-actions");
@@
-logger.info("Created and scheduled action with QStash", {
+logger.info("Created and scheduled action with queue system", {
@@
-logger.error("Failed to create QStash scheduled action", {
+logger.error("Failed to create scheduled action", {
@@
-logger.info("Scheduled delayed actions with QStash", {
+logger.info("Scheduled delayed actions", {
@@
-logger.info("Cancelled QStash scheduled actions", {
+logger.info("Cancelled scheduled actions", {
@@
-logger.error("Failed to cancel QStash scheduled actions", {
+logger.error("Failed to cancel scheduled actions", {

Also applies to: 94-103, 105-113, 157-163, 229-236, 239-246


322-343: Function name is QStash-specific

markQStashActionAsExecuting should be backend-agnostic (e.g., markScheduledActionAsExecuting) for clarity and future maintenance.

Would you like a follow-up PR to rename and update references?

apps/web/utils/queue/queue.test.ts (1)

250-268: Consider adding tests for cancellation behavior and DB gating

Nice coverage. Add tests asserting:

  • cancelScheduledActions cancels QStash messages and updates DB.
  • For Redis, verify worker skips execution when DB status is CANCELLED (until cancel is implemented).

Also applies to: 430-556

apps/web/utils/queue/worker.ts (1)

22-29: Surface worker config (optional)

If available, pass removeOnComplete/removeOnFail via createQueueWorker options to avoid hard-coded defaults in the manager.

apps/web/utils/queue/qstash-manager.ts (1)

19-45: Surface unsupported options (attempts/priority/removeOn) for QStash to avoid silent no-ops*

QStash ignores BullMQ-only options. Consider warning once when such fields are provided to prevent confusion.

const unsupported = ["attempts","priority","removeOnComplete","removeOnFail"].filter(
  (k) => k in (options ?? {}),
);
if (unsupported.length) {
  logger.warn("Ignoring unsupported enqueue options for QStash", { unsupported, queueName });
}

Also applies to: 47-78

apps/web/utils/queue/bullmq-manager.ts (1)

55-59: Don’t pre-stringify data in logs

Logger already stringifies objects. Passing JSON strings reduces readability.

-    logger.info("Job enqueued with BullMQ", {
-      queueName,
-      jobId: job.id,
-      data: JSON.stringify(data),
-    });
+    logger.info("Job enqueued with BullMQ", {
+      queueName,
+      jobId: job.id,
+      data,
+    });
apps/web/utils/queue/types.ts (1)

94-99: Use QueueSystem for QueueSystemInfo.system

Strengthens typing and prevents invalid values.

-export interface QueueSystemInfo {
-  system: string;
+export interface QueueSystemInfo {
+  system: QueueSystem;
   supportsWorkers: boolean;
   supportsDelayedJobs: boolean;
   supportsBulkOperations: boolean;
 }
apps/web/utils/queue/queue-manager.ts (1)

65-75: Return types don’t need | null

These functions either return a Worker/Queue or throw (QStash path). Drop | null to simplify call sites.

-export function createQueueWorker<T extends QueueJobData>(
+export function createQueueWorker<T extends QueueJobData>(
   queueName: string,
   processor: (job: Job<T>) => Promise<void>,
   options?: {
     concurrency?: number;
     connection?: ConnectionOptions;
   },
-): Worker | null {
+): Worker {
   const manager = getQueueManager();
   return manager.createWorker(queueName, processor, options);
 }
...
-export function createQueue<T extends QueueJobData>(
+export function createQueue<T extends QueueJobData>(
   queueName: string,
   options?: {
     connection?: ConnectionOptions;
     defaultJobOptions?: Record<string, unknown>;
   },
-): Queue<T> | null {
+): Queue<T> {
   const manager = getQueueManager();
   return manager.createQueue(queueName, options);
 }

Also applies to: 77-86

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08249d9 and cbbfa25.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • .cursor/rules/queues.mdc (1 hunks)
  • apps/web/app/api/clean/route.ts (2 hunks)
  • apps/web/app/api/queue/[queueName]/route.ts (1 hunks)
  • apps/web/app/api/resend/digest/all/route.ts (2 hunks)
  • apps/web/app/api/resend/summary/all/route.ts (2 hunks)
  • apps/web/env.ts (2 hunks)
  • apps/web/package.json (1 hunks)
  • apps/web/utils/actions/clean.ts (3 hunks)
  • apps/web/utils/digest/index.ts (2 hunks)
  • apps/web/utils/queue/bullmq-manager.ts (1 hunks)
  • apps/web/utils/queue/qstash-manager.ts (1 hunks)
  • apps/web/utils/queue/queue-manager.ts (1 hunks)
  • apps/web/utils/queue/queue.test.ts (1 hunks)
  • apps/web/utils/queue/queues.ts (1 hunks)
  • apps/web/utils/queue/types.ts (1 hunks)
  • apps/web/utils/queue/worker.ts (1 hunks)
  • apps/web/utils/scheduled-actions/scheduler.ts (3 hunks)
  • apps/web/utils/upstash/categorize-senders.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (21)
.cursor/rules/*.mdc

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

.cursor/rules/*.mdc: Always place rule files in PROJECT_ROOT/.cursor/rules/
Follow the naming convention: use kebab-case for filenames, always use .mdc extension, and make names descriptive of the rule's purpose
Cursor rules must have a frontmatter section with description, globs, and alwaysApply fields, followed by markdown content explaining the rule

Files:

  • .cursor/rules/queues.mdc
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • .cursor/rules/queues.mdc
  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/env.ts
  • apps/web/utils/queue/worker.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/utils/queue/types.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/package.json
  • apps/web/utils/digest/index.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/qstash-manager.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • .cursor/rules/queues.mdc
  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/env.ts
  • apps/web/utils/queue/worker.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/utils/queue/types.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/package.json
  • apps/web/utils/digest/index.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/qstash-manager.ts
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/env.ts
  • apps/web/utils/queue/worker.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/utils/queue/types.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/digest/index.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/qstash-manager.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/env.ts
  • apps/web/utils/queue/worker.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/utils/queue/types.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/digest/index.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/qstash-manager.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/env.ts
  • apps/web/utils/queue/worker.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/utils/queue/types.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/digest/index.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/qstash-manager.ts
**/api/**/route.ts

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

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/env.ts
  • apps/web/utils/queue/worker.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/utils/queue/types.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/digest/index.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/qstash-manager.ts
apps/web/**/{.env.example,env.ts,turbo.json}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Add environment variables to .env.example, env.ts, and turbo.json

Files:

  • apps/web/env.ts
apps/web/**/{.env.example,env.ts}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Client-side environment variables: Prefix with NEXT_PUBLIC_

Files:

  • apps/web/env.ts
apps/web/env.ts

📄 CodeRabbit inference engine (.cursor/rules/environment-variables.mdc)

apps/web/env.ts: When adding a new environment variable, add it to apps/web/env.ts in the appropriate section: use server for server-only variables, and for client-side variables, use the client section and also add to experimental__runtimeEnv.
Client-side environment variables must be prefixed with NEXT_PUBLIC_ and added to both the client and experimental__runtimeEnv sections in apps/web/env.ts.

Files:

  • apps/web/env.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/utils/queue/types.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/digest/index.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/qstash-manager.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/utils/queue/types.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/digest/index.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/actions/clean.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/qstash-manager.ts
**/*.test.{ts,js}

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

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/utils/queue/queue.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

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

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.

Files:

  • apps/web/utils/queue/queue.test.ts
**/*.test.{ts,tsx}

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

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/utils/queue/queue.test.ts
apps/web/utils/actions/**/*.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/utils/actions/**/*.ts: Use server actions for all mutations (create/update/delete operations)
next-safe-action provides centralized error handling
Use Zod schemas for validation on both client and server
Use revalidatePath in server actions for cache invalidation

apps/web/utils/actions/**/*.ts: Use server actions (with next-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
Use revalidatePath in server actions to invalidate cache after mutations.

Files:

  • apps/web/utils/actions/clean.ts
apps/web/utils/actions/*.ts

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

apps/web/utils/actions/*.ts: Implement all server actions using the next-safe-action library for type safety, input validation, context management, and error handling. Refer to apps/web/utils/actions/safe-action.ts for client definitions (actionClient, actionClientUser, adminActionClient).
Use actionClientUser when only authenticated user context (userId) is needed.
Use actionClient when both authenticated user context and a specific emailAccountId are needed. The emailAccountId must be bound when calling the action from the client.
Use adminActionClient for actions restricted to admin users.
Access necessary context (like userId, emailAccountId, etc.) provided by the safe action client via the ctx object in the .action() handler.
Server Actions are strictly for mutations (operations that change data, e.g., creating, updating, deleting). Do NOT use Server Actions for data fetching (GET operations). For data fetching, use dedicated GET API Routes combined with SWR Hooks.
Use SafeError for expected/handled errors within actions if needed. next-safe-action provides centralized error handling.
Use the .metadata({ name: "actionName" }) method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied via withServerActionInstrumentation within the safe action clients.
If an action modifies data displayed elsewhere, use revalidatePath or revalidateTag from next/cache within the action handler as needed.

Server action files must start with use server

Files:

  • apps/web/utils/actions/clean.ts
🧬 Code graph analysis (14)
apps/web/app/api/queue/[queueName]/route.ts (2)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/queues.ts (2)
  • isValidQueueName (176-178)
  • getQueueHandler (172-174)
apps/web/utils/queue/worker.ts (3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/queue-manager.ts (5)
  • Worker (103-103)
  • JobProcessor (113-113)
  • WorkerConfig (112-112)
  • createQueueWorker (65-75)
  • closeQueueManager (88-93)
apps/web/utils/queue/types.ts (3)
  • Worker (101-101)
  • JobProcessor (92-92)
  • WorkerConfig (84-90)
apps/web/app/api/clean/route.ts (1)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (48-55)
apps/web/app/api/resend/digest/all/route.ts (1)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (48-55)
apps/web/utils/queue/queues.ts (2)
apps/web/app/api/outlook/webhook/logger.ts (1)
  • logger (3-3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/queue-manager.ts (5)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/types.ts (8)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
  • ConnectionOptions (101-101)
  • Worker (101-101)
  • Queue (101-101)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/queue/bullmq-manager.ts (2)
  • BullMQManager (25-199)
  • createQueue (138-157)
apps/web/utils/queue/qstash-manager.ts (2)
  • QStashManager (18-111)
  • createQueue (93-101)
apps/web/app/api/resend/summary/all/route.ts (1)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (48-55)
apps/web/utils/digest/index.ts (2)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (48-55)
apps/web/utils/mail.ts (1)
  • emailToContent (72-101)
apps/web/utils/upstash/categorize-senders.ts (1)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (48-55)
apps/web/utils/queue/queue.test.ts (4)
apps/web/utils/queue/queue-manager.ts (5)
  • closeQueueManager (88-93)
  • getQueueSystemInfo (95-101)
  • enqueueJob (48-55)
  • bulkEnqueueJobs (57-63)
  • createQueueManager (21-36)
apps/web/utils/queue/bullmq-manager.ts (1)
  • BullMQManager (25-199)
apps/web/utils/queue/qstash-manager.ts (1)
  • QStashManager (18-111)
apps/web/utils/queue/worker.ts (7)
  • registerWorker (12-67)
  • registerWorker (122-128)
  • unregisterWorker (69-79)
  • unregisterWorker (130-132)
  • getWorker (85-87)
  • getWorker (134-136)
  • shutdownAllWorkers (142-144)
apps/web/utils/queue/bullmq-manager.ts (3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/types.ts (9)
  • QueueManager (50-82)
  • Queue (101-101)
  • Worker (101-101)
  • QueueEvents (101-101)
  • ConnectionOptions (101-101)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/actions/clean.ts (4)
apps/web/app/api/outlook/webhook/logger.ts (1)
  • logger (3-3)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/types.ts (1)
  • isDefined (8-10)
apps/web/utils/queue/queue-manager.ts (1)
  • bulkEnqueueJobs (57-63)
apps/web/utils/scheduled-actions/scheduler.ts (2)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (48-55)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/queue/qstash-manager.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/types.ts (6)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • BulkEnqueueOptions (42-48)
  • Job (101-101)
  • ConnectionOptions (101-101)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/upstash/index.ts (1)
  • publishToQstashQueue (56-78)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: test
🔇 Additional comments (13)
apps/web/package.json (1)

95-95: LGTM! BullMQ dependency added correctly.

The BullMQ version ^5.61.0 is a stable release from the v5 line with good Redis compatibility. Based on learnings, this version includes incremental improvements and bug fixes without breaking API changes.

apps/web/app/api/clean/route.ts (2)

5-5: Migration to unified queue system looks correct.

The import change from publishToQstash to enqueueJob aligns with the new queue abstraction.


274-274: Queue "clean-gmail" is properly registered.

The queue name is correctly registered with handler handleCleanGmailJob at line 168 of apps/web/utils/queue/queues.ts. The enqueueJob call is valid.

apps/web/utils/upstash/categorize-senders.ts (2)

2-3: Import changes look correct.

The migration from publishToQstashQueue to enqueueJob aligns with the unified queue system.


46-49: Dynamic queue names are fully supported.

BullMQ explicitly creates or retrieves the queue via getOrCreateQueue(queueName), and QStash constructs unique webhook endpoints per queue name, which auto-creates queues on first enqueue. The code correctly passes the dynamic queueName (ai-categorize-senders-{emailAccountId}) through both implementations, and both queue systems handle this pattern natively without additional configuration.

apps/web/app/api/resend/digest/all/route.ts (2)

8-8: Import change looks correct.

The migration to enqueueJob from the unified queue manager is appropriate.


51-53: Verification complete — queue registration confirmed.

The "email-digest-all" queue is registered at line 166 of apps/web/utils/queue/queues.ts with the handler handleEmailDigestAllJob. The code changes properly reference this existing queue.

apps/web/utils/digest/index.ts (2)

1-1: Import change looks correct.

The migration to the unified queue system is appropriate.


21-39: Queue name "digest-item-summarize" is properly registered.

Verification confirms the queue registration exists at line 162 in apps/web/utils/queue/queues.ts with handler handleDigestJob. The simplified enqueueing logic maintains the message structure correctly, and error handling is properly implemented.

apps/web/app/api/resend/summary/all/route.ts (1)

46-48: Migration to simplified enqueueing logic verified.

Queue name "email-summary-all" is properly registered in apps/web/utils/queue/queues.ts (line 167), confirming the API route correctly uses the registered queue handler handleEmailSummaryAllJob. The removal of URL construction and adoption of the cleaner queue API is sound.

apps/web/utils/scheduled-actions/scheduler.ts (1)

249-279: Good: unified scheduling via enqueueJob with dedup and delay

The switch to enqueueJob with milliseconds delay and deduplicationId looks solid.

apps/web/utils/queue/queue.test.ts (1)

54-64: Test isolation looks good

clearAllMocks + resetModules + closeQueueManager is the right combo here.

apps/web/utils/queue/worker.ts (1)

146-158: Graceful shutdown handlers look good

SIGINT/SIGTERM hooks delegating to shutdownAllWorkers and closeQueueManager are appropriate.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

12 issues found across 19 files

Prompt for AI agents (all 12 issues)

Understand the root cause of the following 12 issues and fix them.


<file name="apps/web/app/api/resend/digest/all/route.ts">

<violation number="1" location="apps/web/app/api/resend/digest/all/route.ts:51">
Routing this cron through `enqueueJob(&quot;email-digest-all&quot;, …)` hands execution to `handleEmailDigestAllJob`, which is still just a TODO stub that logs and sleeps instead of sending the digest. The previous direct call to `/api/resend/digest` actually processed the email, so this change causes every digest send to be dropped.</violation>
</file>

<file name="apps/web/app/api/queue/[queueName]/route.ts">

<violation number="1" location="apps/web/app/api/queue/[queueName]/route.ts:15">
This queue endpoint processes QStash jobs without verifying the Upstash signature, so any unauthenticated caller can trigger queue handlers with arbitrary payloads, allowing unauthorized job execution. Please wrap the handler with verifySignatureAppRouter (or equivalent) like other QStash routes.</violation>
</file>

<file name=".cursor/rules/queues.mdc">

<violation number="1" location=".cursor/rules/queues.mdc:58">
The documentation states the BullMQ worker default concurrency is 3, but the code actually enforces a default of 1 via the worker registry. Please update the docs or code so they match.</violation>
</file>

<file name="apps/web/utils/queue/qstash-manager.ts">

<violation number="1" location="apps/web/utils/queue/qstash-manager.ts:27">
Convert the delayed notBefore calculation to use whole-second Unix timestamps so QStash receives an integer value.</violation>

<violation number="2" location="apps/web/utils/queue/qstash-manager.ts:65">
Ensure bulk-enqueued jobs also send integer Unix timestamps for notBefore when applying a delay.</violation>
</file>

<file name="apps/web/utils/queue/worker.ts">

<violation number="1" location="apps/web/utils/queue/worker.ts:45">
Guard `job.opts` when logging maxAttempts in the failed event listener so the handler cannot throw if opts is undefined.</violation>
</file>

<file name="apps/web/utils/queue/bullmq-manager.ts">

<violation number="1" location="apps/web/utils/queue/bullmq-manager.ts:33">
Passing the Redis connection string through the host option makes the BullMQ Redis connection fail; supply it via the url field (or parse host/port) so ioredis can connect.</violation>

<violation number="2" location="apps/web/utils/queue/bullmq-manager.ts:46">
Using `||` here prevents callers from setting attempts to 0 to disable retries; use nullish coalescing so 0 is respected.</violation>

<violation number="3" location="apps/web/utils/queue/bullmq-manager.ts:48">
Using `||` here overrides explicit `false`/`0` values, preventing callers from retaining completed jobs; use nullish coalescing instead.</violation>

<violation number="4" location="apps/web/utils/queue/bullmq-manager.ts:49">
This `||` fallback discards explicit `false`/`0` settings, so failed jobs cannot be retained as requested; switch to nullish coalescing.</violation>
</file>

<file name="apps/web/utils/queue/queue-manager.ts">

<violation number="1" location="apps/web/utils/queue/queue-manager.ts:74">
Calling manager.createWorker here will throw whenever QUEUE_SYSTEM is set to &quot;upstash&quot;, because QStashManager.createWorker intentionally throws. registerWorker expects this helper to return null when workers aren’t supported, so this line causes a crash instead of graceful fallback. Please guard against non-redis systems before invoking manager.createWorker.</violation>
</file>

<file name="apps/web/utils/upstash/categorize-senders.ts">

<violation number="1" location="apps/web/utils/upstash/categorize-senders.ts:46">
enqueueJob posts to /api/queue/${queueName}, but the dynamic queue name with the emailAccountId suffix is not registered in QUEUE_HANDLERS, so the handler returns 400 and these categorization jobs never run.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
apps/web/app/api/resend/summary/all/route.ts (1)

41-53: Don’t log PII (email) in error logs.

Avoid emitting raw email addresses. Log a count or a hash if needed.

   for (const emailAccount of emailAccounts) {
     try {
       await enqueueJob("email-summary-all", {
         email: emailAccount.email,
       });
     } catch (error) {
-      logger.error("Failed to enqueue summary job", {
-        email: emailAccount.email,
-        error,
-      });
+      logger.error("Failed to enqueue summary job", {
+        // Avoid PII in logs
+        error,
+      });
     }
   }
docker-compose.yml (2)

59-64: Set QUEUE_SYSTEM consistently for web and worker.

worker uses redis, but web inherits default upstash. This mismatch means web enqueues via QStash while the worker listens on Redis (idle worker). Align both explicitly.

   environment:
     DATABASE_URL: "postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-inboxzero}?schema=public"
     DIRECT_URL: "postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-password}@db:5432/${POSTGRES_DB:-inboxzero}?schema=public"
     UPSTASH_REDIS_URL: "http://serverless-redis-http:80"
     UPSTASH_REDIS_TOKEN: "${UPSTASH_REDIS_TOKEN}"
+    # Ensure web matches the intended backend for the environment
+    QUEUE_SYSTEM: "${QUEUE_SYSTEM:-upstash}"

18-25: Use a separate volume for Redis data.

redis currently uses database-data, the same as Postgres. Use a dedicated redis-data volume to avoid data mixing and simplify lifecycle.

   redis:
     image: redis
     ports:
       - ${REDIS_PORT:-6380}:6379
     volumes:
-      - database-data:/data
+      - redis-data:/data
@@
 volumes:
   database-data:
+  redis-data:

Also applies to: 90-94

apps/web/utils/upstash/categorize-senders.ts (2)

61-66: Redact PII from logs.

queueName contains emailAccountId in the QStash branch. Avoid logging it; keep only counts and queueSystem.

-  logger.info("Publishing to AI categorize senders queue in chunks", {
-    url,
-    queueName,
-    totalSenders: body.senders.length,
-    numberOfChunks: chunks.length,
-  });
+  logger.info("Publishing categorize-senders jobs", {
+    totalSenders: body.senders.length,
+    numberOfChunks: chunks.length,
+    queueSystem: env.QUEUE_SYSTEM,
+  });

90-116: Guard Upstash-only queue deletion.

listQueues/deleteQueue are Upstash utilities. Skip when QUEUE_SYSTEM !== "upstash" to avoid errors under Redis.

 async function deleteEmptyQueues({
   prefix,
   skipEmailAccountId,
 }: {
   prefix: string;
   skipEmailAccountId: string;
 }) {
+  if (env.QUEUE_SYSTEM !== "upstash") return;
   const queues = await listQueues();
   logger.info("Found queues", { count: queues.length });
   for (const queue of queues) {
♻️ Duplicate comments (6)
apps/web/app/api/queue/[queueName]/route.ts (2)

16-64: Wrap with error middleware and gate signature verification to QStash.

Use withError for consistent errors, and only require Upstash signature when QUEUE_SYSTEM === "upstash". Reject other calls to this endpoint under Redis.

-import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";
+import { verifySignatureAppRouter } from "@upstash/qstash/nextjs";
+import { withError } from "@/utils/middleware";
+import { getQueueSystemInfo } from "@/utils/queue/queue-manager";
@@
-const logger = createScopedLogger("queue-api");
-
-export const POST = verifySignatureAppRouter(
-  async (
+const logger = createScopedLogger("queue-api");
+
+async function handleQueuePost(
     request: NextRequest,
     { params }: { params: { queueName: string } },
-  ) => {
+  ) {
@@
-    }
-  },
-);
+    }
+  }
+
+export const POST = withError(
+  async (request: NextRequest, context: { params: { queueName: string } }) => {
+    const { isQStash } = getQueueSystemInfo();
+    return isQStash
+      ? verifySignatureAppRouter(handleQueuePost)(request, context)
+      : NextResponse.json(
+          { error: "This endpoint is only for QStash backend" },
+          { status: 403 },
+        );
+  },
+);

26-29: Stop logging request body (PII risk).

Remove body: JSON.stringify(body); only log non-sensitive metadata (e.g., queueName, jobId if present).

-      logger.info("Received queue job", {
-        queueName,
-        body: JSON.stringify(body),
-      });
+      logger.info("Received queue job", { queueName });
apps/web/utils/queue/qstash-manager.ts (2)

30-39: Round notBefore up to avoid early execution.

Use ceil so short delays don’t schedule earlier than intended.

-      const notBefore =
-        Math.floor(Date.now() / 1000) + Math.floor(options.delay / 1000);
+      const notBefore = Math.ceil((Date.now() + options.delay) / 1000);

18-21: Remove non-null assertion; validate token up front.

Fail fast if QSTASH_TOKEN is missing.

-function getQstashClient(): Client {
-  return new Client({ token: env.QSTASH_TOKEN! });
-}
+function getQstashClient(): Client {
+  if (!env.QSTASH_TOKEN) {
+    throw new Error("QSTASH_TOKEN is required when QUEUE_SYSTEM=upstash");
+  }
+  return new Client({ token: env.QSTASH_TOKEN });
+}
apps/web/utils/upstash/categorize-senders.ts (1)

28-41: Jobs will 400 due to queue-name validation mismatch.

For QStash you emit ai-categorize-senders-{emailAccountId}, but isValidQueueName in apps/web/utils/queue/queues.ts only accepts indexed names (0..AI_CATEGORIZE_SENDERS_QUEUE_COUNT-1). The POST route rejects these as unknown, so chunks never run. Fix one of:

  • Accept emailAccountId-suffixed names in isValidQueueName and route to the handler, or
  • Use the indexed form for both backends.

Recommended change in queues.ts (accept both forms):

-  if (queueName.startsWith(`${AI_CATEGORIZE_SENDERS_PREFIX}-`)) {
-    const queueIndex = getAiCategorizeSendersQueueIndex(queueName);
-    return (
-      queueIndex !== null &&
-      queueIndex >= 0 &&
-      queueIndex < AI_CATEGORIZE_SENDERS_QUEUE_COUNT
-    );
-  }
+  if (queueName.startsWith(`${AI_CATEGORIZE_SENDERS_PREFIX}-`)) {
+    // Allow either "-<index>" (redis) or "-<emailAccountId>" (qstash)
+    const idx = getAiCategorizeSendersQueueIndex(queueName);
+    return idx !== null
+      ? idx >= 0 && idx < AI_CATEGORIZE_SENDERS_QUEUE_COUNT
+      : true;
+  }

Alternatively, standardize here to always return the indexed form even for QStash.

Also applies to: 69-76

apps/web/utils/queue/queues.ts (1)

437-448: Address PII logging and complete TODO implementation.

Two issues:

  1. Lines 438-441 log raw email addresses, which is a compliance/privacy risk. As noted in previous reviews, email addresses should be redacted or hashed before logging.

  2. This handler contains placeholder logic that needs implementation.

Based on learnings

🧹 Nitpick comments (13)
apps/web/utils/queue/queue.test.ts (3)

54-63: Tighten mock hygiene between tests.

Add vi.resetAllMocks() so stubbed implementations don’t leak across tests (e.g., prior mockResolvedValueOnce). Keep vi.clearAllMocks() for call counts.

   beforeEach(() => {
-    vi.clearAllMocks();
+    vi.clearAllMocks();
+    vi.resetAllMocks();
   });

146-159: Make delay/notBefore assertions deterministic.

Use fake timers and a fixed system time to assert exact notBefore, avoiding flakiness from wall-clock timing.

 it("should enqueue a job with options", async () => {
+  vi.useFakeTimers({ now: new Date("2025-01-01T00:00:00Z") });
   const { enqueueJob } = await import("./queue-manager");
   const jobData = { message: "Delayed job", userId: "user-456" };
   const options = { delay: 5000, priority: 1, jobId: "custom-job-id" };
@@
-  expect(mockClient.publishJSON).toHaveBeenCalledWith({
+  expect(mockClient.publishJSON).toHaveBeenCalledWith({
     url: "https://test.com/api/queue/test-queue",
     body: jobData,
-    notBefore: expect.any(Number),
+    notBefore: 1735689605, // 2025-01-01T00:00:00Z + 5s
     deduplicationId: "custom-job-id",
   });
   expect(result).toBe("qstash-delayed-123");
+  vi.useRealTimers();
 });

Also applies to: 469-478


499-535: Consider a bulk+delay test for QStash.

Add a case for bulkEnqueue with options.delay and job.opts.jobId to verify both notBefore and dedup IDs flow into batchJSON entries.

docker/Dockerfile.prod (1)

8-10: Harden image: drop root at runtime and remove .npmrc after install.

  • Remove .npmrc* from final layer to avoid leaking registry tokens.
  • Run as non-root in the final container. No behavior change to build steps.
 RUN pnpm install --no-frozen-lockfile --prefer-offline
+# Remove npm config files (may contain tokens) from final image layer
+RUN rm -f .npmrc .npmrc* || true
@@
 EXPOSE 3000
+#
+# Create non-root user for runtime and switch
+RUN addgroup -S nodejs && adduser -S node -G nodejs
+RUN chown -R node:node /app
+USER node
@@
 CMD pnpm --filter inbox-zero-ai start 

Also applies to: 33-34, 74-78

apps/web/app/api/resend/summary/all/route.ts (2)

9-11: Export a typed response for GET.

Expose a response type to enable type-safe client usage of this route. As per coding guidelines.

 import { createScopedLogger } from "@/utils/logger";
-import { enqueueJob } from "@/utils/queue/queue-manager";
+import { enqueueJob, bulkEnqueueJobs } from "@/utils/queue/queue-manager";
+
+export type SummaryAllResponse = { count: number };

41-57: Prefer bulk enqueue for throughput and lower latency.

Replace per-email enqueueJob loop with a single bulkEnqueueJobs call. Keeps behavior, improves performance, and reduces external calls. As per coding guidelines.

-  for (const emailAccount of emailAccounts) {
-    try {
-      await enqueueJob("email-summary-all", {
-        email: emailAccount.email,
-      });
-    } catch (error) {
-      logger.error("Failed to enqueue summary job", {
-        // Avoid PII in logs
-        error,
-      });
-    }
-  }
+  try {
+    const jobs = emailAccounts.map((ea) => ({ data: { email: ea.email } }));
+    await bulkEnqueueJobs("email-summary-all", { jobs });
+  } catch (error) {
+    logger.error("Failed to bulk enqueue summary jobs", { error });
+  }
apps/web/utils/upstash/categorize-senders.ts (1)

44-46: Docstring is outdated.

Update "Publishes ... to QStash" to "Publishes ... to the configured queue system."

apps/web/utils/scheduled-actions/scheduler.ts (1)

11-11: Make naming/logs backend-agnostic.

Logger scope and messages still mention QStash; rename to generic and update messages. Also consider renaming markQStashActionAsExecuting to markScheduledActionAsExecuting (and update call sites).

-const logger = createScopedLogger("qstash-scheduled-actions");
+const logger = createScopedLogger("scheduled-actions");
@@
-    logger.info("Created and scheduled action with QStash", {
+    logger.info("Created and scheduled action", {
@@
-  logger.info("Scheduled delayed actions with QStash", {
+  logger.info("Scheduled delayed actions", {
@@
-  logger.info("Cancelled QStash scheduled actions", {
+  logger.info("Cancelled scheduled actions", {
@@
-export async function markQStashActionAsExecuting(…
+export async function markScheduledActionAsExecuting(…

Also applies to: 95-103, 158-164, 230-237, 323-344

apps/web/utils/queue/qstash-manager.ts (1)

69-77: Apply per-job delay and ceil in bulk path.

Respect job.opts?.delay when set; fall back to options.delay. Also use ceil.

-      if (options.delay) {
-        item.notBefore =
-          Math.floor(Date.now() / 1000) + Math.floor(options.delay / 1000);
-      }
+      const d = job.opts?.delay ?? options.delay;
+      if (d) {
+        item.notBefore = Math.ceil((Date.now() + d) / 1000);
+      }
apps/web/utils/queue/queues.ts (4)

81-94: Complete the TODO implementation.

This handler contains placeholder logic with a hardcoded delay. The actual digest processing logic needs to be implemented.

Do you want me to help implement the digest processing logic based on the patterns used in sendDigestEmailForAccount?


126-151: Complete the TODO implementations.

Both handleScheduledActionJob and handleAiCleanJob contain placeholder logic with hardcoded delays. These need actual implementations before production use.


276-276: Consider extracting batch fetching logic.

The TODO comment suggests refactoring this batch message fetching pattern into the provider if used elsewhere. This would improve code reusability.


450-462: Complete the TODO implementation.

This handler contains placeholder logic with a hardcoded delay. The actual Gmail cleaning logic needs to be implemented.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbbfa25 and 9d39a5f.

📒 Files selected for processing (15)
  • .cursor/rules/queues.mdc (1 hunks)
  • apps/web/app/api/queue/[queueName]/route.ts (1 hunks)
  • apps/web/app/api/resend/summary/all/route.ts (2 hunks)
  • apps/web/package.json (3 hunks)
  • apps/web/utils/queue/bullmq-manager.ts (1 hunks)
  • apps/web/utils/queue/qstash-manager.ts (1 hunks)
  • apps/web/utils/queue/queue-manager.ts (1 hunks)
  • apps/web/utils/queue/queue.test.ts (1 hunks)
  • apps/web/utils/queue/queues.ts (1 hunks)
  • apps/web/utils/queue/worker.ts (1 hunks)
  • apps/web/utils/scheduled-actions/scheduler.ts (3 hunks)
  • apps/web/utils/upstash/categorize-senders.ts (3 hunks)
  • apps/web/worker.ts (1 hunks)
  • docker-compose.yml (1 hunks)
  • docker/Dockerfile.prod (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .cursor/rules/queues.mdc
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/web/package.json
  • apps/web/utils/queue/worker.ts
  • apps/web/utils/queue/bullmq-manager.ts
🧰 Additional context used
📓 Path-based instructions (15)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/worker.ts
apps/web/app/**

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

NextJS app router structure with (app) directory

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
apps/web/app/api/**/route.ts

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/app/api/**/route.ts: Use withAuth for user-level operations
Use withEmailAccount for email-account-level operations
Do NOT use POST API routes for mutations - use server actions instead
No need for try/catch in GET routes when using middleware
Export response types from GET routes

apps/web/app/api/**/route.ts: Wrap all GET API route handlers with withAuth or withEmailAccount middleware for authentication and authorization.
Export response types from GET API routes for type-safe client usage.
Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • docker/Dockerfile.prod
  • apps/web/utils/scheduled-actions/scheduler.ts
  • docker-compose.yml
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/worker.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/worker.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/worker.ts
**/api/**/route.ts

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

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/worker.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • docker/Dockerfile.prod
  • apps/web/utils/scheduled-actions/scheduler.ts
  • docker-compose.yml
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
  • apps/web/worker.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
**/*.test.{ts,js}

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

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/utils/queue/queue.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

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

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.

Files:

  • apps/web/utils/queue/queue.test.ts
**/*.test.{ts,tsx}

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

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/utils/queue/queue.test.ts
🧠 Learnings (1)
📚 Learning: 2025-07-20T09:00:41.968Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-07-20T09:00:41.968Z
Learning: Applies to apps/web/app/api/**/*.{ts,js} : All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.

Applied to files:

  • apps/web/app/api/queue/[queueName]/route.ts
🧬 Code graph analysis (9)
apps/web/app/api/queue/[queueName]/route.ts (1)
apps/web/utils/queue/queues.ts (2)
  • isValidQueueName (507-522)
  • getQueueHandler (495-505)
apps/web/utils/upstash/categorize-senders.ts (3)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/queue/queues.ts (1)
  • AI_CATEGORIZE_SENDERS_QUEUE_COUNT (465-465)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (56-63)
apps/web/utils/scheduled-actions/scheduler.ts (3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (56-63)
apps/web/env.ts (1)
  • env (16-240)
apps/web/app/api/resend/summary/all/route.ts (1)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (56-63)
apps/web/utils/queue/queue.test.ts (4)
apps/web/utils/queue/queue-manager.ts (5)
  • closeQueueManager (106-111)
  • getQueueSystemInfo (113-119)
  • enqueueJob (56-63)
  • bulkEnqueueJobs (65-71)
  • createQueueManager (21-44)
apps/web/utils/queue/bullmq-manager.ts (1)
  • BullMQManager (22-196)
apps/web/utils/queue/qstash-manager.ts (1)
  • QStashManager (22-117)
apps/web/utils/queue/worker.ts (7)
  • registerWorker (13-74)
  • registerWorker (129-135)
  • unregisterWorker (76-86)
  • unregisterWorker (137-139)
  • getWorker (92-94)
  • getWorker (141-143)
  • shutdownAllWorkers (149-151)
apps/web/utils/queue/qstash-manager.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/queue/types.ts (6)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • BulkEnqueueOptions (42-48)
  • Job (101-101)
  • ConnectionOptions (101-101)
apps/web/utils/upstash/index.ts (1)
  • publishToQstashQueue (56-78)
apps/web/utils/queue/queue-manager.ts (4)
apps/web/utils/queue/types.ts (8)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
  • ConnectionOptions (101-101)
  • Worker (101-101)
  • Queue (101-101)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/queue/bullmq-manager.ts (2)
  • BullMQManager (22-196)
  • createQueue (135-154)
apps/web/utils/queue/qstash-manager.ts (2)
  • QStashManager (22-117)
  • createQueue (99-107)
apps/web/utils/queue/queues.ts (9)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/types.ts (1)
  • ParsedMessage (55-70)
apps/web/utils/rule/consts.ts (1)
  • getRuleName (118-120)
apps/web/app/api/resend/digest/validation.ts (2)
  • storedDigestContentSchema (3-3)
  • Digest (16-16)
apps/web/utils/email.ts (1)
  • extractNameFromEmail (9-16)
packages/resend/src/send.tsx (1)
  • sendDigestEmail (117-142)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/schedule.ts (1)
  • calculateNextScheduleDate (124-227)
apps/web/worker.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/queue/queues.ts (2)
  • QUEUE_HANDLERS (477-492)
  • QueueName (494-494)
apps/web/utils/queue/worker.ts (4)
  • registerWorker (13-74)
  • registerWorker (129-135)
  • shutdown (96-125)
  • shutdownAllWorkers (149-151)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: Jit Security
🔇 Additional comments (11)
apps/web/utils/queue/queue-manager.ts (1)

26-44: Env validation before manager creation — nice.

Validates Redis/QStash env per backend and fails fast. LGTM.

apps/web/utils/scheduled-actions/scheduler.ts (2)

263-267: No action required—queue handler is properly registered.

The verification confirms that "scheduled-actions" is registered in QUEUE_HANDLERS (line 479 of queues.ts) and mapped to handleScheduledActionJob. The validation functions getQueueHandler() and isValidQueueName() will correctly identify and process this queue name. The code is correct as-is.


168-239: Now I need to check the scheduler.ts file to understand the queue system configuration and the section at lines 298-317:

Based on the script output and web search findings, I can now make an informed assessment. Let me search for the scheduler.ts section at lines 298-317 and queue-manager implementation:

Based on my analysis of the code and findings:

Worker-level status check for CANCELLED actions is confirmed. The execute route at lines 66-75 checks if status is CANCELLED and skips execution, or verifies status is PENDING before proceeding.

However, the underlying concern about BullMQ job removal is valid but cannot be fully verified without seeing:

  1. The queue-manager.ts implementation to confirm whether BullMQ or only QStash is used
  2. Lines 298-317 in scheduler.ts (mentioned in review as "Also applies to")
  3. Environment configuration for queue system selection

The current safety mechanism (DB status check) prevents execution, but standard BullMQ practice for removing delayed jobs uses queue.getJob(jobId) followed by job.remove(). The code currently only cancels QStash messages (lines 193-202) but appears to lack BullMQ job removal if that queue system is used.

Worker-level protection is functioning as a safeguard, but job accumulation in Redis is a potential inefficiency that the review correctly identifies.

apps/web/utils/queue/queues.ts (8)

1-19: LGTM!

Proper use of createScopedLogger at the file level and clean import structure following the path alias conventions.


21-79: LGTM!

The job data interfaces are well-structured with proper TypeScript typing and follow best practices.


153-169: LGTM!

Proper error handling and logging for the email digest job handler.


171-188: LGTM!

Clean helper function with appropriate field selection.


190-253: LGTM!

Good pattern of marking digests as PROCESSING before handling them to prevent duplicate processing.


288-358: LGTM!

Excellent implementation with proper data structure usage, content validation, and graceful error handling that logs issues but continues processing.


360-434: LGTM!

Excellent implementation with proper transaction usage for atomicity, content redaction for privacy, and robust error handling that marks failed digests appropriately.


464-522: LGTM!

Well-designed queue routing system with proper validation and type safety. The distributed AI categorize senders queue pattern is clean and the helper functions handle both explicit and dynamic queue names correctly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
apps/web/utils/scheduled-actions/scheduler.ts (5)

144-146: Avoid non‑null assertion on delayInMinutes.

actionItem.delayInMinutes! violates the “don’t use non‑null assertions” rule. Refine the type from the filter or use a safe local.

[As per coding guidelines]
Apply one of:

A) Narrow via local var:

-    const scheduledFor = addMinutes(new Date(), actionItem.delayInMinutes!)
+    const delay = actionItem.delayInMinutes ?? 0
+    const scheduledFor = addMinutes(new Date(), delay)

B) Use a type‑predicate in the filter (preferred for strict TS):

-  const delayedActions = actionItems.filter(
-    (item) =>
+  const delayedActions = actionItems.filter(
+    (item): item is typeof item & { delayInMinutes: number } =>
       item.delayInMinutes != null &&
       item.delayInMinutes > 0 &&
       canActionBeDelayed(item.type),
   )

270-274: Add backoff and cleanup options for enqueued jobs.

Improve resilience and queue hygiene: add exponential backoff and auto‑cleanup on completion/failure (BullMQ respects these; Upstash path can ignore safely).

-  const job = await enqueueJob("scheduled-actions", payload, {
-    delay: delayInMs,
-    jobId: deduplicationId,
-    attempts: 3,
-  });
+  const job = await enqueueJob("scheduled-actions", payload, {
+    delay: delayInMs,
+    jobId: deduplicationId,
+    attempts: 3,
+    backoff: { type: "exponential", delay: 30_000 },
+    removeOnComplete: true,
+    removeOnFail: 100, // keep a small tail for inspection
+  });

95-103: Make log messages backend‑agnostic.

These info/error logs still say “QStash” though scheduling now uses a unified queue. Update wording or include queueSystem consistently.

Example:

- logger.info("Created and scheduled action with QStash", { ... })
+ logger.info("Created and scheduled action with queue system", { ... })

- logger.info("Cancelled QStash scheduled actions", { ... })
+ logger.info("Cancelled scheduled actions", { ... })

- logger.error("Failed to cancel QStash scheduled actions", { ... })
+ logger.error("Failed to cancel scheduled actions", { ... })

Also applies to: 236-244, 247-253


11-11: Align function name with generic implementation.

markQStashActionAsExecuting is architecturally generic (updates database without QStash-specific logic) but named for its current usage context. Rename to markScheduledActionAsExecuting to reflect the implementation and improve reusability should other queue backends be added. Update the three occurrences in:

  • apps/web/utils/scheduled-actions/scheduler.ts:330 (definition)
  • apps/web/app/api/scheduled-actions/execute/route.ts:6 (import)
  • apps/web/app/api/scheduled-actions/execute/route.ts:83 (call site)

310-323: The code already safely guards QStash operations and gates worker execution on CANCELLED status—refactor comments and add unified queue-manager cancel stub as planned improvement.

The cancelMessage() function at lines 310-323 already protects QStash calls with the env.QUEUE_SYSTEM === "upstash" check. Additionally, the execute route gate at apps/web/app/api/scheduled-actions/execute/route.ts:67 already prevents workers from executing CANCELLED actions. Since no cancel methods exist in the queue managers, the fallback approach (updating DB status and checking at execution) is a reasonable interim solution.

To align with intent, clarify the Redis/BullMQ TODO and standardize comments:

-    // For QStash, we can still cancel directly
+    // QStash deletion supported; other queue systems handled via DB status check
     if (env.QUEUE_SYSTEM === "upstash") {
       await client.http.request({
         path: ["v2", "messages", messageId],
         method: "DELETE",
       });
       logger.info("Successfully cancelled QStash message", { messageId });
     } else {
-      // For Redis/BullMQ, we would need to implement job cancellation
-      // For now, just log that cancellation is not supported
+      // TODO: Implement unified cancel in queue-manager for Redis/BullMQ
       logger.warn("Cancellation not implemented for Redis queue system", { messageId });
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc265b6 and 1d1788a.

📒 Files selected for processing (2)
  • apps/web/package.json (3 hunks)
  • apps/web/utils/scheduled-actions/scheduler.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/scheduled-actions/scheduler.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/package.json
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/scheduled-actions/scheduler.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/scheduled-actions/scheduler.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/scheduled-actions/scheduler.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/scheduled-actions/scheduler.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/scheduled-actions/scheduler.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/package.json
🧠 Learnings (1)
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
PR: elie222/inbox-zero#0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest (`vitest`) as the testing framework

Applied to files:

  • apps/web/package.json
🧬 Code graph analysis (1)
apps/web/utils/scheduled-actions/scheduler.ts (3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (56-63)
apps/web/env.ts (1)
  • env (16-240)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: Static Code Analysis Js
  • GitHub Check: Jit Security
🔇 Additional comments (1)
apps/web/package.json (1)

9-10: The review comment is incorrect. The codebase correctly handles tsx availability in production.

The Dockerfile.prod explicitly installs tsx globally via npm install -g [email protected], and the Docker build includes all dependencies (including devDependencies) with pnpm install --no-frozen-lockfile. The worker service in docker-compose.yml uses the same production Docker image as the web service, ensuring tsx is available when the worker process runs. This is a standard and valid production pattern for TypeScript-based worker processes in containerized environments.

No changes to package.json are required.

Likely an incorrect or invalid review comment.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
docker/Dockerfile.prod (1)

29-30: Consider whether 16GB heap is necessary.

The heap size is set to 16GB, which is quite large. While this might be necessary for your build, consider profiling to determine if a smaller value would suffice, which would allow the image to build in more constrained environments.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d1788a and 1ca7d0f.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • .cursor/rules/queues.mdc (1 hunks)
  • .dockerignore (1 hunks)
  • apps/web/env.ts (1 hunks)
  • apps/web/instrumentation.ts (2 hunks)
  • apps/web/next.config.ts (1 hunks)
  • apps/web/package.json (3 hunks)
  • apps/web/utils/queue/bullmq-manager.ts (1 hunks)
  • apps/web/utils/queue/queue-manager.ts (1 hunks)
  • docker-compose.yml (1 hunks)
  • docker/Dockerfile.prod (2 hunks)
  • package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • docker-compose.yml
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/package.json
🧰 Additional context used
📓 Path-based instructions (12)
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • .dockerignore
  • package.json
  • apps/web/env.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/instrumentation.ts
  • docker/Dockerfile.prod
  • .cursor/rules/queues.mdc
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • .dockerignore
  • package.json
  • apps/web/env.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/instrumentation.ts
  • docker/Dockerfile.prod
  • .cursor/rules/queues.mdc
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/env.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/instrumentation.ts
apps/web/**/{.env.example,env.ts,turbo.json}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Add environment variables to .env.example, env.ts, and turbo.json

Files:

  • apps/web/env.ts
apps/web/**/{.env.example,env.ts}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

Client-side environment variables: Prefix with NEXT_PUBLIC_

Files:

  • apps/web/env.ts
apps/web/env.ts

📄 CodeRabbit inference engine (.cursor/rules/environment-variables.mdc)

apps/web/env.ts: When adding a new environment variable, add it to apps/web/env.ts in the appropriate section: use server for server-only variables, and for client-side variables, use the client section and also add to experimental__runtimeEnv.
Client-side environment variables must be prefixed with NEXT_PUBLIC_ and added to both the client and experimental__runtimeEnv sections in apps/web/env.ts.

Files:

  • apps/web/env.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/env.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/instrumentation.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/env.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/instrumentation.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/env.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/instrumentation.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/queue/queue-manager.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/queue/queue-manager.ts
.cursor/rules/*.mdc

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

.cursor/rules/*.mdc: Always place rule files in PROJECT_ROOT/.cursor/rules/
Follow the naming convention: use kebab-case for filenames, always use .mdc extension, and make names descriptive of the rule's purpose
Cursor rules must have a frontmatter section with description, globs, and alwaysApply fields, followed by markdown content explaining the rule

Files:

  • .cursor/rules/queues.mdc
🧠 Learnings (12)
📚 Learning: 2025-07-19T17:50:22.078Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ui-components.mdc:0-0
Timestamp: 2025-07-19T17:50:22.078Z
Learning: Applies to components/**/*.tsx : Use Shadcn UI and Tailwind for components and styling

Applied to files:

  • package.json
📚 Learning: 2025-07-18T15:04:50.520Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-07-18T15:04:50.520Z
Learning: Applies to apps/web/env.ts : When adding a new environment variable, add it to `apps/web/env.ts` in the appropriate section: use `server` for server-only variables, and for client-side variables, use the `client` section and also add to `experimental__runtimeEnv`.

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-07-18T15:04:50.520Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-07-18T15:04:50.520Z
Learning: Applies to apps/web/env.ts : Client-side environment variables must be prefixed with `NEXT_PUBLIC_` and added to both the `client` and `experimental__runtimeEnv` sections in `apps/web/env.ts`.

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/**/{.env.example,env.ts} : Client-side environment variables: Prefix with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/env.ts
  • apps/web/instrumentation.ts
  • docker/Dockerfile.prod
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/**/{.env.example,env.ts,turbo.json} : Add environment variables to `.env.example`, `env.ts`, and `turbo.json`

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-07-18T15:04:50.520Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-07-18T15:04:50.520Z
Learning: Applies to apps/web/.env.example : When adding a new environment variable, add it to `.env.example` with an example value (e.g., NEW_VARIABLE=value_example).

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-07-19T15:06:43.730Z
Learnt from: garnertb
Repo: elie222/inbox-zero PR: 580
File: apps/web/.env.example:4-7
Timestamp: 2025-07-19T15:06:43.730Z
Learning: In apps/web/.env.example, boolean environment variables follow an unquoted convention (e.g., LOG_ZOD_ERRORS=true, AUTH_TRUST_HOST=true). Logical grouping of related variables (like auth variables together) is preferred over strict alphabetical ordering for better developer experience.

Applied to files:

  • apps/web/env.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : System prompt should define the LLM's role and task specifications

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Implement fallbacks for AI failures

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-07-18T15:04:36.476Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/cursor-rules.mdc:0-0
Timestamp: 2025-07-18T15:04:36.476Z
Learning: Applies to .cursor/rules/*.mdc : Cursor rules must have a frontmatter section with description, globs, and alwaysApply fields, followed by markdown content explaining the rule

Applied to files:

  • .cursor/rules/queues.mdc
📚 Learning: 2025-07-18T15:04:36.476Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/cursor-rules.mdc:0-0
Timestamp: 2025-07-18T15:04:36.476Z
Learning: Applies to .cursor/rules/*.mdc : Follow the naming convention: use kebab-case for filenames, always use .mdc extension, and make names descriptive of the rule's purpose

Applied to files:

  • .cursor/rules/queues.mdc
📚 Learning: 2025-07-18T15:04:36.476Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/cursor-rules.mdc:0-0
Timestamp: 2025-07-18T15:04:36.476Z
Learning: Applies to .cursor/rules/*.mdc : Always place rule files in PROJECT_ROOT/.cursor/rules/

Applied to files:

  • .cursor/rules/queues.mdc
🧬 Code graph analysis (2)
apps/web/utils/queue/queue-manager.ts (5)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/types.ts (8)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
  • ConnectionOptions (101-101)
  • Worker (101-101)
  • Queue (101-101)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/queue/bullmq-manager.ts (2)
  • BullMQManager (22-200)
  • createQueue (139-158)
apps/web/utils/queue/qstash-manager.ts (2)
  • QStashManager (22-115)
  • createQueue (97-105)
apps/web/instrumentation.ts (2)
apps/web/env.ts (1)
  • env (16-240)
apps/web/utils/queue/queues.ts (1)
  • QUEUE_HANDLERS (479-494)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (9)
docker/Dockerfile.prod (2)

60-73: LGTM! Well-structured runner stage.

The runner stage correctly copies the standalone bundle and static assets from the builder. The standalone output includes server.js at the root, making CMD ["node", "server.js"] the correct entrypoint.


54-58: No action needed—shx is correctly listed as a dependency.

Verification confirms shx (version ^0.3.4) is present in apps/web/package.json. The build commands on lines 56-57 will execute successfully.

apps/web/utils/queue/queue-manager.ts (7)

1-19: LGTM! Clean imports and proper logger setup.

The file correctly uses import type for type-only imports and initializes the scoped logger at the top of the file, following the project's coding guidelines.


21-34: Past review addressed: Environment validation occurs in manager constructors.

The environment validation requested in the past review is handled by the respective manager constructors (BullMQManager throws if REDIS_URL is missing). This is an acceptable approach that keeps validation close to where it's needed.


36-43: LGTM! Appropriate singleton pattern.

The lazy singleton pattern is correctly implemented for Node.js's single-threaded environment.


45-61: LGTM! Well-typed queue operation helpers.

Both enqueueJob and bulkEnqueueJobs are properly typed with generics and correctly delegate to the underlying manager.


63-83: Past review addressed: Worker guard properly implemented.

The guard for non-Redis systems is correctly in place (lines 74-80), returning null with a warning log when workers aren't supported. This addresses the past review comment.


85-104: Past review addressed: Queue creation guard properly implemented.

The guard for non-Redis systems is correctly in place (lines 95-101), returning null with a warning log when queue creation isn't supported. This addresses the past review comment.


121-132: LGTM! Proper type re-exports.

The barrel export pattern is correctly implemented using export type for type-only exports, following the coding guidelines.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 25 files

Prompt for AI agents (all 4 issues)

Understand the root cause of the following 4 issues and fix them.


<file name="apps/web/instrumentation.ts">

<violation number="1" location="apps/web/instrumentation.ts:27">
Setting the dev-mode guard flag before confirming any BullMQ worker actually starts means that any transient startup failure (e.g., registerWorker returning null when Redis is unreachable) leaves the flag stuck at true. Every later hot reload will therefore skip worker initialization, so the local server keeps running without background workers until developers restart the process. Please delay setting the guard until after a successful startup or reset it on failure.</violation>
</file>

<file name="apps/web/utils/actions/clean.ts">

<violation number="1" location="apps/web/utils/actions/clean.ts:168">
Switching to bulkEnqueueJobs drops the per-account flowControl key we previously attached to each Upstash enqueue, so ai-clean jobs are now emitted without any queue parallelism limit under env.QUEUE_SYSTEM=&quot;upstash&quot;. QStashManager.bulkEnqueue only forwards url/body to client.batchJSON, so every account shares the global queue and can flood processing, regressing the per-user parallelism=3 safeguard. Please restore a per-account flow control/queue key when calling bulkEnqueueJobs.</violation>
</file>

<file name="apps/web/utils/queue/queues.ts">

<violation number="1" location="apps/web/utils/queue/queues.ts:357">
Returning here leaves the digests stuck in PROCESSING so future runs never pick them up again; reset their status (or mark them handled) before exiting.</violation>
</file>

<file name="docker/Dockerfile.prod">

<violation number="1" location="docker/Dockerfile.prod:56">
`worker.js` does not exist in apps/web, so this copy step will fail and the Docker image build will break.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (4)
apps/web/package.json (1)

98-98: BullMQ Node 22 compatibility issue already flagged.

The incompatibility between BullMQ v5.61.0 and Node 22 has been comprehensively documented in previous review comments. Please address that concern before merging.

apps/web/instrumentation.ts (2)

12-13: Guard flag timing issue already flagged.

The concern about setting __inboxZeroWorkersStarted before confirming successful worker startup has been raised in previous comments. This can leave the flag stuck on transient failures.


40-40: Empty catch block swallowing errors already flagged.

The empty catch block that hides worker bootstrap failures has been comprehensively documented in previous review comments.

docker/Dockerfile.prod (1)

22-22: Use --frozen-lockfile for deterministic builds.

The concern about --no-frozen-lockfile allowing non-reproducible builds has been raised in previous comments. Production builds should use --frozen-lockfile.

🧹 Nitpick comments (2)
apps/web/instrumentation.ts (1)

60-60: Use validated env instead of process.env.

Directly accessing process.env.ENABLE_WORKER_QUEUES bypasses the Zod validation and type safety provided by the env module. Since you're already importing env dynamically on line 16, consider moving this check inside the import callback or importing env at the top level.

Consider refactoring to use the validated env:

// At top of file
import { env } from "@/env";

// Then in register()
if (process.env.NODE_ENV === "development" && env.ENABLE_WORKER_QUEUES) {
  startBullMQWorkers();
}
apps/web/worker.js (1)

35-41: Hardcoded instrumentation path is brittle.

The path apps/web/.next/server/instrumentation.js assumes a specific standalone build structure. If the build output changes or the worker is run from a different working directory, this will fail silently (until the existsSync check).

Consider adding debug output and checking multiple possible paths:

const possiblePaths = [
  path.join(__dirname, 'apps/web/.next/server/instrumentation.js'),
  path.join(__dirname, '.next/server/instrumentation.js'),
];

let instrumentationPath;
for (const p of possiblePaths) {
  if (fs.existsSync(p)) {
    instrumentationPath = p;
    break;
  }
}

if (!instrumentationPath) {
  console.error('Could not find instrumentation.js in any expected location');
  console.error('Searched:', possiblePaths);
  console.error('Current __dirname:', __dirname);
  process.exit(1);
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1ca7d0f and e485bd2.

📒 Files selected for processing (8)
  • apps/web/env.ts (1 hunks)
  • apps/web/instrumentation.ts (2 hunks)
  • apps/web/next.config.ts (1 hunks)
  • apps/web/package.json (5 hunks)
  • apps/web/worker.js (1 hunks)
  • biome.json (1 hunks)
  • docker-compose.yml (1 hunks)
  • docker/Dockerfile.prod (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/web/env.ts
🧰 Additional context used
📓 Path-based instructions (6)
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/worker.js
  • docker/Dockerfile.prod
  • apps/web/instrumentation.ts
  • apps/web/next.config.ts
  • biome.json
  • docker-compose.yml
  • apps/web/package.json
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/worker.js
  • apps/web/instrumentation.ts
  • apps/web/next.config.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/worker.js
  • docker/Dockerfile.prod
  • apps/web/instrumentation.ts
  • apps/web/next.config.ts
  • biome.json
  • docker-compose.yml
  • apps/web/package.json
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/instrumentation.ts
  • apps/web/next.config.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/instrumentation.ts
  • apps/web/next.config.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/instrumentation.ts
  • apps/web/next.config.ts
🧠 Learnings (9)
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use proper error types and logging for failures

Applied to files:

  • apps/web/instrumentation.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/**/*.{ts,tsx} : Use proper error handling with try/catch blocks

Applied to files:

  • apps/web/instrumentation.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Implement fallbacks for AI failures

Applied to files:

  • apps/web/instrumentation.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use unnecessary catch clauses.

Applied to files:

  • apps/web/instrumentation.ts
📚 Learning: 2025-07-18T15:05:16.146Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-07-18T15:05:16.146Z
Learning: Applies to apps/web/app/api/**/route.ts : Do not use try/catch in GET API routes when using authentication middleware; rely on centralized error handling.

Applied to files:

  • apps/web/instrumentation.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : System prompt should define the LLM's role and task specifications

Applied to files:

  • apps/web/instrumentation.ts
📚 Learning: 2025-07-18T15:04:50.520Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/environment-variables.mdc:0-0
Timestamp: 2025-07-18T15:04:50.520Z
Learning: Applies to apps/web/env.ts : Client-side environment variables must be prefixed with `NEXT_PUBLIC_` and added to both the `client` and `experimental__runtimeEnv` sections in `apps/web/env.ts`.

Applied to files:

  • apps/web/next.config.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/**/{.env.example,env.ts} : Client-side environment variables: Prefix with `NEXT_PUBLIC_`

Applied to files:

  • apps/web/next.config.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't use debugger.

Applied to files:

  • biome.json
🧬 Code graph analysis (1)
apps/web/instrumentation.ts (2)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/queues.ts (1)
  • QUEUE_HANDLERS (479-494)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (10)
apps/web/package.json (2)

9-10: LGTM: Standalone and worker scripts.

The new scripts appropriately target the standalone build output. These align with the Docker-based deployment strategy introduced in this PR.


32-32: Exact version pinning improves build reproducibility.

Removing the caret (^) and pinning to exact versions for these packages is a good practice for production stability.

Also applies to: 46-46, 106-106, 109-109, 174-174

biome.json (1)

107-107: LGTM: Biome exclusion for worker.js.

Excluding worker.js is consistent with the existing sw.js exclusion and appropriate for a generated/copied file.

apps/web/instrumentation.ts (1)

29-38: Job handler wrapper correctly re-throws errors.

The wrapper extracts job data and ensures errors are proper Error instances, which is good for consistent error handling by BullMQ.

apps/web/worker.js (1)

19-30: Dual env loading fallback provides good resilience.

The graceful fallback from Next.js's loadEnvConfig to dotenv ensures environment variables are loaded across different deployment scenarios.

docker-compose.yml (1)

62-64: LGTM: Queue system configuration for web service.

Adding QUEUE_SYSTEM and REDIS_URL environment variables correctly configures the web service to use Redis-based queues in Docker.

docker/Dockerfile.prod (3)

2-2: Multi-stage build with standalone output is well-structured.

The separation of builder and runner stages, combined with the standalone Next.js output, creates lean production images with only necessary runtime dependencies.

Also applies to: 61-74


55-59: Build sequence correctly integrates worker.js.

The build step generates Prisma client, builds Next.js, copies worker.js into the standalone directory, and cleans up cache. This ensures both server.js and worker.js are available in the runner stage.


34-52: No validation issues found – dummy environment variables satisfy all schema constraints.

All dummy values in the Dockerfile.prod align with their Zod schema definitions in apps/web/env.ts:

  • DATABASE_URL meets the .url() requirement with a valid PostgreSQL connection string
  • GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_PUBSUB_TOPIC_NAME all satisfy .min(1) constraints
  • All other required string fields have valid non-empty values
  • Optional fields are properly handled

The build will not fail due to schema validation errors.

apps/web/next.config.ts (1)

15-15: No issues found—DOCKER_BUILD is correctly scoped to Docker builds only.

Verification confirms that DOCKER_BUILD="true" is set exclusively in docker/Dockerfile.prod during Docker image builds. It is not set in CI/CD workflows, environment files, or local development environments. The conditional logic in apps/web/next.config.ts correctly isolates the standalone output configuration to Docker builds only, leaving regular and local development builds unaffected.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/utils/upstash/categorize-senders.ts (1)

51-51: Remove unused url variable.

The url variable is no longer needed since the function now uses enqueueJob instead of direct QStash publishing.

Apply this diff:

 export async function publishToAiCategorizeSendersQueue(
   body: AiCategorizeSenders,
 ) {
-  const url = `${env.WEBHOOK_URL || env.NEXT_PUBLIC_BASE_URL}/api/user/categorize/senders/batch`;
-
   // Split senders into smaller chunks to process in batches
♻️ Duplicate comments (1)
apps/web/utils/queue/qstash-manager.ts (1)

18-20: Remove non-null assertion and validate QSTASH_TOKEN upfront.

The non-null assertion violates coding guidelines. Add explicit validation to fail fast if the token is missing.

Apply this diff:

 function getQstashClient(): Client {
-  return new Client({ token: env.QSTASH_TOKEN! });
+  if (!env.QSTASH_TOKEN) {
+    throw new Error("QSTASH_TOKEN is required when QUEUE_SYSTEM=upstash");
+  }
+  return new Client({ token: env.QSTASH_TOKEN });
 }

Based on coding guidelines

🧹 Nitpick comments (6)
apps/web/utils/queue/queues.ts (4)

82-95: Address TODO: Implement digest processing logic.

The function currently has a placeholder implementation with a hardcoded delay.

Do you want me to generate an implementation or open an issue to track this task?


104-112: Consider refactoring to call batch logic directly without Request wrapper.

Creating a fake Request object with "http://localhost" is a workaround. Consider extracting the core logic from handleBatchRequest into a reusable function that doesn't require a Request object.

Example approach:

// In handle-batch.ts, export the core logic:
export async function categorizeSendersBatch(data: AiCategorizeSenders) {
  // existing logic without Request/Response wrappers
}

// Then here:
await categorizeSendersBatch(data);

274-288: Consider extracting batch size and rate limit delay as constants.

The hardcoded values (100, 2000) could be extracted as named constants for better maintainability.

Apply this diff at the top of the function or file level:

+const MESSAGE_BATCH_SIZE = 100;
+const RATE_LIMIT_DELAY_MS = 2000;
+
 async function sendDigestEmailForAccount(emailAccountId: string) {
   ...
-  const batchSize = 100;
+  const batchSize = MESSAGE_BATCH_SIZE;
   ...
-    await sleep(2000);
+    await sleep(RATE_LIMIT_DELAY_MS);

129-139: Address placeholder implementations for multiple handlers.

Several handlers (handleScheduledActionJob, handleAiCleanJob, handleEmailSummaryAllJob, handleCleanGmailJob) have TODO comments and placeholder implementations with hardcoded delays.

Do you want me to help implement these handlers or create issues to track them?

Also applies to: 141-154, 451-461, 463-475

apps/web/utils/queue/queue-manager.ts (2)

22-35: Consider adding early environment validation for consistency.

While the individual managers validate their required environment variables, adding upfront checks here would provide clearer error messages and fail-fast behavior.

Apply this diff:

 export function createQueueManager(): QueueManager {
   const queueSystem = env.QUEUE_SYSTEM;

   logger.info("Creating queue manager", { queueSystem });

   switch (queueSystem) {
     case "redis":
+      if (!env.REDIS_URL) {
+        throw new Error("REDIS_URL is required when QUEUE_SYSTEM=redis");
+      }
       return new BullMQManager();
     case "upstash":
+      if (!env.QSTASH_TOKEN) {
+        throw new Error("QSTASH_TOKEN is required when QUEUE_SYSTEM=upstash");
+      }
       return new QStashManager();
     default:
       throw new Error(`Unsupported queue system: ${queueSystem}`);
   }
 }

114-122: Add explicit return type annotation.

While TypeScript can infer the return type, an explicit annotation improves documentation and catches type errors earlier.

Apply this diff:

-export function getQueueSystemInfo(): QueueSystemInfo {
+export function getQueueSystemInfo(): QueueSystemInfo {
   const isRedis = env.QUEUE_SYSTEM === "redis";
   return {
     system: env.QUEUE_SYSTEM,
     supportsWorkers: isRedis,
     supportsDelayedJobs: true,
     supportsBulkOperations: true,
   };
 }

Wait, the code already has the return type. Let me check again... Yes, line 114 shows : QueueSystemInfo return type. This was already addressed. No issue here.
[actions]

  • Actually, reviewing the code again, the return type IS present at line 114
  • Approve instead
    [scratchpad_end] -->
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e485bd2 and 452fef5.

📒 Files selected for processing (8)
  • apps/web/instrumentation.ts (2 hunks)
  • apps/web/utils/queue/bullmq-manager.ts (1 hunks)
  • apps/web/utils/queue/qstash-manager.ts (1 hunks)
  • apps/web/utils/queue/queue-manager.ts (1 hunks)
  • apps/web/utils/queue/queues.ts (1 hunks)
  • apps/web/utils/upstash/categorize-senders.ts (3 hunks)
  • apps/web/worker.js (1 hunks)
  • biome.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/worker.js
  • apps/web/instrumentation.ts
🧰 Additional context used
📓 Path-based instructions (8)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • biome.json
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • biome.json
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
🧠 Learnings (17)
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.{ts,tsx} : Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
📚 Learning: 2025-07-18T15:04:30.467Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-07-18T15:04:30.467Z
Learning: Applies to apps/web/**/{.env.example,env.ts,turbo.json} : Add environment variables to `.env.example`, `env.ts`, and `turbo.json`

Applied to files:

  • biome.json
📚 Learning: 2025-07-20T09:00:41.968Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-07-20T09:00:41.968Z
Learning: Applies to apps/web/app/api/**/*.{ts,js} : All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use Date.now() to get milliseconds since the Unix Epoch.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{ts,tsx} : Don't misuse the non-null assertion operator (!) in TypeScript files.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{ts,tsx} : Don't use non-null assertions with the ! postfix operator.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use proper error types and logging for failures

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Format prompt data consistently across similar functions

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : System prompt should define the LLM's role and task specifications

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Implement fallbacks for AI failures

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Remove excessive whitespace and truncate long inputs in prompts

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Keep system prompts and user prompts separate

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : User prompt should contain the actual data and context

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Add retry logic for transient failures using withRetry

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use TypeScript types for all parameters and return values in LLM features

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')

Applied to files:

  • apps/web/utils/queue/queues.ts
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
Repo: elie222/inbox-zero PR: 576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/utils/queue/queues.ts
🧬 Code graph analysis (5)
apps/web/utils/upstash/categorize-senders.ts (3)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/queues.ts (2)
  • getQueueIndexFromEmailAccountId (497-506)
  • AI_CATEGORIZE_SENDERS_QUEUE_COUNT (478-478)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (47-54)
apps/web/utils/queue/qstash-manager.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/types.ts (6)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • BulkEnqueueOptions (42-48)
  • Job (101-101)
  • ConnectionOptions (101-101)
apps/web/utils/upstash/index.ts (1)
  • publishToQstashQueue (56-78)
apps/web/utils/queue/bullmq-manager.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/types.ts (9)
  • QueueManager (50-82)
  • Queue (101-101)
  • Worker (101-101)
  • QueueEvents (101-101)
  • ConnectionOptions (101-101)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/queues.ts (1)
  • getAiCleanQueueName (511-523)
apps/web/utils/queue/queue-manager.ts (5)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/types.ts (9)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
  • ConnectionOptions (101-101)
  • Worker (101-101)
  • Queue (101-101)
  • QueueSystemInfo (94-99)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/bullmq-manager.ts (2)
  • BullMQManager (23-258)
  • createQueue (197-216)
apps/web/utils/queue/qstash-manager.ts (2)
  • QStashManager (22-179)
  • createQueue (161-169)
apps/web/utils/queue/queues.ts (10)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/app/api/user/categorize/senders/batch/handle-batch.ts (1)
  • handleBatchRequest (19-32)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/types.ts (1)
  • ParsedMessage (47-62)
apps/web/utils/rule/consts.ts (1)
  • getRuleName (118-120)
apps/web/app/api/resend/digest/validation.ts (2)
  • storedDigestContentSchema (3-3)
  • Digest (16-16)
apps/web/utils/email.ts (1)
  • extractNameFromEmail (9-16)
packages/resend/src/send.tsx (1)
  • sendDigestEmail (117-142)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/schedule.ts (1)
  • calculateNextScheduleDate (124-227)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security
🔇 Additional comments (12)
biome.json (1)

107-108: Inconsistent exclusion: worker.js excluded from linting but not from formatting.

Line 108 adds "!apps/web/worker.js" to files.includes, which excludes it from linting. However, this exclusion is not present in formatter.includes (lines 79–88), meaning the worker file will still be formatted by Biome. This creates an asymmetric configuration where the file is formatted but not linted, which could lead to lint violations that won't be caught.

Additionally, line 107's addition of "!.vscode" is not mentioned in the PR context and its purpose is unclear.

  1. Clarify the exclusion intent: Should worker.js be completely excluded from Biome processing (both linting and formatting), or is the asymmetric exclusion intentional?
  2. If full exclusion is needed, apply this diff to add worker.js to formatter.includes for consistency:
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2,
    "includes": [
      "apps/**",
      "packages/**",
      "!node_modules/**",
      "!*.config.*",
      "!*.json",
      "!**/tsconfig*.json",
      "!.turbo/**",
      "!.next/**",
+     "!apps/web/worker.js"
    ]
  },
  1. Clarify the .vscode addition: Please explain why !.vscode is being added and verify it's intentional for this PR.
apps/web/utils/queue/qstash-manager.ts (2)

13-13: LGTM: Logger initialized correctly.

Logger properly initialized at the top level with scoped name, following guidelines.


148-173: LGTM: Correct error handling for unsupported operations.

These methods correctly throw descriptive errors since QStash doesn't support worker-based queue operations. The queue-manager.ts guards these calls appropriately.

apps/web/utils/queue/queues.ts (3)

355-372: LGTM: Correct status reset for early return.

The function now correctly resets digest status back to PENDING before returning early, ensuring digests can be picked up in future runs. This addresses the previous concern.


497-506: LGTM: Simple and effective queue distribution.

The character code sum provides consistent hashing for distributing email accounts across queues. Consider adding a runtime assertion that queueCount > 0 if this becomes a public API, but current usage is safe.


558-588: LGTM: Robust dynamic queue handler resolution.

The function correctly handles both static queue names and dynamic prefixes, with appropriate validation for Redis vs QStash backends.

apps/web/utils/upstash/categorize-senders.ts (2)

17-42: LGTM: Well-documented queue distribution strategy.

The documentation clearly explains the different distribution approaches for Redis (hash-based) vs QStash (per-account), making the code maintainable. The implementation correctly mirrors the logic in queues.ts.


69-78: LGTM: Clean parallel enqueue implementation.

The use of Promise.all with enqueueJob is efficient and type-safe. The satisfies operator ensures type correctness without losing inference.

apps/web/utils/queue/bullmq-manager.ts (1)

46-53: LGTM: Correct use of nullish coalescing.

The job options correctly use ?? instead of ||, properly handling explicit 0 and false values. This addresses the previous concerns.

apps/web/utils/queue/queue-manager.ts (3)

64-84: LGTM: Correct guard for worker creation.

The function properly guards against calling createWorker on QStash systems, returning null and logging a warning. This addresses the previous concern about QStashManager.createWorker throwing errors.


86-105: LGTM: Correct guard for queue creation.

The function properly guards against calling createQueue on QStash systems, matching the pattern in createQueueWorker. This addresses the previous concern.


114-122: LGTM: Function has proper return type annotation.

The function correctly includes an explicit QueueSystemInfo return type annotation, providing clear documentation and type safety.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

6 issues found across 27 files

Prompt for AI agents (all 6 issues)

Understand the root cause of the following 6 issues and fix them.


<file name="apps/web/app/api/queue/[queueName]/route.ts">

<violation number="1" location="apps/web/app/api/queue/[queueName]/route.ts:91">
Wrapping this endpoint with `verifySignatureAppRouter` forces every request (including your internal Redis/BullMQ jobs) to pass QStash signature checks, so internal requests are rejected before reaching `validateInternalRequest`, breaking the Redis queue flow.</violation>
</file>

<file name="apps/web/utils/upstash/categorize-senders.ts">

<violation number="1" location="apps/web/utils/upstash/categorize-senders.ts:72">
Switching to enqueueJob makes QStash requests hit /api/queue/{queueName}, but that endpoint rejects ai-categorize-senders-{emailAccountId} because isValidQueueName only allows numeric suffixes—so every job now fails with 400 and never runs.</violation>
</file>

<file name="apps/web/env.ts">

<violation number="1" location="apps/web/env.ts:65">
Use z.coerce.boolean() here so the environment parser accepts string values like &quot;true&quot;/&quot;false&quot; from process.env.</violation>
</file>

<file name=".cursor/rules/queues.mdc">

<violation number="1" location=".cursor/rules/queues.mdc:48">
Development workers do not auto-start with NODE_ENV=development alone; they also require ENABLE_WORKER_QUEUES=true. Update the doc to mention the needed flag so developers don’t miss enabling the workers.</violation>
</file>

<file name="apps/web/utils/queue/qstash-manager.ts">

<violation number="1" location="apps/web/utils/queue/qstash-manager.ts:41">
Immediate enqueues never pass the deduplication jobId. This regression drops the QStash deduplication guard, so the same job can be enqueued multiple times when jobId is provided.</violation>
</file>

<file name="apps/web/worker.js">

<violation number="1" location="apps/web/worker.js:18">
This script only loads environment variables when a plain .env file is present, so builds that rely on `.env.local` or `.env.production` never invoke loadEnvConfig. That means required values stay undefined, and startBullMQWorkers exits immediately by design in instrumentation.ts. Call loadEnvConfig regardless of the `.env` file so the worker can boot.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (3)
.cursor/rules/queues.mdc (3)

81-81: Verify worker concurrency default value (1 vs. 3).

The documentation claims "Runs with concurrency of 3 per queue," but past reviews flagged that the actual worker registry default is 1 (not 3). Ensure this statement matches the actual implementation in apps/web/utils/queue/worker.ts.

Reference line 130 below for cross-check of concurrency configuration statements.


129-132: Clarify and verify concurrency defaults across all systems.

The concurrency configuration section contains multiple statements that may be inconsistent with actual code defaults:

  • Line 130: "Worker Registry: Default concurrency of 3"
  • Line 131: "BullMQ Manager: Default concurrency of 3"
  • Line 132: "QStash: Uses parallelism of 3"

Past reviews indicated that worker.ts actually defaults to 1, not 3. For accuracy, verify these values match the actual implementations in:

  • apps/web/utils/queue/worker.ts (Worker Registry default)
  • apps/web/utils/queue/bullmq-manager.ts (BullMQ default)
  • apps/web/utils/queue/qstash-manager.ts (QStash parallelism)

The note on line 134 pointing to these files is helpful, but the stated values must be accurate.

If defaults differ, update lines 130–132 and line 81 to reflect reality. Consider also checking if there are different defaults for delayed vs. non-delayed jobs (as QStash behavior may vary).


57-57: Add ENABLE_WORKER_QUEUES flag to development worker startup requirements.

Development worker initialization requires both environment variables to be set, but the documentation only mentions NODE_ENV=development. Developers must also enable ENABLE_WORKER_QUEUES=true for workers to start.

- Development: workers auto-start inside the Next.js server via `apps/web/instrumentation.ts` when `NODE_ENV=development`.
+ Development: workers auto-start inside the Next.js server via `apps/web/instrumentation.ts` when both `NODE_ENV=development` and `ENABLE_WORKER_QUEUES=true`.
🧹 Nitpick comments (1)
apps/web/utils/queue/queue.test.ts (1)

1-88: Consider adding documentation for the mock setup.

The mock setup is comprehensive but spans 88 lines. Adding a brief comment at the top explaining the mock strategy would help future maintainers understand the test architecture.

For example:

+/**
+ * Queue System Test Suite
+ * 
+ * This file tests both BullMQ (Redis) and QStash queue implementations.
+ * Extensive mocking is required to isolate the queue logic from:
+ * - BullMQ components (Queue, Worker, QueueEvents)
+ * - Redis connections (ioredis)
+ * - QStash HTTP client
+ * - Environment configuration
+ */
 import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 452fef5 and 781a9b3.

📒 Files selected for processing (2)
  • .cursor/rules/queues.mdc (1 hunks)
  • apps/web/utils/queue/queue.test.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (12)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/queue/queue.test.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/queue/queue.test.ts
  • .cursor/rules/queues.mdc
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/queue/queue.test.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/queue/queue.test.ts
**/*.test.{ts,js}

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

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/utils/queue/queue.test.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/queue/queue.test.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/queue/queue.test.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/queue/queue.test.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/queue/queue.test.ts
  • .cursor/rules/queues.mdc
**/*.{test,spec}.{js,jsx,ts,tsx}

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

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.

Files:

  • apps/web/utils/queue/queue.test.ts
**/*.test.{ts,tsx}

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

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/utils/queue/queue.test.ts
.cursor/rules/*.mdc

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

.cursor/rules/*.mdc: Always place rule files in PROJECT_ROOT/.cursor/rules/
Follow the naming convention: use kebab-case for filenames, always use .mdc extension, and make names descriptive of the rule's purpose
Cursor rules must have a frontmatter section with description, globs, and alwaysApply fields, followed by markdown content explaining the rule

Files:

  • .cursor/rules/queues.mdc
🧠 Learnings (19)
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Include standard test cases: happy path, error handling, edge cases (empty/null), different user configurations, and various input formats

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths, including cases where no AI processing is required

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies in tests

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Do not mock the actual LLM call in these tests; exercise real LLM integrations

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Place all LLM-related tests under apps/web/__tests__/

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Gate LLM tests behind RUN_AI_TESTS using describe.runIf(process.env.RUN_AI_TESTS === "true")

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Avoid testing implementation details; focus on observable behavior

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Use descriptive test names

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Clean up mocks between tests (e.g., `vi.clearAllMocks()` in `beforeEach`)

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Set a TIMEOUT of 15_000ms for LLM-related tests and pass it to long-running tests/describe blocks

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : In tests, mock the `server-only` module with `vi.mock("server-only", () => ({}));`

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Mock the Next.js server runtime marker by adding vi.mock("server-only", () => ({})) in LLM tests

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Call vi.clearAllMocks() in a beforeEach for LLM tests

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-07-18T15:04:36.476Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/cursor-rules.mdc:0-0
Timestamp: 2025-07-18T15:04:36.476Z
Learning: Applies to .cursor/rules/*.mdc : Cursor rules must have a frontmatter section with description, globs, and alwaysApply fields, followed by markdown content explaining the rule

Applied to files:

  • .cursor/rules/queues.mdc
📚 Learning: 2025-07-18T15:04:36.476Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/cursor-rules.mdc:0-0
Timestamp: 2025-07-18T15:04:36.476Z
Learning: Applies to .cursor/rules/*.mdc : Follow the naming convention: use kebab-case for filenames, always use .mdc extension, and make names descriptive of the rule's purpose

Applied to files:

  • .cursor/rules/queues.mdc
📚 Learning: 2025-07-18T15:04:36.476Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/cursor-rules.mdc:0-0
Timestamp: 2025-07-18T15:04:36.476Z
Learning: Applies to .cursor/rules/*.mdc : Always place rule files in PROJECT_ROOT/.cursor/rules/

Applied to files:

  • .cursor/rules/queues.mdc
📚 Learning: 2025-07-18T15:04:36.476Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/cursor-rules.mdc:0-0
Timestamp: 2025-07-18T15:04:36.476Z
Learning: Applies to !{.cursor/rules/*.mdc} : Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Applied to files:

  • .cursor/rules/queues.mdc
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Make sure to use the "use strict" directive in script files.

Applied to files:

  • .cursor/rules/queues.mdc
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Follow curly brace conventions.

Applied to files:

  • .cursor/rules/queues.mdc
🧬 Code graph analysis (1)
apps/web/utils/queue/queue.test.ts (4)
apps/web/utils/queue/queue-manager.ts (5)
  • closeQueueManager (107-112)
  • getQueueSystemInfo (114-122)
  • enqueueJob (47-54)
  • bulkEnqueueJobs (56-62)
  • createQueueManager (22-35)
apps/web/utils/queue/bullmq-manager.ts (1)
  • BullMQManager (23-258)
apps/web/utils/queue/qstash-manager.ts (1)
  • QStashManager (22-179)
apps/web/utils/queue/worker.ts (7)
  • registerWorker (13-74)
  • registerWorker (129-135)
  • unregisterWorker (76-86)
  • unregisterWorker (137-139)
  • getWorker (92-94)
  • getWorker (141-143)
  • shutdownAllWorkers (149-151)
🪛 GitHub Actions: Run Tests
apps/web/utils/queue/queue.test.ts

[error] 103-103: Test timed out in 5000ms for test 'should detect QStash system by default'.

🪛 GitHub Check: test
apps/web/utils/queue/queue.test.ts

[failure] 103-103: utils/queue/queue.test.ts > Queue System > Queue Manager > System Detection > should detect QStash system by default
Error: Test timed out in 5000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
❯ utils/queue/queue.test.ts:103:7

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Software Component Analysis Js
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security
🔇 Additional comments (2)
.cursor/rules/queues.mdc (2)

1-9: ✓ Cursor rule frontmatter is properly configured.

The frontmatter contains all required fields (description, globs, alwaysApply) and the file is correctly located in .cursor/rules/. Naming convention is also correct (kebab-case with .mdc extension). Based on learnings from previous reviews.


316-336: ✓ File structure is comprehensive and complete.

The documented file structure (lines 319–331) now includes all queue utility files (ai-queue.ts, email-action-queue.ts, email-actions.ts, queues.ts, queue.test.ts), resolving the completeness issue flagged in previous reviews. The Docker and worker entry point files are also properly documented.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (7)
docker/Dockerfile.prod (2)

55-63: Verify that apps/web/worker.js exists in this PR.

The build copies apps/web/worker.js into the standalone bundle. A past review comment indicated this file doesn't exist. If worker.js is being added in this PR, ensure it's included in the changeset; otherwise, this Docker build will fail.

#!/bin/bash
# Verify worker.js exists in apps/web
fd -t f '^worker\.js$' apps/web

21-23: Use --frozen-lockfile and reconsider --shamefully-hoist for production.

Two concerns with the installation flags:

  1. --no-frozen-lockfile allows the lockfile to be modified during installation, leading to non-reproducible builds. Production images should use --frozen-lockfile to ensure exact dependency versions match the lockfile.

  2. --shamefully-hoist flattens the node_modules structure, which is generally a workaround for broken module resolution. This can hide dependency issues and is not recommended for production environments.

Apply this diff:

-# Install deps
-# Use shamefully-hoist to ensure all packages are available at root level for webpack resolution
-RUN pnpm install --no-frozen-lockfile --prefer-offline --shamefully-hoist
+# Install deps
+RUN pnpm install --frozen-lockfile --prefer-offline
apps/web/utils/queue/bullmq-manager.ts (3)

36-38: Remove unnecessary type cast.

The type cast as unknown as ConnectionOptions is unnecessary. BullMQ's ConnectionOptions natively supports the url field (type string). After validation on line 32, env.REDIS_URL is guaranteed to be a string, so the object { url: env.REDIS_URL } already satisfies the interface.

Apply this diff:

-    this.connection = {
-      url: env.REDIS_URL,
-    } as unknown as ConnectionOptions;
+    this.connection = {
+      url: env.REDIS_URL,
+    };

221-229: Refactor to avoid non-null assertion.

While safe due to the has() check, the non-null assertion on line 228 violates coding guidelines. Refactor to use a local variable.

Apply this diff:

   getQueueEvents(queueName: string): QueueEvents {
+    let queueEvents = this.queueEvents.get(queueName);
-    if (!this.queueEvents.has(queueName)) {
-      const queueEvents = new QueueEvents(queueName, {
+    if (!queueEvents) {
+      queueEvents = new QueueEvents(queueName, {
         connection: this.connection,
       });
       this.queueEvents.set(queueName, queueEvents);
     }
-    return this.queueEvents.get(queueName)!;
+    return queueEvents;
   }

231-236: Refactor to avoid non-null assertion.

The non-null assertion on line 235 violates coding guidelines. Refactor to ensure type safety without using !.

Apply this diff:

   private getOrCreateQueue(queueName: string): Queue {
-    if (!this.queues.has(queueName)) {
+    let queue = this.queues.get(queueName);
+    if (!queue) {
       this.createQueue(queueName);
+      queue = this.queues.get(queueName);
+      if (!queue) {
+        throw new Error(`Failed to create queue: ${queueName}`);
+      }
     }
-    return this.queues.get(queueName)!;
+    return queue;
   }
apps/web/utils/queue/qstash-manager.ts (1)

19-21: Remove non-null assertion; validate token upfront.

The non-null assertion on env.QSTASH_TOKEN! violates coding guidelines. Validate the token presence and throw a clear error if missing.

Apply this diff:

 function getQstashClient(): Client {
+  if (!env.QSTASH_TOKEN) {
+    throw new Error("QSTASH_TOKEN is required when QUEUE_SYSTEM=upstash");
+  }
   return new Client({ token: env.QSTASH_TOKEN });
 }
apps/web/utils/queue/worker.ts (1)

47-55: Add optional chaining for job.opts access.

Line 53 uses job?.opts.attempts, which will throw if job is defined but opts is undefined. Use full optional chaining to handle both cases.

Apply this diff:

       logger.error("Job failed", {
         queueName,
         jobId: job?.id,
         error: err.message,
         attempts: job?.attemptsMade,
-        maxAttempts: job?.opts.attempts,
+        maxAttempts: job?.opts?.attempts,
       });
🧹 Nitpick comments (6)
docker/Dockerfile.prod (1)

35-53: Consider validating dummy env vars and runtime requirements.

While using dummy environment variables to enable builds without secrets is a valid pattern, be aware of potential issues:

  1. Any build-time validation or feature flags that depend on these values will use dummy data
  2. Ensure none of these dummy values are inadvertently inlined into the client bundle (especially concerning for values that might look like real secrets)
  3. Add runtime validation to fail fast if real environment variables are not provided when the container starts
apps/web/utils/queue/bullmq-manager.ts (1)

238-260: Add error handling to close method.

If closing a worker, queue, or queue event fails, the error will prevent subsequent resources from closing. Consider wrapping each close operation in a try-catch block to ensure all resources are closed even if some fail.

Apply this approach:

   async close(): Promise<void> {
     // Close all workers
     for (const [name, worker] of this.workers) {
-      logger.info("Closing worker", { queueName: name });
-      await worker.close();
+      try {
+        logger.info("Closing worker", { queueName: name });
+        await worker.close();
+      } catch (error) {
+        logger.error("Error closing worker", {
+          queueName: name,
+          error: error instanceof Error ? error.message : String(error),
+        });
+      }
     }

     // Close all queues
     for (const [name, queue] of this.queues) {
-      logger.info("Closing queue", { queueName: name });
-      await queue.close();
+      try {
+        logger.info("Closing queue", { queueName: name });
+        await queue.close();
+      } catch (error) {
+        logger.error("Error closing queue", {
+          queueName: name,
+          error: error instanceof Error ? error.message : String(error),
+        });
+      }
     }

     // Close all queue events
     for (const [name, queueEvents] of this.queueEvents) {
-      logger.info("Closing queue events", { queueName: name });
-      await queueEvents.close();
+      try {
+        logger.info("Closing queue events", { queueName: name });
+        await queueEvents.close();
+      } catch (error) {
+        logger.error("Error closing queue events", {
+          queueName: name,
+          error: error instanceof Error ? error.message : String(error),
+        });
+      }
     }

     this.queues.clear();
     this.workers.clear();
     this.queueEvents.clear();
   }
apps/web/utils/queue/queue-manager.ts (1)

24-37: Consider adding early environment validation for fail-fast behavior.

While each manager validates its required environment variables, adding validation here would provide clearer error messages and fail-fast behavior before attempting to instantiate the manager.

Apply this diff:

 export function createQueueManager(): QueueManager {
   const queueSystem = env.QUEUE_SYSTEM;

   logger.info("Creating queue manager", { queueSystem });

   switch (queueSystem) {
     case "redis":
+      if (!env.REDIS_URL) {
+        throw new Error("REDIS_URL is required when QUEUE_SYSTEM=redis");
+      }
       return new BullMQManager();
     case "upstash":
+      if (!env.QSTASH_TOKEN) {
+        throw new Error("QSTASH_TOKEN is required when QUEUE_SYSTEM=upstash");
+      }
       return new QStashManager();
     default:
       throw new Error(`Unsupported queue system: ${queueSystem}`);
   }
 }
apps/web/utils/queue/queues.ts (3)

82-95: TODO: Implement digest job processing logic.

The function currently contains a placeholder implementation. Ensure the actual digest processing logic is implemented before this goes to production.

Do you want me to help implement the digest job processing logic or open an issue to track this task?


129-154: TODO: Implement scheduled action and AI clean job logic.

Both functions contain placeholder implementations. Ensure the actual processing logic is implemented before production deployment.

Do you want me to help implement these job handlers or open an issue to track these tasks?


451-475: TODO: Implement email summary and clean Gmail job logic.

Both functions contain placeholder implementations. Ensure the actual processing logic is implemented before production deployment.

Do you want me to help implement these job handlers or open an issue to track these tasks?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 781a9b3 and 3f3213d.

📒 Files selected for processing (10)
  • apps/web/env.ts (1 hunks)
  • apps/web/next.config.ts (1 hunks)
  • apps/web/utils/ai/rule/diff-rules.ts (1 hunks)
  • apps/web/utils/queue/bullmq-manager.ts (1 hunks)
  • apps/web/utils/queue/qstash-manager.ts (1 hunks)
  • apps/web/utils/queue/queue-manager.ts (1 hunks)
  • apps/web/utils/queue/queues.ts (1 hunks)
  • apps/web/utils/queue/worker.ts (1 hunks)
  • apps/web/worker.js (1 hunks)
  • docker/Dockerfile.prod (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/web/utils/ai/rule/diff-rules.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/env.ts
  • apps/web/worker.js
🧰 Additional context used
📓 Path-based instructions (8)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
  • docker/Dockerfile.prod
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/queue/worker.ts
  • apps/web/next.config.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
  • docker/Dockerfile.prod
🧠 Learnings (16)
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{ts,tsx} : Don't misuse the non-null assertion operator (!) in TypeScript files.

Applied to files:

  • apps/web/utils/queue/worker.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{ts,tsx} : Don't use non-null assertions with the ! postfix operator.

Applied to files:

  • apps/web/utils/queue/worker.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use proper error types and logging for failures

Applied to files:

  • apps/web/utils/queue/worker.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Implement fallbacks for AI failures

Applied to files:

  • apps/web/utils/queue/worker.ts
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : System prompt should define the LLM's role and task specifications

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Remove excessive whitespace and truncate long inputs in prompts

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Keep system prompts and user prompts separate

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : User prompt should contain the actual data and context

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Format prompt data consistently across similar functions

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Add retry logic for transient failures using withRetry

Applied to files:

  • apps/web/utils/queue/bullmq-manager.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use Date.now() to get milliseconds since the Unix Epoch.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-07-20T09:00:41.968Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-07-20T09:00:41.968Z
Learning: Applies to apps/web/app/api/**/*.{ts,js} : All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use TypeScript types for all parameters and return values in LLM features

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.{ts,tsx} : Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/queues.ts
📚 Learning: 2025-07-18T15:05:34.899Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/gmail-api.mdc:0-0
Timestamp: 2025-07-18T15:05:34.899Z
Learning: Applies to apps/web/utils/gmail/**/*.ts : Keep provider-specific implementation details isolated in the appropriate utils subfolder (e.g., 'apps/web/utils/gmail/')

Applied to files:

  • apps/web/utils/queue/queues.ts
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
Repo: elie222/inbox-zero PR: 576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/utils/queue/queues.ts
🧬 Code graph analysis (5)
apps/web/utils/queue/worker.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/queue-manager.ts (5)
  • Worker (126-126)
  • JobProcessor (136-136)
  • WorkerConfig (135-135)
  • createQueueWorker (66-86)
  • closeQueueManager (109-114)
apps/web/utils/queue/types.ts (3)
  • Worker (101-101)
  • JobProcessor (92-92)
  • WorkerConfig (84-90)
apps/web/utils/queue/bullmq-manager.ts (1)
  • DEFAULT_CONCURRENCY (20-20)
apps/web/utils/queue/bullmq-manager.ts (4)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/types.ts (9)
  • QueueManager (50-82)
  • Queue (101-101)
  • Worker (101-101)
  • QueueEvents (101-101)
  • ConnectionOptions (101-101)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/queues.ts (1)
  • getAiCleanQueueName (511-523)
apps/web/utils/queue/qstash-manager.ts (3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/types.ts (6)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • BulkEnqueueOptions (42-48)
  • Job (101-101)
  • ConnectionOptions (101-101)
apps/web/utils/queue/queue-manager.ts (5)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/types.ts (9)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
  • ConnectionOptions (101-101)
  • Worker (101-101)
  • Queue (101-101)
  • QueueSystemInfo (94-99)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/bullmq-manager.ts (2)
  • BullMQManager (25-261)
  • createQueue (200-219)
apps/web/utils/queue/qstash-manager.ts (2)
  • QStashManager (23-184)
  • createQueue (166-174)
apps/web/utils/queue/queues.ts (10)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/app/api/user/categorize/senders/batch/handle-batch.ts (1)
  • handleBatchRequest (19-32)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/types.ts (1)
  • ParsedMessage (47-62)
apps/web/utils/rule/consts.ts (1)
  • getRuleName (118-120)
apps/web/app/api/resend/digest/validation.ts (2)
  • storedDigestContentSchema (3-3)
  • Digest (16-16)
apps/web/utils/email.ts (1)
  • extractNameFromEmail (9-16)
packages/resend/src/send.tsx (1)
  • sendDigestEmail (117-142)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/schedule.ts (1)
  • calculateNextScheduleDate (124-227)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (28)
apps/web/next.config.ts (1)

15-15: LGTM! Already disabled for regular builds.

The conditional spread correctly applies the standalone output only when DOCKER_BUILD === "true". For regular builds (where this env var is not set), the spread evaluates to false and no additional config is applied.

docker/Dockerfile.prod (2)

28-33: Review memory allocation and default base URL.

Two observations:

  1. 16GB heap allocation (NODE_OPTIONS=--max_old_space_size=16384) is extremely high for a Next.js build. Consider whether this is necessary or if a more reasonable limit like 4GB-8GB would suffice.

  2. Default NEXT_PUBLIC_BASE_URL of http://localhost:3000 will be baked into the client bundle if not overridden at build time. Ensure this ARG is always provided in production builds.


65-78: LGTM! Runner stage is well-structured.

The runner stage correctly:

  • Uses a minimal alpine base image
  • Copies only the necessary artifacts from the builder (standalone bundle, static assets, public files)
  • Exposes the standard Next.js port
  • Runs the standalone server bundle
apps/web/utils/queue/bullmq-manager.ts (3)

68-155: LGTM! Bulk enqueue implementation handles distribution correctly.

The special-case logic for "ai-clean" queue properly groups jobs by emailAccountId and distributes them across multiple queues, maintaining per-account parallelism. The fallback path for other queues uses a single-queue approach. Both paths correctly apply nullish coalescing for option defaults.


157-198: LGTM! Worker creation with proper lifecycle logging.

The worker implementation correctly wraps the processor with lifecycle logging (processing, completion, failure) without exposing PII. Error handling properly logs and rethrows to ensure failed jobs are retried according to BullMQ configuration.


200-219: LGTM! Queue creation with sensible defaults.

Queue creation properly sets default job options (removal counts, retry attempts) and allows override via options parameter. The defaults align with the constants defined at the top of the file.

apps/web/utils/queue/qstash-manager.ts (4)

24-52: LGTM! Enqueue implementation with proper deduplication support.

Both delay and non-delay paths correctly support deduplicationId via options.jobId. The delay path uses publishJSON with notBefore, while the non-delay path creates/updates a queue and uses enqueueJSON. The notBefore calculation has been corrected to use Math.ceil((Date.now() + options.delay) / 1000).


54-151: LGTM! Bulk enqueue with per-account parallelism for ai-clean.

The implementation correctly handles the ai-clean queue by grouping jobs by emailAccountId and creating per-account queues with parallelism=3. Each job is enqueued with proper support for delays (notBefore) and deduplication (deduplicationId). The fallback for other queues uses batchJSON appropriately.


153-178: LGTM! Correct error handling for unsupported operations.

The methods createWorker, createQueue, and getQueueEvents correctly throw descriptive errors explaining that QStash uses HTTP endpoints instead of BullMQ workers and queues. This prevents misuse and provides clear guidance to developers.


180-183: LGTM! Close implementation is appropriate for QStash.

QStash doesn't require explicit connection cleanup, so logging the closure is sufficient. This aligns with the HTTP-based nature of QStash.

apps/web/utils/queue/worker.ts (5)

33-75: LGTM! Event handlers provide comprehensive observability.

The worker event handlers (completed, failed, stalled, error) provide good observability with appropriate logging. The completion handler correctly handles the optional processedOn field (lines 39-42).


77-87: LGTM! Clean worker unregistration.

The method properly closes the worker before removing it from the registry, with appropriate logging and early return for missing workers.


89-95: LGTM! Proper getter methods with defensive copy.

getWorkers() returns a defensive copy of the workers map, preventing external modification. getWorker() correctly returns a single worker or undefined.


97-127: LGTM! Robust shutdown with per-worker error handling.

The shutdown method properly handles errors for each worker individually, ensuring all workers attempt to close even if some fail. The idempotency check (isShuttingDown) prevents concurrent shutdown attempts.


154-166: LGTM! Proper signal handling for graceful shutdown.

The SIGINT and SIGTERM handlers correctly trigger graceful shutdown by awaiting both worker shutdown and queue manager closure before exiting. This ensures in-flight jobs can complete and resources are properly released.

apps/web/utils/queue/queue-manager.ts (6)

39-46: LGTM! Proper singleton pattern for queue manager.

The memoized accessor ensures a single queue manager instance is created and reused across the application.


48-64: LGTM! Clean delegation to queue manager.

The enqueueJob and bulkEnqueueJobs functions provide a convenient API that delegates to the underlying manager implementation.


66-86: LGTM! Worker creation with proper backend guard.

The function correctly guards against non-Redis queue systems, returning null with a warning for QStash. This prevents runtime errors when workers aren't supported.


88-107: LGTM! Queue creation with proper backend guard.

Similar to worker creation, this function correctly guards against non-Redis queue systems, returning null with a warning for QStash.


109-114: LGTM! Proper cleanup of queue manager.

The function correctly closes the manager and resets the singleton to null, allowing clean shutdown and re-initialization if needed.


116-124: Add explicit return type annotation.

The function should have an explicit return type for better type safety and documentation.

Apply this diff:

-export function getQueueSystemInfo(): QueueSystemInfo {
+export function getQueueSystemInfo(): QueueSystemInfo {
   const isRedis = env.QUEUE_SYSTEM === "redis";
   return {
     system: env.QUEUE_SYSTEM,
     supportsWorkers: isRedis,
     supportsDelayedJobs: true,
     supportsBulkOperations: true,
   };
 }

Note: The return type is already present in the code. This is good!

apps/web/utils/queue/queues.ts (7)

22-80: LGTM! Well-defined job data interfaces.

The interfaces provide clear contracts for each job type with appropriate fields and types. The skips object in AiCleanJobData provides granular control over processing rules.


97-127: LGTM! Direct batch categorization call avoids HTTP overhead.

The function correctly calls handleBatchRequest directly with a synthetic Request object. While the "http://localhost" URL is a placeholder, this approach eliminates unnecessary network overhead compared to making an actual HTTP call.


156-191: LGTM! Email digest handling with proper error logging.

The handleEmailDigestAllJob function correctly delegates to sendDigestEmailForAccount with appropriate error handling and logging. getDigestSchedule properly fetches schedule data from the database.


193-449: LGTM! Comprehensive digest email processing with proper error handling.

The function correctly:

  • Marks digests as PROCESSING to prevent duplicate processing
  • Handles the no-digests case with early return (line 260-262)
  • Fetches messages in batches with rate limiting (lines 277-289)
  • Resets digests to PENDING if no valid rules are found (lines 357-367)
  • Uses a transaction to ensure atomicity when updating digests and schedule (lines 396-432)
  • Resets digests to FAILED on error (lines 433-446)

The TODO on line 279 about refactoring batch fetching into the provider is valid for code reusability if this pattern is used elsewhere.


477-530: LGTM! Deterministic queue distribution logic.

The hash-based distribution using character code sum ensures the same emailAccountId always routes to the same queue index, maintaining per-account ordering. The comments clearly explain the BullMQ vs QStash routing strategies.


532-556: LGTM! Comprehensive queue handler mapping.

The QUEUE_HANDLERS mapping explicitly defines all queue names and their handlers, including the distributed queues. This provides type safety through the QueueName type and makes the queue routing explicit.


558-629: LGTM! Backend-aware queue routing and validation.

Both getQueueHandler and isValidQueueName correctly handle the differences between BullMQ (hash-based distribution with fixed queue indices 0-6) and QStash (per-account queues). The validation logic ensures only valid queue names are processed while maintaining flexibility for each backend's approach.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

Reviewed changes from recent commits (found 1 issue).

1 issue found across 10 files

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="apps/web/env.ts">

<violation number="1" location="apps/web/env.ts:65">
Switching this env schema to `z.coerce.boolean()` causes strings like &quot;false&quot; to coerce to `true` because Zod uses JavaScript truthiness, so `&quot;false&quot;` now enables the worker queues. Keep the parser strict or implement explicit string-to-boolean mapping instead.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
apps/web/utils/queue/qstash-manager.ts (1)

17-19: Remove non-null assertion and validate QSTASH_TOKEN upfront.

The non-null assertion operator violates coding guidelines and can lead to cryptic runtime errors. Validate the token and fail fast with a clear error message.

As per coding guidelines.

Apply this diff:

 function getQstashClient(): Client {
-  return new Client({ token: env.QSTASH_TOKEN! });
+  if (!env.QSTASH_TOKEN) {
+    throw new Error("QSTASH_TOKEN is required when QUEUE_SYSTEM=upstash");
+  }
+  return new Client({ token: env.QSTASH_TOKEN });
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f3213d and 7d8123c.

📒 Files selected for processing (7)
  • .cursor/rules/queues.mdc (1 hunks)
  • apps/web/app/api/queue/[queueName]/route.ts (1 hunks)
  • apps/web/utils/queue/bullmq-manager.ts (1 hunks)
  • apps/web/utils/queue/qstash-manager.ts (1 hunks)
  • apps/web/utils/queue/queue-manager.ts (1 hunks)
  • apps/web/utils/queue/queue.test.ts (1 hunks)
  • apps/web/utils/queue/worker.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/web/utils/queue/bullmq-manager.ts
  • apps/web/utils/queue/worker.ts
  • .cursor/rules/queues.mdc
  • apps/web/app/api/queue/[queueName]/route.ts
🧰 Additional context used
📓 Path-based instructions (11)
apps/web/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/web/CLAUDE.md)

apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Path aliases: Use @/ for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Leverage TypeScript inference for better DX

Files:

  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
!{.cursor/rules/*.mdc}

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

Never place rule files in the project root, in subdirectories outside .cursor/rules, or in any other location

Files:

  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/form-handling.mdc)

**/*.ts: The same validation should be done in the server action too
Define validation schemas using Zod

Files:

  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
**/*.{ts,tsx}

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

**/*.{ts,tsx}: Use createScopedLogger for logging in backend TypeScript files
Typically add the logger initialization at the top of the file when using createScopedLogger
Only use .with() on a logger instance within a specific function, not for a global logger

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
**/*.test.{ts,js}

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

Include security tests in your test suites to verify authentication, authorization, and error handling.

Files:

  • apps/web/utils/queue/queue.test.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/web/utils/queue/queue.test.ts
  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
**/*.{test,spec}.{js,jsx,ts,tsx}

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

**/*.{test,spec}.{js,jsx,ts,tsx}: Don't use export or module.exports in test files.
Don't use focused tests.
Don't use disabled tests.
Make sure the assertion function, like expect, is placed inside an it() function call.
Don't nest describe() blocks too deeply in test files.
Don't use focused tests.
Don't use disabled tests.
Don't use export or module.exports in test files.

Files:

  • apps/web/utils/queue/queue.test.ts
**/*.test.{ts,tsx}

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

**/*.test.{ts,tsx}: Use Vitest (vitest) as the testing framework
Colocate tests next to the file under test (e.g., dir/format.ts with dir/format.test.ts)
In tests, mock the server-only module with vi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it with vi.mock("@/utils/prisma") and use the mock from @/utils/__mocks__/prisma
Use provided helpers for mocks: import { getEmail, getEmailAccount, getRule } from @/__tests__/helpers
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Clean up mocks between tests (e.g., vi.clearAllMocks() in beforeEach)
Avoid testing implementation details; focus on observable behavior
Do not mock the Logger

Files:

  • apps/web/utils/queue/queue.test.ts
🧠 Learnings (26)
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Include standard test cases: happy path, error handling, edge cases (empty/null), different user configurations, and various input formats

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Test both AI and non-AI paths, including cases where no AI processing is required

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Mock external dependencies in tests

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Do not mock the actual LLM call in these tests; exercise real LLM integrations

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Place all LLM-related tests under apps/web/__tests__/

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Gate LLM tests behind RUN_AI_TESTS using describe.runIf(process.env.RUN_AI_TESTS === "true")

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Clean up mocks between tests (e.g., `vi.clearAllMocks()` in `beforeEach`)

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Use descriptive test names

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Mock the Next.js server runtime marker by adding vi.mock("server-only", () => ({})) in LLM tests

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Set a TIMEOUT of 15_000ms for LLM-related tests and pass it to long-running tests/describe blocks

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Call vi.clearAllMocks() in a beforeEach for LLM tests

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : In tests, mock the `server-only` module with `vi.mock("server-only", () => ({}));`

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-10-02T23:23:48.064Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-10-02T23:23:48.064Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Use Vitest in LLM tests and import { describe, expect, test, vi, beforeEach } from "vitest"

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Each test should be independent

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : When testing code that uses Prisma, mock it with `vi.mock("@/utils/prisma")` and use the mock from `@/utils/__mocks__/prisma`

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-20T18:24:34.280Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-09-20T18:24:34.280Z
Learning: Applies to **/*.test.{ts,tsx} : Avoid testing implementation details; focus on observable behavior

Applied to files:

  • apps/web/utils/queue/queue.test.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : System prompt should define the LLM's role and task specifications

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Implement fallbacks for AI failures

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use TypeScript types for all parameters and return values in LLM features

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Format prompt data consistently across similar functions

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/{ai,llms}/**/*.{ts,tsx} : Keep related AI functions co-located and extract common patterns into utilities; document complex AI logic with clear comments

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-09-17T22:05:28.646Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-09-17T22:05:28.646Z
Learning: Applies to apps/web/utils/ai/**/*.{ts,tsx} : Use proper error types and logging for failures

Applied to files:

  • apps/web/utils/queue/queue-manager.ts
📚 Learning: 2025-07-20T09:00:41.968Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-07-20T09:00:41.968Z
Learning: Applies to apps/web/app/api/**/*.{ts,js} : All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use Date.now() to get milliseconds since the Unix Epoch.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{ts,tsx} : Don't misuse the non-null assertion operator (!) in TypeScript files.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
📚 Learning: 2025-07-20T09:03:06.318Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-07-20T09:03:06.318Z
Learning: Applies to **/*.{ts,tsx} : Don't use non-null assertions with the ! postfix operator.

Applied to files:

  • apps/web/utils/queue/qstash-manager.ts
🧬 Code graph analysis (3)
apps/web/utils/queue/queue.test.ts (4)
apps/web/utils/queue/queue-manager.ts (5)
  • closeQueueManager (107-112)
  • getQueueSystemInfo (114-122)
  • enqueueJob (47-54)
  • bulkEnqueueJobs (56-62)
  • createQueueManager (22-35)
apps/web/utils/queue/bullmq-manager.ts (1)
  • BullMQManager (23-259)
apps/web/utils/queue/qstash-manager.ts (1)
  • QStashManager (21-182)
apps/web/utils/queue/worker.ts (7)
  • registerWorker (13-74)
  • registerWorker (129-135)
  • unregisterWorker (76-86)
  • unregisterWorker (137-139)
  • getWorker (92-94)
  • getWorker (141-143)
  • shutdownAllWorkers (149-151)
apps/web/utils/queue/queue-manager.ts (5)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/utils/queue/types.ts (9)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
  • ConnectionOptions (101-101)
  • Worker (101-101)
  • Queue (101-101)
  • QueueSystemInfo (94-99)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/bullmq-manager.ts (2)
  • BullMQManager (23-259)
  • createQueue (198-217)
apps/web/utils/queue/qstash-manager.ts (2)
  • QStashManager (21-182)
  • createQueue (164-172)
apps/web/utils/queue/qstash-manager.ts (3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-80)
apps/web/env.ts (1)
  • env (16-241)
apps/web/utils/queue/types.ts (6)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (33-40)
  • BulkEnqueueOptions (42-48)
  • Job (101-101)
  • ConnectionOptions (101-101)
🪛 GitHub Check: test
apps/web/utils/queue/queue.test.ts

[failure] 203-203: utils/queue/queue.test.ts > Queue System > Queue Manager > Job Enqueueing > should handle job enqueueing errors
AssertionError: expected [Function] to throw error including 'Enqueue failed' but got 'client.queue is not a function'

Expected: "Enqueue failed"
Received: "client.queue is not a function"

❯ utils/queue/queue.test.ts:203:9


[failure] 106-106: utils/queue/queue.test.ts > Queue System > Queue Manager > System Detection > should detect QStash system by default
Error: Test timed out in 5000ms.
If this is a long-running test, pass a timeout value as the last argument or configure it globally with "testTimeout".
❯ utils/queue/queue.test.ts:106:7

apps/web/utils/queue/qstash-manager.ts

[failure] 41-41: utils/queue/queue.test.ts > Queue System > QStash Manager > URL Construction > should fallback to NEXT_PUBLIC_BASE_URL when WEBHOOK_URL is not available
TypeError: client.queue is not a function
❯ QStashManager.enqueue utils/queue/qstash-manager.ts:41:28
❯ utils/queue/queue.test.ts:608:29


[failure] 41-41: utils/queue/queue.test.ts > Queue System > QStash Manager > URL Construction > should use WEBHOOK_URL when available
TypeError: client.queue is not a function
❯ QStashManager.enqueue utils/queue/qstash-manager.ts:41:28
❯ utils/queue/queue.test.ts:583:29


[failure] 41-41: utils/queue/queue.test.ts > Queue System > QStash Manager > Job Enqueueing > should return 'unknown' when messageId is missing
TypeError: client.queue is not a function
❯ QStashManager.enqueue utils/queue/qstash-manager.ts:41:28
❯ utils/queue/queue.test.ts:503:38


[failure] 41-41: utils/queue/queue.test.ts > Queue System > QStash Manager > Job Enqueueing > should enqueue a single job
TypeError: client.queue is not a function
❯ QStashManager.enqueue utils/queue/qstash-manager.ts:41:28
❯ utils/queue/queue.test.ts:460:38


[failure] 41-41: utils/queue/queue.test.ts > Queue System > Queue Manager > Job Enqueueing > should enqueue a single job with QStash
TypeError: client.queue is not a function
❯ QStashManager.enqueue utils/queue/qstash-manager.ts:41:28
❯ enqueueJob utils/queue/queue-manager.ts:53:18
❯ utils/queue/queue.test.ts:151:30

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 9 files

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

Reviewed changes from recent commits (found 1 issue).

1 issue found across 1 file

Prompt for AI agents (all 1 issues)

Understand the root cause of the following 1 issues and fix them.


<file name="docker-compose.yml">

<violation number="1" location="docker-compose.yml:65">
Explicitly interpolating UPSTASH_REDIS_TOKEN here overrides the value supplied via env_file; when the host variable is unset Compose replaces it with an empty string, leaving the web container without the required Upstash token.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

@socket-security
Copy link

socket-security bot commented Nov 13, 2025

@socket-security
Copy link

socket-security bot commented Nov 13, 2025

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm safer-buffer is 94.0% likely obfuscated

Confidence: 0.94

Location: Package overview

From: pnpm-lock.yamlnpm/[email protected]

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at [email protected].

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/[email protected]. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

Copy link

@jit-ci jit-ci bot left a comment

Choose a reason for hiding this comment

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

❌ Jit has detected 1 important finding in this PR that you should review.
The finding is detailed below as a comment.
It’s highly recommended that you fix this security issue before merge.

@@ -0,0 +1,44 @@
FROM node:22-alpine AS builder
Copy link

Choose a reason for hiding this comment

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

Security control: Docker Scan

Image User Should Not Be 'Root'

Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.

Severity: HIGH

Learn more about this issue


Jit Bot commands and options (e.g., ignore issue)

You can trigger Jit actions by commenting on this PR review:

  • #jit_ignore_fp Ignore and mark this specific single instance of finding as “False Positive”
  • #jit_ignore_accept Ignore and mark this specific single instance of finding as “Accept Risk”
  • #jit_ignore_type_in_file Ignore any finding of type "Image user should not be 'root'" in docker/Dockerfile.worker; future occurrences will also be ignored.
  • #jit_undo_ignore Undo ignore command

@edulelis
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 13, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

♻️ Duplicate comments (2)
apps/web/env.ts (1)

65-65: z.coerce.boolean() will treat "false" string as true—use explicit parsing instead.

As flagged in previous reviews, z.coerce.boolean() uses JavaScript's Boolean("false") which evaluates to true (any non-empty string is truthy). This means setting ENABLE_WORKER_QUEUES=false in the environment will actually enable worker queues.

Apply this diff to use explicit string parsing:

-    ENABLE_WORKER_QUEUES: z.coerce.boolean().default(false),
+    ENABLE_WORKER_QUEUES: z
+      .string()
+      .optional()
+      .default("false")
+      .transform((val) => val === "true")
+      .pipe(z.boolean()),

Or use the preprocess approach:

-    ENABLE_WORKER_QUEUES: z.coerce.boolean().default(false),
+    ENABLE_WORKER_QUEUES: z.preprocess(
+      (val) => val === "true" || val === true,
+      z.boolean().default(false)
+    ),

Based on past review comments.

apps/web/app/api/queue/[queueName]/route.ts (1)

48-88: Add input validation for queue job data before handler execution.

The route forwards body directly to handlers without Zod schema validation (line 69-87). Per coding guidelines and past review comments, request bodies in API routes must be validated using Zod schemas before use. Each queue handler receives unvalidated data, relying only on compile-time TypeScript interfaces.

Create a Zod validation layer. For example, add a validation registry:

// In apps/web/utils/queue/queues.ts
export const QUEUE_VALIDATORS: Record<string, z.ZodSchema> = {
  digest: digestBody,
  "scheduled-action": scheduledActionBody,
  // ... other queue schemas
};

export function getQueueValidator(queueName: string) {
  return QUEUE_VALIDATORS[queueName];
}

Then in the route handler:

  const body = await request.json();

+ // Validate input based on queue type
+ const validator = getQueueValidator(queueName);
+ if (validator) {
+   const result = validator.safeParse(body);
+   if (!result.success) {
+     logger.warn("Invalid job data", { queueName, errors: result.error });
+     return NextResponse.json(
+       { error: "Invalid job data", details: result.error.flatten() },
+       { status: 400 }
+     );
+   }
+ }

  logger.info("Processing queue job", {
    queueName,
    queueSystem: env.QUEUE_SYSTEM,
  });

Based on past review comments and coding guidelines.

🧹 Nitpick comments (4)
apps/web/app/api/clean/gmail/route.ts (1)

3-3: Remove unused import.

The verifyWorkerSignatureAppRouter import is not used in this file. It's called internally by verifyQueueSignatureAppRouter from line 2, so this import is redundant.

Apply this diff:

-import { verifyWorkerSignatureAppRouter } from "@/utils/worker-signature";
apps/web/utils/queue-signature.ts (1)

14-17: Consider strengthening type safety.

The adapter uses multiple type assertions (as TReq, as TRes as Response) that bypass TypeScript's type checking. While this pattern works, it could hide type mismatches.

Consider adding runtime type guards or narrowing the generic constraints to ensure type safety:

const adapter = async (req: Request): Promise<Response> => {
  // Add validation that req is compatible with TReq if needed
  const result = await handler(req as TReq);
  return result as Response;
};
apps/queue-worker/package.json (1)

11-11: Placeholder lint script is acknowledged.

The lint script is a placeholder with an explicit message. Consider adding ESLint configuration in a follow-up to maintain code quality consistency across the monorepo.

apps/web/utils/queue/queues.ts (1)

193-449: Consider breaking down this complex function.

The sendDigestEmailForAccount function is 257 lines long and handles multiple concerns: fetching data, transforming messages, grouping by rules, sending emails, and database updates.

While the implementation is correct (including proper transaction usage and error handling), breaking it into smaller, focused functions would improve maintainability and testability.

Potential extractions:

  • fetchPendingDigests(emailAccountId) - lines 215-256
  • fetchAndBatchMessages(messageIds, emailProvider) - lines 271-289
  • transformDigestItems(pendingDigests, messageMap) - lines 297-353
  • updateDigestStatusAfterSend(processedDigestIds, digestScheduleData) - lines 396-432

This refactor is not urgent but would make the code easier to test and reason about.

NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
PORT: z.number().default(5070),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add type coercion for PORT environment variable.

Environment variables from process.env are always strings. The PORT variable needs .coerce to convert the string to a number, otherwise validation will fail at runtime.

Apply this diff:

-    PORT: z.number().default(5070),
+    PORT: z.coerce.number().default(5070),
📝 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
PORT: z.number().default(5070),
PORT: z.coerce.number().default(5070),
🤖 Prompt for AI Agents
In apps/queue-worker/src/env.ts around line 10, the PORT schema uses
z.number().default(5070) but environment variables are strings so validation
will fail; replace it with the coercing form (use
z.coerce.number().default(5070)) so the string from process.env is converted to
a number before validation and defaults still apply.

WORKER_SIGNING_SECRET: z.string().optional(),

// Tuning
DEFAULT_CONCURRENCY: z.number().default(3),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add type coercion for DEFAULT_CONCURRENCY environment variable.

Similar to PORT, DEFAULT_CONCURRENCY needs .coerce to convert the string environment variable to a number.

Apply this diff:

-    DEFAULT_CONCURRENCY: z.number().default(3),
+    DEFAULT_CONCURRENCY: z.coerce.number().default(3),
🤖 Prompt for AI Agents
In apps/queue-worker/src/env.ts around line 24, the DEFAULT_CONCURRENCY schema
uses z.number().default(3) but doesn't coerce the string env value to a number;
update the schema to use z.coerce.number().default(3) (matching how PORT is
handled) so process.env values like "3" are converted to numeric values before
validation.

Comment on lines +111 to +118
const jobId = await enqueue(
body.queueName,
{ targetPath, payload: data, headers: body.headers },
{
delay: delayMs,
parallelism: body.options?.parallelism,
jobId: dedupId,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Forward retry/cleanup options to the worker.

Line [115] accepts attempts, priority, removeOnComplete, and removeOnFail, but the call into enqueue() only forwards delay, parallelism, and jobId. The same drop happens for bulk payloads around Line [155]. Those settings are therefore silently ignored, so callers cannot configure retries or retention as intended. Please forward the full option set to keep behaviour consistent with queue.ts.

Apply this diff (single enqueue):

         const jobId = await enqueue(
           body.queueName,
           { targetPath, payload: data, headers: body.headers },
           {
             delay: delayMs,
             parallelism: body.options?.parallelism,
             jobId: dedupId,
+            attempts: body.options?.attempts,
+            priority: body.options?.priority,
+            removeOnComplete: body.options?.removeOnComplete,
+            removeOnFail: body.options?.removeOnFail,
           },
         );

And for the bulk mapper:

             return {
               data: { targetPath, payload: data, headers: item.headers },
               options: {
                 delay: delayMs,
                 jobId: dedupId,
+                attempts: item.options?.attempts,
+                priority: item.options?.priority,
+                removeOnComplete: item.options?.removeOnComplete,
+                removeOnFail: item.options?.removeOnFail,
               },
             };

Also applies to: 153-157

🤖 Prompt for AI Agents
In apps/queue-worker/src/http.ts around lines 111-118 (and similarly 153-157 for
the bulk mapper), the enqueue() call currently forwards only delay, parallelism
and jobId so options like attempts, priority, removeOnComplete and removeOnFail
are dropped; update the option object passed to enqueue() to include the full
set from body.options (attempts, priority, removeOnComplete, removeOnFail) in
addition to delay, parallelism and jobId for both the single enqueue and the
bulk enqueue paths so retry and retention settings are preserved and propagated
to the worker.

Comment on lines +64 to +67
QUEUE_SYSTEM: z.enum(["redis", "upstash"]).default("upstash"),
ENABLE_WORKER_QUEUES: z.coerce.boolean().default(false),
QSTASH_TOKEN: z.string().optional(),
QSTASH_CURRENT_SIGNING_KEY: z.string().optional(),
QSTASH_NEXT_SIGNING_KEY: z.string().optional(),
WORKER_BASE_URL: z.string().optional(),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify .env.example and turbo.json updates for new environment variables.

Coding guidelines require adding new environment variables to .env.example, env.ts, and turbo.json. Please confirm the following new variables are properly documented:

  • QUEUE_SYSTEM
  • ENABLE_WORKER_QUEUES
  • WORKER_BASE_URL
  • WORKER_SIGNING_SECRET

As per coding guidelines.

Also applies to: 115-115


🏁 Script executed:

#!/bin/bash
# Verify new env vars are documented in .env.example and turbo.json
echo "=== Checking .env.example ==="
grep -E '(QUEUE_SYSTEM|ENABLE_WORKER_QUEUES|WORKER_BASE_URL|WORKER_SIGNING_SECRET)' apps/web/.env.example || echo "NOT FOUND in .env.example"

echo -e "\n=== Checking turbo.json ==="
grep -E '(QUEUE_SYSTEM|ENABLE_WORKER_QUEUES|WORKER_BASE_URL|WORKER_SIGNING_SECRET)' turbo.json || echo "NOT FOUND in turbo.json"

Length of output: 108


🏁 Script executed:

#!/bin/bash
# Check if QSTASH_TOKEN is in the files
echo "=== Checking QSTASH_TOKEN in .env.example ==="
grep -i 'QSTASH_TOKEN' apps/web/.env.example || echo "NOT FOUND in .env.example"

echo -e "\n=== Checking QSTASH_TOKEN in turbo.json ==="
grep -i 'QSTASH_TOKEN' turbo.json || echo "NOT FOUND in turbo.json"

# Show relevant section from turbo.json for context
echo -e "\n=== Current turbo.json build.env section ==="
jq '.tasks.build.env // "NOT FOUND"' turbo.json 2>/dev/null || echo "Could not parse turbo.json"

# Show .env.example for context
echo -e "\n=== Current .env.example (last 20 lines) ==="
tail -20 apps/web/.env.example

Length of output: 439


🏁 Script executed:

#!/bin/bash
# Show current .env.example structure
echo "=== Current apps/web/.env.example ==="
cat apps/web/.env.example

echo -e "\n\n=== Current turbo.json tasks.build.env section ==="
jq '.tasks.build.env' turbo.json

Length of output: 5749


Add missing environment variables to .env.example and turbo.json.

Four new variables are missing from required configuration files:

  1. apps/web/.env.example – Add to the redis config section (near existing QSTASH_TOKEN entries):

    • QUEUE_SYSTEM=upstash (or redis)
    • ENABLE_WORKER_QUEUES=false
    • WORKER_BASE_URL= (with comment if needed)
    • WORKER_SIGNING_SECRET= (with comment if needed)
  2. turbo.json – Add these four variables to the tasks.build.env array.

Note: QSTASH_TOKEN is already present in both files. Boolean variable ENABLE_WORKER_QUEUES should follow the unquoted convention (e.g., ENABLE_WORKER_QUEUES=false).

🤖 Prompt for AI Agents
In apps/web/env.ts around lines 64 to 67, four new env vars (QUEUE_SYSTEM,
ENABLE_WORKER_QUEUES, WORKER_BASE_URL, WORKER_SIGNING_SECRET) are declared but
not added to configuration examples and build envs; update apps/web/.env.example
by adding under the redis config section the entries QUEUE_SYSTEM=upstash (or
redis), ENABLE_WORKER_QUEUES=false (unquoted boolean), WORKER_BASE_URL= (empty
with optional comment), and WORKER_SIGNING_SECRET= (empty with optional
comment), and update turbo.json by adding these four variable names to the
tasks.build.env array so the build process receives them (keep QSTASH_TOKEN
as-is).

QSTASH_TOKEN: z.string().optional(),
QSTASH_CURRENT_SIGNING_KEY: z.string().optional(),
QSTASH_NEXT_SIGNING_KEY: z.string().optional(),
WORKER_BASE_URL: z.string().optional(),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add URL format validation to WORKER_BASE_URL.

WORKER_BASE_URL is used to construct HTTP endpoints for the queue worker service (e.g., ${WORKER_BASE_URL}/v1/jobs in BullMQManager). Without URL validation, invalid formats will cause runtime fetch errors.

Apply this diff:

-    WORKER_BASE_URL: z.string().optional(),
+    WORKER_BASE_URL: z.string().url().optional(),

This follows the same pattern as the REDIS_URL fix from previous reviews.

🤖 Prompt for AI Agents
In apps/web/env.ts around line 67, WORKER_BASE_URL is currently validated as
z.string().optional(); update the schema to validate it as a URL by using
z.string().url().optional() (following the same pattern used for REDIS_URL) so
that invalid URL formats are rejected at startup; keep it optional if the
variable may be omitted.

Comment on lines +82 to +95
async function handleDigestJob(data: DigestJobData) {
logger.info("Processing digest job", {
emailAccountId: data.emailAccountId,
actionId: data.actionId,
coldEmailId: data.coldEmailId,
messageId: data.message.id,
});

// TODO: Implement actual digest processing logic
await new Promise((resolve) => setTimeout(resolve, 1000));

logger.info("Digest job completed");
return NextResponse.json({ success: true });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Refactor return type and complete implementation.

This function has several issues:

  1. Return type mismatch: Queue handlers shouldn't return NextResponse objects. They should return simple objects or void. The NextResponse.json() return adds unnecessary serialization overhead for queue processing.
  2. Placeholder implementation: The TODO and setTimeout indicate this is incomplete.
  3. Inconsistent sleep usage: Use the imported sleep utility instead of raw setTimeout for consistency.

Apply this diff to fix the return type and sleep usage:

 async function handleDigestJob(data: DigestJobData) {
   logger.info("Processing digest job", {
     emailAccountId: data.emailAccountId,
     actionId: data.actionId,
     coldEmailId: data.coldEmailId,
     messageId: data.message.id,
   });
 
   // TODO: Implement actual digest processing logic
-  await new Promise((resolve) => setTimeout(resolve, 1000));
+  await sleep(1000);
 
   logger.info("Digest job completed");
-  return NextResponse.json({ success: true });
+  return { success: true };
 }

Would you like me to help implement the actual digest processing logic, or open an issue to track this work?

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/utils/queue/queues.ts around lines 82-95, the handler currently
returns a NextResponse and uses setTimeout as a placeholder; change the function
to not return a NextResponse (return void or a plain object like { success: true
}) and replace the setTimeout placeholder with the imported sleep utility so it
reads await sleep(1000); keep the logging and TODO marker or add a short comment
indicating real digest processing should be implemented later, and ensure the
function signature and any callers are updated to expect a simple return value
or void rather than a NextResponse.

Comment on lines +97 to +127
async function handleCategorizeSendersJob(data: AiCategorizeSendersJobData) {
logger.info("Processing categorize senders job", {
emailAccountId: data.emailAccountId,
senderCount: data.senders.length,
});

try {
// Call the batch categorization logic directly instead of making an HTTP call
// This eliminates unnecessary network overhead and improves performance
const response = await handleBatchRequest(
new Request("http://localhost", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}),
);

if (!response.ok) {
const errorText = await response.text();
throw new Error(
`Batch categorization failed: ${response.status} - ${errorText}`,
);
}

logger.info("Categorize senders job completed successfully");
return NextResponse.json({ success: true });
} catch (error) {
logger.error("Error processing categorize senders job", { error });
throw error;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Refactor to call business logic directly instead of creating fake Request.

Creating a fake Request object with a dummy URL ("http://localhost") is a code smell that adds unnecessary overhead and coupling to the HTTP layer.

Recommended approach:

  1. Extract the core business logic from handleBatchRequest into a separate function (e.g., categorizeSendersBatch)
  2. Call that function directly from this handler
  3. Remove the fake Request creation

Additional issue: Same return type problem as other handlers - should return a plain object instead of NextResponse.json().

Example refactor:

 async function handleCategorizeSendersJob(data: AiCategorizeSendersJobData) {
   logger.info("Processing categorize senders job", {
     emailAccountId: data.emailAccountId,
     senderCount: data.senders.length,
   });
 
   try {
-    // Call the batch categorization logic directly instead of making an HTTP call
-    // This eliminates unnecessary network overhead and improves performance
-    const response = await handleBatchRequest(
-      new Request("http://localhost", {
-        method: "POST",
-        headers: { "Content-Type": "application/json" },
-        body: JSON.stringify(data),
-      }),
-    );
-
-    if (!response.ok) {
-      const errorText = await response.text();
-      throw new Error(
-        `Batch categorization failed: ${response.status} - ${errorText}`,
-      );
-    }
+    // Extract the business logic from handleBatchRequest into a separate function
+    // and call it directly here
+    await categorizeSendersBatch(data);
 
     logger.info("Categorize senders job completed successfully");
-    return NextResponse.json({ success: true });
+    return { success: true };
   } catch (error) {
     logger.error("Error processing categorize senders job", { error });
     throw error;
   }
 }

You'll need to refactor apps/web/app/api/user/categorize/senders/batch/handle-batch.ts to expose the core logic separately from the HTTP handler.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/utils/queue/queues.ts around lines 97 to 127, the job handler
creates a fake Request and calls handleBatchRequest (HTTP layer) and returns
NextResponse.json(), which is unnecessary and couples the queue code to the HTTP
handler; instead import and call the core business function (e.g.,
categorizeSendersBatch) you should expose from
apps/web/app/api/user/categorize/senders/batch/handle-batch.ts, pass the
AiCategorizeSendersJobData directly, remove the fake Request construction and
HTTP call, handle errors the same way, and return a plain object like { success:
true } (not NextResponse.json); update imports and refactor handle-batch.ts to
export the core logic separately from the route handler.

Comment on lines +129 to +154
async function handleScheduledActionJob(data: ScheduledActionJobData) {
logger.info("Processing scheduled action job", {
scheduledActionId: data.scheduledActionId,
});

// TODO: Implement actual scheduled action logic
await new Promise((resolve) => setTimeout(resolve, 500));

logger.info("Scheduled action job completed");
return NextResponse.json({ success: true });
}

async function handleAiCleanJob(data: AiCleanJobData) {
logger.info("Processing AI clean job", {
emailAccountId: data.emailAccountId,
threadId: data.threadId,
action: data.action,
jobId: data.jobId,
});

// TODO: Implement actual AI clean logic
await new Promise((resolve) => setTimeout(resolve, 3000));

logger.info("AI clean job completed");
return NextResponse.json({ success: true });
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Complete placeholder implementations and fix return types.

Both handleScheduledActionJob and handleAiCleanJob have identical issues:

  1. Placeholder implementations: TODO comments with setTimeout delays
  2. Inconsistent sleep usage: Should use the imported sleep utility
  3. Wrong return type: Should return plain objects instead of NextResponse.json()

Apply this diff:

 async function handleScheduledActionJob(data: ScheduledActionJobData) {
   logger.info("Processing scheduled action job", {
     scheduledActionId: data.scheduledActionId,
   });
 
   // TODO: Implement actual scheduled action logic
-  await new Promise((resolve) => setTimeout(resolve, 500));
+  await sleep(500);
 
   logger.info("Scheduled action job completed");
-  return NextResponse.json({ success: true });
+  return { success: true };
 }
 
 async function handleAiCleanJob(data: AiCleanJobData) {
   logger.info("Processing AI clean job", {
     emailAccountId: data.emailAccountId,
     threadId: data.threadId,
     action: data.action,
     jobId: data.jobId,
   });
 
   // TODO: Implement actual AI clean logic
-  await new Promise((resolve) => setTimeout(resolve, 3000));
+  await sleep(3000);
 
   logger.info("AI clean job completed");
-  return NextResponse.json({ success: true });
+  return { success: true };
 }

Would you like me to help implement the actual logic for these handlers, or open issues to track this work?

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

12 issues found across 46 files

Prompt for AI agents (all 12 issues)

Understand the root cause of the following 12 issues and fix them.


<file name="apps/queue-worker/src/http.ts">

<violation number="1" location="apps/queue-worker/src/http.ts:111">
The /v1/jobs handler parses attempts/priority/removeOnComplete/removeOnFail but never forwards them to enqueue, so client-specified overrides are ignored.</violation>

<violation number="2" location="apps/queue-worker/src/http.ts:152">
The /v1/jobs/bulk handler drops attempts/priority/removeOnComplete/removeOnFail when calling bulkEnqueue, preventing clients from using those options.</violation>
</file>

<file name="apps/queue-worker/src/env.ts">

<violation number="1" location="apps/queue-worker/src/env.ts:10">
PORT should use z.coerce.number() so string environment values parse correctly; otherwise any provided PORT causes the env schema to reject and the worker fails to boot.</violation>

<violation number="2" location="apps/queue-worker/src/env.ts:24">
Use z.coerce.number() here so provided concurrency values are parsed from strings; otherwise any configured DEFAULT_CONCURRENCY causes schema validation to fail.</violation>
</file>

<file name="apps/web/utils/scheduled-actions/scheduler.ts">

<violation number="1" location="apps/web/utils/scheduled-actions/scheduler.ts:270">
The enqueueJob call passes unsupported options (delay/jobId) instead of notBefore/deduplicationId, so the queue executes immediately and ignores the configured delay, breaking scheduled actions.</violation>
</file>

<file name="apps/web/utils/queue/queue-manager.ts">

<violation number="1" location="apps/web/utils/queue/queue-manager.ts:76">
Returning null from createQueueWorker prevents the worker runtime from registering BullMQ workers, so Redis queues can no longer process jobs. Please restore a real Worker instance here.</violation>
</file>

<file name="apps/queue-worker/src/queue.ts">

<violation number="1" location="apps/queue-worker/src/queue.ts:60">
Only a small subset of JobsOptions is forwarded to queue.add here. Any other BullMQ options supplied by callers—like backoff, repeat, or timeout—will be ignored, breaking those configurations.</violation>

<violation number="2" location="apps/queue-worker/src/queue.ts:87">
This opts object forwards only a narrow subset of JobsOptions to queue.addBulk. Any caller-supplied BullMQ options beyond these fields (e.g., backoff, timeout, repeat) are lost, preventing those job behaviors from working.</violation>
</file>

<file name="docker-compose.yml">

<violation number="1" location="docker-compose.yml:56">
`depends_on` references `queue-worker`, but no service with that name exists; this prevents the compose stack from starting. Update the dependency to use the actual worker service name.</violation>

<violation number="2" location="docker-compose.yml:68">
`WORKER_BASE_URL` points to `queue-worker`, but the service is named `worker`; the web service will be unable to reach the worker at runtime. Update the URL to reference the actual service name.</violation>
</file>

<file name="apps/web/utils/queue-signature.ts">

<violation number="1" location="apps/web/utils/queue-signature.ts:25">
This conversion assumes every Response payload is valid JSON; when handlers return plain text, streams, or empty bodies, `response.json()` will throw and break the request. Either return the original Response or clone its body without forcing JSON parsing.</violation>
</file>

<file name="apps/web/package.json">

<violation number="1" location="apps/web/package.json:10">
`node .next/standalone/worker.js` will fail because the standalone build never creates that file. The worker code lives under `utils/queue/worker.ts`, so Next copies it into `.next/standalone/apps/web/utils/queue/…`, leaving no root-level `worker.js`. Update the script to point at the generated path or add a build step that emits `worker.js`.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.

const delayMs = item.options?.notBefore
? Math.max(0, item.options.notBefore * 1000 - Date.now())
: undefined;
return {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

The /v1/jobs/bulk handler drops attempts/priority/removeOnComplete/removeOnFail when calling bulkEnqueue, preventing clients from using those options.

Prompt for AI agents
Address the following comment on apps/queue-worker/src/http.ts at line 152:

<comment>The /v1/jobs/bulk handler drops attempts/priority/removeOnComplete/removeOnFail when calling bulkEnqueue, preventing clients from using those options.</comment>

<file context>
@@ -0,0 +1,177 @@
+            const delayMs = item.options?.notBefore
+              ? Math.max(0, item.options.notBefore * 1000 - Date.now())
+              : undefined;
+            return {
+              data: { targetPath, payload: data, headers: item.headers },
+              options: {
</file context>
Fix with Cubic

? Math.max(0, body.options.notBefore * 1000 - Date.now())
: undefined;

const jobId = await enqueue(
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

The /v1/jobs handler parses attempts/priority/removeOnComplete/removeOnFail but never forwards them to enqueue, so client-specified overrides are ignored.

Prompt for AI agents
Address the following comment on apps/queue-worker/src/http.ts at line 111:

<comment>The /v1/jobs handler parses attempts/priority/removeOnComplete/removeOnFail but never forwards them to enqueue, so client-specified overrides are ignored.</comment>

<file context>
@@ -0,0 +1,177 @@
+          ? Math.max(0, body.options.notBefore * 1000 - Date.now())
+          : undefined;
+
+        const jobId = await enqueue(
+          body.queueName,
+          { targetPath, payload: data, headers: body.headers },
</file context>
Fix with Cubic

WORKER_SIGNING_SECRET: z.string().optional(),

// Tuning
DEFAULT_CONCURRENCY: z.number().default(3),
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

Use z.coerce.number() here so provided concurrency values are parsed from strings; otherwise any configured DEFAULT_CONCURRENCY causes schema validation to fail.

Prompt for AI agents
Address the following comment on apps/queue-worker/src/env.ts at line 24:

<comment>Use z.coerce.number() here so provided concurrency values are parsed from strings; otherwise any configured DEFAULT_CONCURRENCY causes schema validation to fail.</comment>

<file context>
@@ -0,0 +1,31 @@
+    WORKER_SIGNING_SECRET: z.string().optional(),
+
+    // Tuning
+    DEFAULT_CONCURRENCY: z.number().default(3),
+    LOG_LEVEL: z
+      .enum([&quot;fatal&quot;, &quot;error&quot;, &quot;warn&quot;, &quot;info&quot;, &quot;debug&quot;, &quot;trace&quot;, &quot;silent&quot;])
</file context>
Suggested change
DEFAULT_CONCURRENCY: z.number().default(3),
DEFAULT_CONCURRENCY: z.coerce.number().default(3),
Fix with Cubic

NODE_ENV: z
.enum(["development", "production", "test"])
.default("development"),
PORT: z.number().default(5070),
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

PORT should use z.coerce.number() so string environment values parse correctly; otherwise any provided PORT causes the env schema to reject and the worker fails to boot.

Prompt for AI agents
Address the following comment on apps/queue-worker/src/env.ts at line 10:

<comment>PORT should use z.coerce.number() so string environment values parse correctly; otherwise any provided PORT causes the env schema to reject and the worker fails to boot.</comment>

<file context>
@@ -0,0 +1,31 @@
+    NODE_ENV: z
+      .enum([&quot;development&quot;, &quot;production&quot;, &quot;test&quot;])
+      .default(&quot;development&quot;),
+    PORT: z.number().default(5070),
+
+    // Redis/BullMQ
</file context>
Suggested change
PORT: z.number().default(5070),
PORT: z.coerce.number().default(5070),
Fix with Cubic

delayInMinutes,
deduplicationId,
});
const job = await enqueueJob("scheduled-actions", payload, {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

The enqueueJob call passes unsupported options (delay/jobId) instead of notBefore/deduplicationId, so the queue executes immediately and ignores the configured delay, breaking scheduled actions.

Prompt for AI agents
Address the following comment on apps/web/utils/scheduled-actions/scheduler.ts at line 270:

<comment>The enqueueJob call passes unsupported options (delay/jobId) instead of notBefore/deduplicationId, so the queue executes immediately and ignores the configured delay, breaking scheduled actions.</comment>

<file context>
@@ -262,57 +263,29 @@ async function scheduleMessage({
-        delayInMinutes,
-        deduplicationId,
-      });
+    const job = await enqueueJob(&quot;scheduled-actions&quot;, payload, {
+      delay: delayInMs,
+      jobId: deduplicationId,
</file context>

✅ Addressed in f813203

options?: JobsOptions & { parallelism?: number },
) {
const queue = getOrCreateQueue(queueName, options?.parallelism);
const job = await queue.add(queueName, data, {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

Only a small subset of JobsOptions is forwarded to queue.add here. Any other BullMQ options supplied by callers—like backoff, repeat, or timeout—will be ignored, breaking those configurations.

Prompt for AI agents
Address the following comment on apps/queue-worker/src/queue.ts at line 60:

<comment>Only a small subset of JobsOptions is forwarded to queue.add here. Any other BullMQ options supplied by callers—like backoff, repeat, or timeout—will be ignored, breaking those configurations.</comment>

<file context>
@@ -0,0 +1,100 @@
+  options?: JobsOptions &amp; { parallelism?: number },
+) {
+  const queue = getOrCreateQueue(queueName, options?.parallelism);
+  const job = await queue.add(queueName, data, {
+    delay: options?.delay,
+    attempts: options?.attempts ?? 5,
</file context>
Fix with Cubic

UPSTASH_REDIS_TOKEN: "${UPSTASH_REDIS_TOKEN}"

UPSTASH_REDIS_TOKEN: ${UPSTASH_REDIS_TOKEN}
WORKER_BASE_URL: "http://queue-worker:5070"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

WORKER_BASE_URL points to queue-worker, but the service is named worker; the web service will be unable to reach the worker at runtime. Update the URL to reference the actual service name.

Prompt for AI agents
Address the following comment on docker-compose.yml at line 68:

<comment>`WORKER_BASE_URL` points to `queue-worker`, but the service is named `worker`; the web service will be unable to reach the worker at runtime. Update the URL to reference the actual service name.</comment>

<file context>
@@ -52,16 +53,37 @@ services:
-      UPSTASH_REDIS_TOKEN: &quot;${UPSTASH_REDIS_TOKEN}&quot;
-
+      UPSTASH_REDIS_TOKEN: ${UPSTASH_REDIS_TOKEN}
+      WORKER_BASE_URL: &quot;http://queue-worker:5070&quot;
+      CRON_SECRET: ${CRON_SECRET}
+    restart: unless-stopped
</file context>
Suggested change
WORKER_BASE_URL: "http://queue-worker:5070"
WORKER_BASE_URL: "http://worker:5070"
Fix with Cubic

depends_on:
- db
- redis
- queue-worker
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

depends_on references queue-worker, but no service with that name exists; this prevents the compose stack from starting. Update the dependency to use the actual worker service name.

Prompt for AI agents
Address the following comment on docker-compose.yml at line 56:

<comment>`depends_on` references `queue-worker`, but no service with that name exists; this prevents the compose stack from starting. Update the dependency to use the actual worker service name.</comment>

<file context>
@@ -52,16 +53,37 @@ services:
     depends_on:
       - db
       - redis
+      - queue-worker
     ports:
       - ${WEB_PORT:-3000}:3000
</file context>
Suggested change
- queue-worker
- worker
Fix with Cubic


return response instanceof NextResponse
? response
: NextResponse.json(await response.json(), {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

This conversion assumes every Response payload is valid JSON; when handlers return plain text, streams, or empty bodies, response.json() will throw and break the request. Either return the original Response or clone its body without forcing JSON parsing.

Prompt for AI agents
Address the following comment on apps/web/utils/queue-signature.ts at line 25:

<comment>This conversion assumes every Response payload is valid JSON; when handlers return plain text, streams, or empty bodies, `response.json()` will throw and break the request. Either return the original Response or clone its body without forcing JSON parsing.</comment>

<file context>
@@ -0,0 +1,30 @@
+
+    return response instanceof NextResponse
+      ? response
+      : NextResponse.json(await response.json(), {
+          status: response.status,
+          headers: response.headers,
</file context>
Fix with Cubic

"build": "cross-env NODE_OPTIONS=--max_old_space_size=16384 prisma migrate deploy && next build",
"start": "next start",
"start:standalone": "node .next/standalone/server.js",
"worker": "node .next/standalone/worker.js",
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

node .next/standalone/worker.js will fail because the standalone build never creates that file. The worker code lives under utils/queue/worker.ts, so Next copies it into .next/standalone/apps/web/utils/queue/…, leaving no root-level worker.js. Update the script to point at the generated path or add a build step that emits worker.js.

Prompt for AI agents
Address the following comment on apps/web/package.json at line 10:

<comment>`node .next/standalone/worker.js` will fail because the standalone build never creates that file. The worker code lives under `utils/queue/worker.ts`, so Next copies it into `.next/standalone/apps/web/utils/queue/…`, leaving no root-level `worker.js`. Update the script to point at the generated path or add a build step that emits `worker.js`.</comment>

<file context>
@@ -6,6 +6,8 @@
     &quot;build&quot;: &quot;cross-env NODE_OPTIONS=--max_old_space_size=16384 prisma migrate deploy &amp;&amp; next build&quot;,
     &quot;start&quot;: &quot;next start&quot;,
+    &quot;start:standalone&quot;: &quot;node .next/standalone/server.js&quot;,
+    &quot;worker&quot;: &quot;node .next/standalone/worker.js&quot;,
     &quot;lint&quot;: &quot;biome check .&quot;,
     &quot;test&quot;: &quot;cross-env RUN_AI_TESTS=false vitest&quot;,
</file context>
Fix with Cubic

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/web/utils/scheduled-actions/scheduler.ts (1)

337-360: Implement job cancellation for Redis/BullMQ or document the limitation clearly.

The cancelMessage function doesn't implement job cancellation for the Redis/BullMQ queue system (lines 349-354). This creates a critical gap: when cancelScheduledActions is called, the database status is updated to CANCELLED, but the job remains in the queue and will still execute.

This inconsistency can lead to:

  • Actions executing despite being marked as cancelled
  • User confusion when "cancelled" actions still take effect
  • Potential data integrity issues if the cancellation was important for correctness

Consider one of these solutions:

Option 1: Implement cancellation (preferred)

   } else {
-    // For Redis/BullMQ, we would need to implement job cancellation
-    // For now, just log that cancellation is not supported
-    logger.warn("Job cancellation not implemented for Redis queue system", {
-      messageId,
-    });
+    // For Redis/BullMQ, remove the job from the queue
+    const queueManager = getQueueManager();
+    await queueManager.removeJob("scheduled-actions", messageId);
+    logger.info("Successfully cancelled Redis queue job", { messageId });
   }

Option 2: Prevent cancellation attempts
Update cancelScheduledActions to throw an error or return early when using Redis/BullMQ, preventing the database status from being incorrectly updated.

apps/web/app/api/resend/summary/all/route.ts (1)

25-45: Include userId in query and job data.

The query only selects email but the handler expects both email and userId. Update the select clause to include the user ID.

Apply this diff:

   const emailAccounts = await prisma.emailAccount.findMany({
     select: { 
       email: true,
+      userId: true,
     },

Then update line 64:

         await enqueueJob(
           "email-summary-all",
-          { email: emailAccount.email },
+          { email: emailAccount.email, userId: emailAccount.userId },
           { targetPath: url },
         );
♻️ Duplicate comments (6)
apps/queue-worker/src/http.ts (1)

111-118: Forward retry and retention options to the worker.

We still drop attempts, priority, removeOnComplete, and removeOnFail in both the single and bulk handlers, so callers cannot tune retries or cleanup. Please pass the full option set through to enqueue/bulkEnqueue just like queue.ts expects.

Apply this diff:

         const jobId = await enqueue(
           body.queueName,
           { targetPath, payload: data, headers: body.headers },
           {
             delay: delayMs,
             parallelism: body.options?.parallelism,
             jobId: dedupId,
+            attempts: body.options?.attempts,
+            priority: body.options?.priority,
+            removeOnComplete: body.options?.removeOnComplete,
+            removeOnFail: body.options?.removeOnFail,
           },
         );
             return {
               data: { targetPath, payload: data, headers: item.headers },
               options: {
                 delay: delayMs,
                 jobId: dedupId,
+                attempts: item.options?.attempts,
+                priority: item.options?.priority,
+                removeOnComplete: item.options?.removeOnComplete,
+                removeOnFail: item.options?.removeOnFail,
               },
             };

Also applies to: 151-155

apps/web/utils/queue/providers/qstash-manager.ts (2)

17-19: Guard QSTASH_TOKEN instead of using !.

The non-null assertion violates our TypeScript guidelines and will fail cryptically if the token is unset. Fail fast with an explicit check before constructing the client. As per coding guidelines.

Apply this diff:

-function getQstashClient(): Client {
-  return new Client({ token: env.QSTASH_TOKEN! });
-}
+function getQstashClient(): Client {
+  if (!env.QSTASH_TOKEN) {
+    throw new Error("QSTASH_TOKEN must be set when QUEUE_SYSTEM=upstash");
+  }
+  return new Client({ token: env.QSTASH_TOKEN });
+}

101-130: Respect per-job notBefore in bulk enqueues.

Bulk paths still ignore each job’s own notBefore, so scheduled messages fire immediately unless the top-level option is set. Please pick job.opts?.notBefore ?? options.notBefore everywhere we enqueue.

Apply this diff:

-            if (options.notBefore) {
-              // For delayed jobs, use enqueueJSON with notBefore
-              const notBefore = options.notBefore;
+            const notBefore = job.opts?.notBefore ?? options.notBefore;
+            if (notBefore) {
               const response = await queue.enqueueJSON({
                 url,
                 body: job.data,
                 notBefore,
                 deduplicationId: job.opts?.deduplicationId,
                 headers: job.opts?.headers ?? options.headers,
               });
               return response?.messageId || "unknown";
             } else {
-      if (options.notBefore) {
-        item.notBefore = options.notBefore;
-      }
+      const notBefore = job.opts?.notBefore ?? options.notBefore;
+      if (notBefore) {
+        item.notBefore = notBefore;
+      }

Also applies to: 156-158

apps/web/utils/queue/queues.ts (3)

82-95: Complete the placeholder implementation.

The handler still contains a TODO comment (line 90), uses setTimeout instead of the imported sleep utility, and returns NextResponse.json which adds unnecessary HTTP overhead for queue processing.

Apply this diff:

 async function handleDigestJob(data: DigestJobData) {
   logger.info("Processing digest job", {
     emailAccountId: data.emailAccountId,
     actionId: data.actionId,
     coldEmailId: data.coldEmailId,
     messageId: data.message.id,
   });
 
-  // TODO: Implement actual digest processing logic
-  await new Promise((resolve) => setTimeout(resolve, 1000));
+  await sleep(1000);
 
   logger.info("Digest job completed");
-  return NextResponse.json({ success: true });
+  return { success: true };
 }

97-127: Refactor to call business logic directly.

Creating a fake Request object to call an HTTP handler from a queue processor adds unnecessary overhead and couples the queue layer to the HTTP layer. Extract the core categorization logic from handleBatchRequest and call it directly.


129-154: Complete placeholder implementations.

Both handleScheduledActionJob and handleAiCleanJob contain TODO comments, use setTimeout instead of the imported sleep utility, and return NextResponse.json which is inappropriate for queue handlers.

Apply this diff:

 async function handleScheduledActionJob(data: ScheduledActionJobData) {
   logger.info("Processing scheduled action job", {
     scheduledActionId: data.scheduledActionId,
   });
 
-  // TODO: Implement actual scheduled action logic
-  await new Promise((resolve) => setTimeout(resolve, 500));
+  await sleep(500);
 
   logger.info("Scheduled action job completed");
-  return NextResponse.json({ success: true });
+  return { success: true };
 }
 
 async function handleAiCleanJob(data: AiCleanJobData) {
   logger.info("Processing AI clean job", {
     emailAccountId: data.emailAccountId,
     threadId: data.threadId,
     action: data.action,
     jobId: data.jobId,
   });
 
-  // TODO: Implement actual AI clean logic
-  await new Promise((resolve) => setTimeout(resolve, 3000));
+  await sleep(3000);
 
   logger.info("AI clean job completed");
-  return NextResponse.json({ success: true });
+  return { success: true };
 }
🧹 Nitpick comments (3)
apps/web/utils/scheduled-actions/scheduler.ts (1)

362-383: Consider renaming function for queue-system agnosticism.

The function name markQStashActionAsExecuting references QStash specifically, but it's now used for all queue systems (QStash and Redis/BullMQ). Consider renaming to markScheduledActionAsExecuting for clarity.

-export async function markQStashActionAsExecuting(scheduledActionId: string) {
+export async function markScheduledActionAsExecuting(scheduledActionId: string) {
   try {
     const updatedAction = await prisma.scheduledAction.update({
apps/web/utils/queue/providers/bullmq-manager.ts (1)

29-38: Consider using dedicated WORKER_SIGNING_SECRET instead of CRON_SECRET.

The code reuses CRON_SECRET for authenticating with the worker service. However, env.ts defines a separate WORKER_SIGNING_SECRET for this purpose. Using the dedicated secret improves security by avoiding credential reuse across different authentication domains.

Apply this diff:

 function getAuthHeaders(): Record<string, string> {
-  if (!env.CRON_SECRET) {
+  if (!env.WORKER_SIGNING_SECRET) {
     throw new Error(
-      "CRON_SECRET is required to authenticate with worker service",
+      "WORKER_SIGNING_SECRET is required to authenticate with worker service",
     );
   }
   return {
-    authorization: `Bearer ${env.CRON_SECRET}`,
+    authorization: `Bearer ${env.WORKER_SIGNING_SECRET}`,
   };
 }
apps/web/utils/queue/queues.ts (1)

453-475: Placeholder implementations remain incomplete.

Both handlers now correctly use sleep and return plain objects instead of NextResponse.json(), which is an improvement. However, they still contain TODO placeholders. Implement the actual logic for email summary and Gmail cleaning.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dc8da7c and 6c2682b.

📒 Files selected for processing (15)
  • .cursor/rules/queues-guidelines.mdc (1 hunks)
  • apps/queue-worker/src/http.ts (1 hunks)
  • apps/web/app/api/clean/gmail/route.ts (2 hunks)
  • apps/web/app/api/clean/route.ts (3 hunks)
  • apps/web/app/api/queue/[queueName]/route.ts (1 hunks)
  • apps/web/app/api/resend/digest/all/route.ts (2 hunks)
  • apps/web/app/api/resend/summary/all/route.ts (2 hunks)
  • apps/web/utils/actions/clean.ts (3 hunks)
  • apps/web/utils/digest/index.ts (2 hunks)
  • apps/web/utils/queue/providers/bullmq-manager.ts (1 hunks)
  • apps/web/utils/queue/providers/qstash-manager.ts (1 hunks)
  • apps/web/utils/queue/publish.ts (1 hunks)
  • apps/web/utils/queue/queues.ts (1 hunks)
  • apps/web/utils/scheduled-actions/scheduler.ts (3 hunks)
  • apps/web/utils/upstash/categorize-senders.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .cursor/rules/queues-guidelines.mdc
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/utils/digest/index.ts
  • apps/web/utils/actions/clean.ts
🧰 Additional context used
📓 Path-based instructions (7)
**/*.{ts,tsx}

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

Import Prisma in the project using import prisma from "@/utils/prisma";

**/*.{ts,tsx}: Don't use TypeScript enums.
Don't use TypeScript const enum.
Don't use the TypeScript directive @ts-ignore.
Don't use primitive type aliases or misleading types.
Don't use empty type parameters in type aliases and interfaces.
Don't use any or unknown as type constraints.
Don't use implicit any type on variable declarations.
Don't let variables evolve into any type through reassignments.
Don't use non-null assertions with the ! postfix operator.
Don't misuse the non-null assertion operator (!) in TypeScript files.
Don't use user-defined types.
Use as const instead of literal types and type annotations.
Use export type for types.
Use import type for types.
Don't declare empty interfaces.
Don't merge interfaces and classes unsafely.
Don't use overload signatures that aren't next to each other.
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces.
Don't use TypeScript namespaces.
Don't export imported variables.
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions.
Don't use parameter properties in class constructors.
Use either T[] or Array consistently.
Initialize each enum member value explicitly.
Make sure all enum members are literal values.

Files:

  • apps/queue-worker/src/http.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/publish.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/utils/queue/providers/qstash-manager.ts
  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/utils/queue/providers/bullmq-manager.ts
  • apps/web/app/api/clean/gmail/route.ts
  • apps/web/utils/queue/queues.ts
**/*.{js,jsx,ts,tsx}

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

**/*.{js,jsx,ts,tsx}: Don't use elements in Next.js projects.
Don't use elements in Next.js projects.
Don't use namespace imports.
Don't access namespace imports dynamically.
Don't use global eval().
Don't use console.
Don't use debugger.
Don't use var.
Don't use with statements in non-strict contexts.
Don't use the arguments object.
Don't use consecutive spaces in regular expression literals.
Don't use the comma operator.
Don't use unnecessary boolean casts.
Don't use unnecessary callbacks with flatMap.
Use for...of statements instead of Array.forEach.
Don't create classes that only have static members (like a static namespace).
Don't use this and super in static contexts.
Don't use unnecessary catch clauses.
Don't use unnecessary constructors.
Don't use unnecessary continue statements.
Don't export empty modules that don't change anything.
Don't use unnecessary escape sequences in regular expression literals.
Don't use unnecessary labels.
Don't use unnecessary nested block statements.
Don't rename imports, exports, and destructured assignments to the same name.
Don't use unnecessary string or template literal concatenation.
Don't use String.raw in template literals when there are no escape sequences.
Don't use useless case statements in switch statements.
Don't use ternary operators when simpler alternatives exist.
Don't use useless this aliasing.
Don't initialize variables to undefined.
Don't use the void operators (they're not familiar).
Use arrow functions instead of function expressions.
Use Date.now() to get milliseconds since the Unix Epoch.
Use .flatMap() instead of map().flat() when possible.
Use literal property access instead of computed property access.
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work.
Use concise optional chaining instead of chained logical expressions.
Use regular expression literals instead of the RegExp constructor when possible.
Don't use number literal object member names th...

Files:

  • apps/queue-worker/src/http.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/publish.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/utils/queue/providers/qstash-manager.ts
  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/utils/queue/providers/bullmq-manager.ts
  • apps/web/app/api/clean/gmail/route.ts
  • apps/web/utils/queue/queues.ts
!pages/_document.{js,jsx,ts,tsx}

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

!pages/_document.{js,jsx,ts,tsx}: Don't import next/document outside of pages/_document.jsx in Next.js projects.
Don't import next/document outside of pages/_document.jsx in Next.js projects.

Files:

  • apps/queue-worker/src/http.ts
  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/publish.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/utils/queue/providers/qstash-manager.ts
  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/utils/queue/providers/bullmq-manager.ts
  • apps/web/app/api/clean/gmail/route.ts
  • apps/web/utils/queue/queues.ts
**/api/**/route.ts

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

**/api/**/route.ts: ALL API routes that handle user data MUST use appropriate authentication and authorization middleware (withAuth or withEmailAccount).
ALL database queries in API routes MUST be scoped to the authenticated user/account (e.g., include userId or emailAccountId in query filters).
Always validate that resources belong to the authenticated user before performing operations (resource ownership validation).
Use withEmailAccount middleware for API routes that operate on a specific email account (i.e., use or require emailAccountId).
Use withAuth middleware for API routes that operate at the user level (i.e., use or require only userId).
Use withError middleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST use withError middleware and validate the cron secret using hasCronSecret(request) or hasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts with captureException and return a 401 status for unauthorized requests.
All parameters in API routes MUST be validated for type, format, and length before use.
Request bodies in API routes MUST be validated using Zod schemas before use.
All Prisma queries in API routes MUST only return necessary fields and never expose sensitive data.
Error messages in API routes MUST not leak internal information or sensitive data; use generic error messages and SafeError where appropriate.
API routes MUST use a consistent error response format, returning JSON with an error message and status code.
All findUnique and findFirst Prisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
All findMany Prisma calls in API routes MUST be scoped to the authenticated user's data.
Never use direct object references in API routes without ownership checks (prevent IDOR vulnerabilities).
Prevent mass assignment vulnerabilities by only allowing explicitly whitelisted fields in update operations in AP...

Files:

  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/clean/gmail/route.ts
apps/web/app/api/**/*.{ts,js}

📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)

apps/web/app/api/**/*.{ts,js}: All API route handlers in 'apps/web/app/api/' must use authentication middleware: withAuth, withEmailAccount, or withError (with custom authentication logic).
All Prisma queries in API routes must include user/account filtering (e.g., emailAccountId or userId in WHERE clauses) to prevent unauthorized data access.
All parameters used in API routes must be validated before use; do not use parameters from 'params' or request bodies directly in queries without validation.
Request bodies in API routes should use Zod schemas for validation.
API routes should only return necessary fields using Prisma's 'select' and must not include sensitive data in error messages.
Error messages in API routes must not reveal internal details; use generic errors and SafeError for user-facing errors.
All QStash endpoints (API routes called via publishToQstash or publishToQstashQueue) must use verifySignatureAppRouter to verify request authenticity.
All cron endpoints in API routes must use hasCronSecret or hasPostCronSecret for authentication.
Do not hardcode weak or plaintext secrets in API route files; secrets must not be directly assigned as string literals.
Review all new withError usage in API routes to ensure custom authentication is implemented where required.

Files:

  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/app/api/resend/summary/all/route.ts
  • apps/web/app/api/clean/route.ts
  • apps/web/app/api/queue/[queueName]/route.ts
  • apps/web/app/api/clean/gmail/route.ts
apps/web/utils/**

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

Create utility functions in utils/ folder for reusable logic

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/publish.ts
  • apps/web/utils/queue/providers/qstash-manager.ts
  • apps/web/utils/queue/providers/bullmq-manager.ts
  • apps/web/utils/queue/queues.ts
apps/web/utils/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)

apps/web/utils/**/*.ts: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size

Files:

  • apps/web/utils/upstash/categorize-senders.ts
  • apps/web/utils/scheduled-actions/scheduler.ts
  • apps/web/utils/queue/publish.ts
  • apps/web/utils/queue/providers/qstash-manager.ts
  • apps/web/utils/queue/providers/bullmq-manager.ts
  • apps/web/utils/queue/queues.ts
🧠 Learnings (3)
📚 Learning: 2025-07-17T04:19:57.099Z
Learnt from: edulelis
Repo: elie222/inbox-zero PR: 576
File: packages/resend/emails/digest.tsx:78-83
Timestamp: 2025-07-17T04:19:57.099Z
Learning: In packages/resend/emails/digest.tsx, the DigestEmailProps type uses `[key: string]: DigestItem[] | undefined | string | Date | undefined` instead of intersection types like `& Record<string, DigestItem[] | undefined>` due to implementation constraints. This was the initial implementation approach and cannot be changed to more restrictive typing.

Applied to files:

  • apps/web/app/api/resend/digest/all/route.ts
  • apps/web/utils/queue/queues.ts
📚 Learning: 2025-08-10T22:03:30.507Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 667
File: apps/web/app/(app)/[emailAccountId]/onboarding/page.tsx:18-25
Timestamp: 2025-08-10T22:03:30.507Z
Learning: In Next.js 15, both `params` and `searchParams` passed to page components in the App Router are Promises that need to be awaited. They should be typed as `Promise<{...}>` and accessed using `await` in server components or React's `use()` hook in client components. This is different from Next.js 14 where they were plain objects.

Applied to files:

  • apps/web/app/api/clean/gmail/route.ts
📚 Learning: 2025-06-05T09:49:12.168Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 485
File: apps/web/app/(landing)/login/page.tsx:41-43
Timestamp: 2025-06-05T09:49:12.168Z
Learning: In Next.js App Router, components that use the `useSearchParams` hook require a Suspense boundary to handle the asynchronous nature of search parameter access. The Suspense wrapper is necessary and should not be removed when a component uses useSearchParams.

Applied to files:

  • apps/web/app/api/clean/gmail/route.ts
🧬 Code graph analysis (12)
apps/queue-worker/src/http.ts (3)
apps/queue-worker/src/env.ts (1)
  • env (5-31)
apps/web/utils/queue/providers/bullmq-manager.ts (2)
  • enqueue (41-107)
  • bulkEnqueue (109-177)
apps/queue-worker/src/queue.ts (2)
  • enqueue (54-69)
  • bulkEnqueue (71-100)
apps/web/app/api/resend/digest/all/route.ts (3)
apps/web/env.ts (1)
  • env (16-245)
apps/web/utils/upstash/index.ts (1)
  • publishToQstashQueue (56-78)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (47-54)
apps/web/utils/upstash/categorize-senders.ts (3)
apps/web/env.ts (1)
  • env (16-245)
apps/web/utils/upstash/index.ts (1)
  • publishToQstashQueue (56-78)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (47-54)
apps/web/utils/scheduled-actions/scheduler.ts (2)
apps/web/env.ts (1)
  • env (16-245)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (47-54)
apps/web/utils/queue/publish.ts (2)
apps/web/utils/upstash/index.ts (1)
  • publishToQstash (14-35)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (47-54)
apps/web/app/api/resend/summary/all/route.ts (3)
apps/web/env.ts (1)
  • env (16-245)
apps/web/utils/upstash/index.ts (1)
  • publishToQstashQueue (56-78)
apps/web/utils/queue/queue-manager.ts (1)
  • enqueueJob (47-54)
apps/web/app/api/clean/route.ts (3)
apps/web/env.ts (1)
  • env (16-245)
apps/web/utils/queue/publish.ts (1)
  • publishFlowControlled (7-27)
apps/web/utils/queue-signature.ts (1)
  • verifyQueueSignatureAppRouter (5-30)
apps/web/utils/queue/providers/qstash-manager.ts (3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-81)
apps/web/env.ts (1)
  • env (16-245)
apps/web/utils/queue/types.ts (6)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (31-40)
  • BulkEnqueueOptions (42-48)
  • Job (101-101)
  • ConnectionOptions (101-101)
apps/web/app/api/queue/[queueName]/route.ts (5)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-81)
apps/web/utils/internal-api.ts (1)
  • isValidInternalApiKey (6-29)
apps/web/env.ts (1)
  • env (16-245)
apps/web/utils/worker-signature.ts (1)
  • verifyWorkerSignatureAppRouter (24-73)
apps/web/utils/middleware.ts (1)
  • withError (328-346)
apps/web/utils/queue/providers/bullmq-manager.ts (3)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-81)
apps/web/env.ts (1)
  • env (16-245)
apps/web/utils/queue/types.ts (9)
  • QueueManager (50-82)
  • QueueJobData (13-15)
  • EnqueueOptions (31-40)
  • Job (101-101)
  • BulkEnqueueOptions (42-48)
  • ConnectionOptions (101-101)
  • Worker (101-101)
  • Queue (101-101)
  • QueueEvents (101-101)
apps/web/app/api/clean/gmail/route.ts (1)
apps/web/utils/queue-signature.ts (1)
  • verifyQueueSignatureAppRouter (5-30)
apps/web/utils/queue/queues.ts (8)
apps/web/utils/logger.ts (1)
  • createScopedLogger (17-81)
apps/web/app/api/user/categorize/senders/batch/handle-batch.ts (1)
  • handleBatchRequest (19-32)
apps/web/utils/email/provider.ts (1)
  • createEmailProvider (13-29)
apps/web/utils/types.ts (1)
  • ParsedMessage (51-73)
apps/web/utils/rule/consts.ts (1)
  • getRuleName (118-120)
apps/web/app/api/resend/digest/validation.ts (2)
  • storedDigestContentSchema (3-3)
  • Digest (16-16)
apps/web/utils/email.ts (1)
  • extractNameFromEmail (9-16)
apps/web/utils/schedule.ts (1)
  • calculateNextScheduleDate (124-227)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Jit Security
  • GitHub Check: test
🔇 Additional comments (11)
apps/web/utils/scheduled-actions/scheduler.ts (2)

18-21: LGTM!

The null-safe QStash client initialization is appropriate.


266-318: Based on the verification results, I can now provide my final assessment:

The EnqueueOptions interface fully supports all parameters used in the code.

The script output confirmed that the EnqueueOptions interface defined in apps/web/utils/queue/types.ts includes:

  • notBefore?: number (seconds since epoch, QStash style, takes precedence over delay at worker)
  • deduplicationId?: string
  • targetPath?: string
  • headers?: Record<string, string>

The code in the review comment correctly uses all three supported parameters:

  • Line 268-269 calculates notBefore as a Unix timestamp in seconds, matching the expected QStash format
  • Line 302-306 passes notBefore, deduplicationId, and targetPath to enqueueJob(), which aligns with the interface definition

The original concern about "unsupported options (delay/jobId)" has been properly addressed. The code now uses the correct, well-defined options that are explicitly documented in the interface with comments clarifying their purpose and format.

apps/web/app/api/queue/[queueName]/route.ts (2)

1-21: LGTM - imports and documentation are clear.

The file header clearly documents the dual authentication model and the imports are appropriate for the functionality.


127-127: LGTM - proper middleware usage.

Correctly wraps the route handler with withError middleware for consistent error handling, as required by coding guidelines.

apps/web/utils/queue/providers/bullmq-manager.ts (3)

19-27: LGTM!

The function correctly validates the required environment variable and cleans up trailing slashes.


109-177: Verify bulk endpoint behavior.

The implementation looks correct, but ensure the worker service's /v1/jobs/bulk endpoint returns { jobIds: string[] } as expected on line 175, and that error responses are handled consistently with the single-job endpoint.


179-208: LGTM!

Throwing errors for unsupported operations and implementing close() as a no-op is appropriate for an HTTP-based queue manager that delegates to a remote service.

apps/web/utils/queue/queues.ts (2)

22-80: LGTM!

The job data interfaces are well-defined with appropriate TypeScript types.


156-451: LGTM!

The digest processing implementation is comprehensive with proper error handling, transaction safety, and edge case management. Good job resetting digests to PENDING when no executed rules are found (lines 360-369) to prevent them from being stuck.

apps/web/app/api/resend/digest/all/route.ts (2)

55-68: LGTM!

The conditional enqueuing logic correctly branches based on QUEUE_SYSTEM, and the data shape { emailAccountId: emailAccount.id } matches what handleEmailDigestAllJob expects. The handler implementation in queues.ts (lines 156-174) is complete and functional.


17-103: LGTM!

The digest scheduling query and cron authentication are properly implemented. The route handlers correctly use hasCronSecret and hasPostCronSecret with captureException for unauthorized attempts.

Based on coding guidelines

Comment on lines +30 to +34
// Check for cron secret (for scheduled jobs)
const authHeader = request.headers.get("authorization");
if (authHeader === `Bearer ${env.CRON_SECRET}`) {
return true;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use standard cron secret validation utilities.

According to coding guidelines, cron endpoints must use hasCronSecret(request) or hasPostCronSecret(request) instead of manually checking the Bearer token. The current manual comparison doesn't follow the established pattern used elsewhere in the codebase.

Apply this diff to use the standard utility:

+import { hasCronSecret } from "@/utils/cron";
+
 // Internal authentication for Redis/BullMQ jobs
 async function validateInternalRequest(request: NextRequest): Promise<boolean> {
   // Check for internal API key
   if (isValidInternalApiKey(request.headers, logger)) {
     return true;
   }
 
   // Check for cron secret (for scheduled jobs)
-  const authHeader = request.headers.get("authorization");
-  if (authHeader === `Bearer ${env.CRON_SECRET}`) {
+  if (hasCronSecret(request)) {
     return true;
   }
🤖 Prompt for AI Agents
In apps/web/app/api/queue/[queueName]/route.ts around lines 30-34, replace the
manual Authorization Bearer check with the standard helper: call
hasCronSecret(request) (or hasPostCronSecret(request) if this handler is a POST
route) and return its boolean result instead of comparing headers yourself; also
add the appropriate import for the helper from the cron auth utilities (where
other cron endpoints import it) so the route uses the established validation
utility.

request: NextRequest,
{ params }: { params: Promise<Record<string, string>> },
) {
const { queueName } = await params;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate queueName parameter.

The queueName parameter is extracted and used in logs but never validated. According to coding guidelines, all parameters must be validated before use. The codebase appears to have an isValidQueueName utility (referenced in past review comments) that should be used here.

Add validation after extracting queueName:

   const { queueName } = await params;
+
+  // Validate queue name
+  if (!isValidQueueName(queueName)) {
+    logger.warn("Invalid queue name", { queueName });
+    return NextResponse.json(
+      { error: "Invalid queue name" },
+      { status: 400 },
+    );
+  }

You'll need to import isValidQueueName from the appropriate module.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/web/app/api/queue/[queueName]/route.ts around line 51, the extracted
const { queueName } = await params; is not validated; add a validation step
using the existing isValidQueueName utility (import it from its module)
immediately after extracting queueName, and if validation fails return an
appropriate 400 response or throw a BadRequestError per project conventions;
ensure logs only run after successful validation and include the queueName
safely.

Comment on lines +62 to +71
await request.json().catch(() => null);

logger.info("Processing queue job", {
queueName,
queueSystem: env.QUEUE_SYSTEM,
});

// No centralized handling; acknowledge receipt only (legacy fallback)
logger.info("Queue job acknowledged (no-op handler)", { queueName });
return NextResponse.json({ success: true });
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Missing Zod validation and incomplete job processing.

This function has multiple issues that must be addressed:

  1. No Zod validation: Line 62 reads the request body but never validates it. Per coding guidelines and past review comments, "Request bodies in API routes MUST be validated using Zod schemas before use."

  2. Body is unused: The body is read with .catch(() => null) but never assigned or used, making it unclear why it's being read at all.

  3. No-op handler: Lines 69-71 show this endpoint just acknowledges receipt without actually processing jobs. The comment "legacy fallback" suggests this is incomplete. If jobs aren't processed here, where are they processed?

You need to:

  1. Define Zod schemas for each queue's job data type
  2. Validate the request body against the appropriate schema based on queueName
  3. Either process the job here or clearly document why this is intentionally a no-op

Example structure:

const body = await request.json();

// Get the appropriate schema for this queue
const schema = getQueueSchema(queueName);
if (!schema) {
  return NextResponse.json(
    { error: "Unknown queue" },
    { status: 400 },
  );
}

// Validate the body
const validation = schema.safeParse(body);
if (!validation.success) {
  logger.warn("Invalid job data", { queueName, errors: validation.error });
  return NextResponse.json(
    { error: "Invalid job data", details: validation.error },
    { status: 400 },
  );
}

// Process or forward the validated job
// ... actual processing logic ...
🤖 Prompt for AI Agents
In apps/web/app/api/queue/[queueName]/route.ts around lines 62 to 71, the
request body is read but never validated, unused, and the handler is effectively
a no-op; fix by adding Zod schemas for each queue job type (e.g.,
export/getQueueSchema or a map from queueName to schema), read and assign const
body = await request.json(), select the schema for the current queueName and
return a 400 JSON response if none exists, validate with schema.safeParse(body)
and return a 400 with validation errors when validation fails (also log the
validation warning), then use the validated data (validation.data) to either
perform the job processing here or call the designated processor/forwarding
function; if intentionally left as a legacy no-op, replace the no-op response
with a clear comment and a JSON response explaining that processing is handled
elsewhere and include a TODO linking to the responsible service.

Comment on lines +74 to +125
const queueRouteHandler = async (
request: NextRequest,
context: { params: Promise<Record<string, string>> },
): Promise<NextResponse> => {
// Internal Redis requests bypass QStash verification
if (
env.QUEUE_SYSTEM === "redis" &&
(await validateInternalRequest(request))
) {
// If worker signature headers are present and secret configured, verify HMAC
if (
request.headers.has("x-worker-signature") &&
request.headers.has("x-worker-timestamp")
) {
const response = await verifyWorkerSignatureAppRouter(
async (req: Request): Promise<Response> => {
const result = await handleQueueJob(req as NextRequest, context);
return new Response(result.body, {
status: result.status,
statusText: result.statusText,
headers: result.headers,
});
},
)(request);

return response instanceof NextResponse
? response
: NextResponse.json(await response.json(), {
status: response.status,
headers: response.headers,
});
}
return handleQueueJob(request, context);
}

// QStash requests: apply signature verification
const response = await verifySignatureAppRouter(async (req: Request) => {
const result = await handleQueueJob(req as NextRequest, context);
return new Response(result.body, {
status: result.status,
statusText: result.statusText,
headers: result.headers,
});
})(request);

return response instanceof NextResponse
? response
: NextResponse.json(await response.json(), {
status: response.status,
headers: response.headers,
});
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Simplify authentication flow and reduce duplication.

This wrapper function has several issues that make it harder to maintain:

  1. Redundant validation: Lines 81 and 55 both call validateInternalRequest for Redis paths, checking authentication twice.

  2. Duplicated response conversion: Lines 99-104 and 119-124 contain identical response conversion logic.

  3. Verbose conversions: Lines 88-97 and 110-117 have awkward Response ↔ NextResponse conversions that could be abstracted.

Consider restructuring to eliminate redundancy:

 const queueRouteHandler = async (
   request: NextRequest,
   context: { params: Promise<Record<string, string>> },
 ): Promise<NextResponse> => {
-  // Internal Redis requests bypass QStash verification
+  // Helper to convert Response to NextResponse
+  const toNextResponse = async (response: Response): Promise<NextResponse> => {
+    return response instanceof NextResponse
+      ? response
+      : NextResponse.json(await response.json(), {
+          status: response.status,
+          headers: response.headers,
+        });
+  };
+
+  // Redis system: check internal auth, optionally verify worker signature
   if (
     env.QUEUE_SYSTEM === "redis" &&
     (await validateInternalRequest(request))
   ) {
-    // If worker signature headers are present and secret configured, verify HMAC
     if (
       request.headers.has("x-worker-signature") &&
       request.headers.has("x-worker-timestamp")
     ) {
       const response = await verifyWorkerSignatureAppRouter(
-        async (req: Request): Promise<Response> => {
-          const result = await handleQueueJob(req as NextRequest, context);
-          return new Response(result.body, {
-            status: result.status,
-            statusText: result.statusText,
-            headers: result.headers,
-          });
-        },
+        (req: Request) => handleQueueJob(req as NextRequest, context),
       )(request);
-
-      return response instanceof NextResponse
-        ? response
-        : NextResponse.json(await response.json(), {
-            status: response.status,
-            headers: response.headers,
-          });
+      return toNextResponse(response);
     }
+    // No worker signature, proceed directly (already validated above)
     return handleQueueJob(request, context);
   }
 
-  // QStash requests: apply signature verification
-  const response = await verifySignatureAppRouter(async (req: Request) => {
-    const result = await handleQueueJob(req as NextRequest, context);
-    return new Response(result.body, {
-      status: result.status,
-      statusText: result.statusText,
-      headers: result.headers,
-    });
-  })(request);
-
-  return response instanceof NextResponse
-    ? response
-    : NextResponse.json(await response.json(), {
-        status: response.status,
-        headers: response.headers,
-      });
+  // QStash system: verify signature
+  const response = await verifySignatureAppRouter(
+    (req: Request) => handleQueueJob(req as NextRequest, context),
+  )(request);
+  return toNextResponse(response);
 };

Note: After this refactor, you can remove the validateInternalRequest check from handleQueueJob (lines 54-60) since it's already checked in the wrapper for Redis paths.

Comment on lines +53 to +67
if (env.QUEUE_SYSTEM === "upstash") {
await publishToQstashQueue({
queueName: "email-summary-all",
parallelism: 3,
url,
body: { email: emailAccount.email },
headers: getCronSecretHeader(),
});
} else {
await enqueueJob(
"email-summary-all",
{ email: emailAccount.email },
{ targetPath: url },
);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify data shape compatibility with handler.

The code enqueues { email: emailAccount.email } (line 64), but handleEmailSummaryAllJob in queues.ts expects EmailSummaryAllJobData which requires both email and userId fields (lines 67-70 in queues.ts). This will cause the handler to receive incomplete data.

Run this script to verify the type definitions:


🏁 Script executed:

#!/bin/bash
# Check EmailSummaryAllJobData interface definition
ast-grep --pattern 'export interface EmailSummaryAllJobData {
  $$$
}'

Length of output: 214


I need to search for the actual code in the route.ts file to verify what data is being passed. Let me generate a script to inspect that:


---

🏁 Script executed:

```shell
#!/bin/bash
# Check the actual enqueue calls in the route.ts file around lines 53-67
cat apps/web/app/api/resend/summary/all/route.ts | sed -n '50,70p'

Length of output: 76


Let me search for the correct file location:


🏁 Script executed:

#!/bin/bash
# Find files matching the pattern for the email summary route
fd -t f "route.ts" | grep -i summary

Length of output: 142



🏁 Script executed:

#!/bin/bash
# Read the full route.ts file to check the enqueue calls
cat apps/web/app/api/resend/summary/all/route.ts

Length of output: 2863


Now let me verify the EmailAccount schema to confirm userId is available:


🏁 Script executed:

#!/bin/bash
# Check the EmailAccount model in Prisma schema
rg -A 20 "model EmailAccount" --type prisma

Length of output: 30



🏁 Script executed:

#!/bin/bash
# Find and check the Prisma schema file
fd -t f "schema.prisma" -x cat {} | grep -A 20 "model EmailAccount"

Length of output: 1020



🏁 Script executed:

#!/bin/bash
# Search for userId in EmailAccount model
fd -t f "schema.prisma" -x cat {} | grep -A 50 "model EmailAccount" | grep -E "(userId|user\s+User)"

Length of output: 103


Add missing userId to the email account query and job data.

The Prisma query on line 27 only selects email, but EmailSummaryAllJobData requires both email and userId fields. Update the query to select: { email: true, userId: true } and pass both fields in the job data: { email: emailAccount.email, userId: emailAccount.userId } for both the upstash (line 59) and enqueueJob (line 64) paths.

🤖 Prompt for AI Agents
In apps/web/app/api/resend/summary/all/route.ts around lines 53 to 67 (and
update the Prisma query at line 27), the code only selects email but
EmailSummaryAllJobData requires both email and userId; change the Prisma select
to select: { email: true, userId: true } and update both job dispatch paths to
include both fields in the body/job data: { email: emailAccount.email, userId:
emailAccount.userId } (for the upstash publish and the enqueueJob call),
ensuring types align with EmailSummaryAllJobData.

Comment on lines +48 to +65
const mapPath = (name: string, fallback: string) => {
switch (name) {
case "digest-item-summarize":
return "/api/ai/digest";
case "scheduled-actions":
return "/api/scheduled-actions/execute";
case "ai-clean":
return "/api/clean";
case "email-digest-all":
return "/api/resend/digest";
case "email-summary-all":
return "/api/resend/summary";
case "clean-gmail":
return "/api/clean/gmail";
default:
return fallback;
}
};
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract duplicated path mapping logic.

The mapPath function is duplicated between enqueue (lines 48-65) and bulkEnqueue (lines 123-139). This violates DRY principles and creates maintenance risk—changes to queue-to-path mappings must be kept in sync.

Extract the mapping as a class-level or module-level helper:

+function getCallbackPathForQueue(queueName: string): string {
+  switch (queueName) {
+    case "digest-item-summarize":
+      return "/api/ai/digest";
+    case "scheduled-actions":
+      return "/api/scheduled-actions/execute";
+    case "ai-clean":
+      return "/api/clean";
+    case "email-digest-all":
+      return "/api/resend/digest";
+    case "email-summary-all":
+      return "/api/resend/summary";
+    case "clean-gmail":
+      return "/api/clean/gmail";
+    default:
+      return `/api/queue/${queueName}`;
+  }
+}
+
 export class BullMQManager implements QueueManager {
   async enqueue<T extends QueueJobData>(
     queueName: string,
     data: T,
     options: EnqueueOptions = {},
   ): Promise<Job<T> | string> {
     const url = `${getWorkerBaseUrl()}/v1/jobs`;
     const base = env.WEBHOOK_URL || env.NEXT_PUBLIC_BASE_URL || "";
-    const mapPath = (name: string, fallback: string) => {
-      switch (name) {
-        case "digest-item-summarize":
-          return "/api/ai/digest";
-        case "scheduled-actions":
-          return "/api/scheduled-actions/execute";
-        case "ai-clean":
-          return "/api/clean";
-        case "email-digest-all":
-          return "/api/resend/digest";
-        case "email-summary-all":
-          return "/api/resend/summary";
-        case "clean-gmail":
-          return "/api/clean/gmail";
-        default:
-          return fallback;
-      }
-    };
     const callbackPath =
-      options.targetPath ?? mapPath(queueName, `/api/queue/${queueName}`);
+      options.targetPath ?? getCallbackPathForQueue(queueName);

Then reuse in bulkEnqueue as well.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const mapPath = (name: string, fallback: string) => {
switch (name) {
case "digest-item-summarize":
return "/api/ai/digest";
case "scheduled-actions":
return "/api/scheduled-actions/execute";
case "ai-clean":
return "/api/clean";
case "email-digest-all":
return "/api/resend/digest";
case "email-summary-all":
return "/api/resend/summary";
case "clean-gmail":
return "/api/clean/gmail";
default:
return fallback;
}
};
function getCallbackPathForQueue(queueName: string): string {
switch (queueName) {
case "digest-item-summarize":
return "/api/ai/digest";
case "scheduled-actions":
return "/api/scheduled-actions/execute";
case "ai-clean":
return "/api/clean";
case "email-digest-all":
return "/api/resend/digest";
case "email-summary-all":
return "/api/resend/summary";
case "clean-gmail":
return "/api/clean/gmail";
default:
return `/api/queue/${queueName}`;
}
}
export class BullMQManager implements QueueManager {
async enqueue<T extends QueueJobData>(
queueName: string,
data: T,
options: EnqueueOptions = {},
): Promise<Job<T> | string> {
const url = `${getWorkerBaseUrl()}/v1/jobs`;
const base = env.WEBHOOK_URL || env.NEXT_PUBLIC_BASE_URL || "";
const callbackPath =
options.targetPath ?? getCallbackPathForQueue(queueName);
🤖 Prompt for AI Agents
In apps/web/utils/queue/providers/bullmq-manager.ts around lines 48-65, the
mapPath switch is duplicated; extract the mapping into a single module-level
helper (e.g., a const object map or a top-level function) and replace the inline
mapPath in both enqueue and bulkEnqueue to call that shared helper; ensure the
new helper is declared above both functions, preserves the same fallback
behavior, and update both callers to use it so all queue-to-path mappings are
maintained in one place.

Comment on lines +20 to +27
if (env.QUEUE_SYSTEM === "upstash") {
return publishToQstash<T>(url, body, flowControl);
}
return enqueueJob(redisQueueName, body, {
targetPath: url,
headers,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix absolute URL handling in publishFlowControlled.

When QUEUE_SYSTEM is upstash, this sends the already-prefixed absolute URL into publishToQstash, which then prepends the base again. Every publish ends up pointing at an invalid URL like https://app…https://app…, so Gmail clean jobs fail in the default configuration. Convert absolute inputs to a path (preserving query) before calling publishToQstash.

Apply this diff:

   if (env.QUEUE_SYSTEM === "upstash") {
-    return publishToQstash<T>(url, body, flowControl);
+    const path =
+      url.startsWith("http://") || url.startsWith("https://")
+        ? (() => {
+            const parsed = new URL(url);
+            return `${parsed.pathname}${parsed.search}`;
+          })()
+        : url;
+    return publishToQstash<T>(path, body, flowControl);
   }
📝 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
if (env.QUEUE_SYSTEM === "upstash") {
return publishToQstash<T>(url, body, flowControl);
}
return enqueueJob(redisQueueName, body, {
targetPath: url,
headers,
});
}
if (env.QUEUE_SYSTEM === "upstash") {
const path =
url.startsWith("http://") || url.startsWith("https://")
? (() => {
const parsed = new URL(url);
return `${parsed.pathname}${parsed.search}`;
})()
: url;
return publishToQstash<T>(path, body, flowControl);
}
return enqueueJob(redisQueueName, body, {
targetPath: url,
headers,
});
}
🤖 Prompt for AI Agents
In apps/web/utils/queue/publish.ts around lines 20 to 27, when QUEUE_SYSTEM ===
"upstash" the code passes an already-prefixed absolute URL into publishToQstash
which causes the base to be prepended twice; modify the branch so that if url is
an absolute URL you extract and pass only the path + query portion (preserving
the query string) to publishToQstash, otherwise pass the original url; ensure
this conversion is done before calling publishToQstash and keep flowControl and
body unchanged.

return Number.isNaN(index) ? null : index;
}

export const QUEUE_HANDLERS = {} as const;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Complete QUEUE_HANDLERS mapping.

The QUEUE_HANDLERS constant is declared as an empty object but should map queue names to their handler functions. Based on the handlers defined earlier in the file and the AI summary mentioning specific mappings, this needs to be populated.

Expected structure:

-export const QUEUE_HANDLERS = {} as const;
+export const QUEUE_HANDLERS = {
+  "digest-item-summarize": handleDigestJob,
+  "scheduled-actions": handleScheduledActionJob,
+  "ai-clean": handleAiCleanJob,
+  "email-digest-all": handleEmailDigestAllJob,
+  "email-summary-all": handleEmailSummaryAllJob,
+  "clean-gmail": handleCleanGmailJob,
+  ...Object.fromEntries(
+    Array.from({ length: AI_CATEGORIZE_SENDERS_QUEUE_COUNT }, (_, i) => [
+      `ai-categorize-senders-${i}`,
+      handleCategorizeSendersJob,
+    ])
+  ),
+  ...Object.fromEntries(
+    Array.from({ length: AI_CLEAN_QUEUE_COUNT }, (_, i) => [
+      `ai-clean-${i}`,
+      handleAiCleanJob,
+    ])
+  ),
+} as const;
🤖 Prompt for AI Agents
In apps/web/utils/queue/queues.ts around line 532, QUEUE_HANDLERS is defined as
an empty object but must map each queue name to its corresponding handler
function declared earlier in this file; replace the empty object with a const
object literal that lists each queue key (using the exact queue name strings
used elsewhere) and assigns the appropriate handler identifier (the handler
functions declared above), preserving the const assertion and types. Ensure keys
exactly match the queue names used by the producer/consumer code, include all
handlers referenced in the file, and export the fully populated QUEUE_HANDLERS
so callers receive correct typed handlers.

Comment on lines 13 to 16
interface ScheduledActionPayload {
scheduledActionId: string;
[key: string]: unknown;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Index signature reduces type safety.

The [key: string]: unknown index signature makes ScheduledActionPayload overly permissive, allowing any property to be added without validation. If additional fields are needed, consider explicitly declaring them or using a discriminated union based on action type.

 interface ScheduledActionPayload {
   scheduledActionId: string;
-  [key: string]: unknown;
 }

Committable suggestion skipped: line range outside the PR's diff.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

21 issues found across 48 files

Prompt for AI agents (all 21 issues)

Understand the root cause of the following 21 issues and fix them.


<file name="apps/web/utils/queue-signature.ts">

<violation number="1" location="apps/web/utils/queue-signature.ts:25">
Converting non-NextResponse results with NextResponse.json forces JSON parsing; if the handler returns plain text or another body type, this throws and breaks the route. Replace this with NextResponse.from(response) so arbitrary responses are preserved.</violation>
</file>

<file name="apps/web/app/api/clean/route.ts">

<violation number="1" location="apps/web/app/api/clean/route.ts:280">
`publishFlowControlled` expects a path here; passing an absolute URL makes Upstash builds prefix the base twice and the webhook publish will fail.</violation>
</file>

<file name="apps/web/utils/worker-signature.ts">

<violation number="1" location="apps/web/utils/worker-signature.ts:60">
Guard timingSafeEqual with the actual byte length; `String.length` can match while the underlying buffers differ, causing timingSafeEqual to throw and the request to crash when a multibyte header is supplied.</violation>
</file>

<file name="apps/web/utils/queue/publish.ts">

<violation number="1" location="apps/web/utils/queue/publish.ts:21">
Passing the full URL to publishToQstash duplicates the base URL, producing invalid Upstash webhook targets. Ensure only a relative path is forwarded to publishToQstash so the composed URL remains valid.</violation>
</file>

<file name="docker/Dockerfile.worker">

<violation number="1" location="docker/Dockerfile.worker:36">
The runner image copies the entire monorepo node_modules without pruning, so all dev dependencies ship in production. Please prune to production-only dependencies before copying (e.g., run pnpm prune --prod in the builder stage and copy the pruned output).</violation>
</file>

<file name="docker-compose.yml">

<violation number="1" location="docker-compose.yml:56">
`depends_on` references a `queue-worker` service that is not defined (the new service is named `worker`), so Compose will error when starting the web service.</violation>

<violation number="2" location="docker-compose.yml:68">
`WORKER_BASE_URL` should use the actual worker service name; pointing to `queue-worker` will break communication because that hostname is undefined.</violation>
</file>

<file name="apps/queue-worker/src/http.ts">

<violation number="1" location="apps/queue-worker/src/http.ts:116">
The single-job endpoint parses attempts/priority/removeOnComplete/removeOnFail but only forwards delay/parallelism/jobId to enqueue, so clients cannot override these queue options despite the API accepting them.</violation>

<violation number="2" location="apps/queue-worker/src/http.ts:152">
The bulk endpoint only forwards delay/jobId for each item (and no global overrides), so attempts/priority/removeOnComplete/removeOnFail provided by clients are dropped and never reach bulkEnqueue.</violation>
</file>

<file name="apps/web/utils/queue/providers/qstash-manager.ts">

<violation number="1" location="apps/web/utils/queue/providers/qstash-manager.ts:81">
Jobs without emailAccountId are dropped entirely: the warning path runs `continue`, so those jobs never reach any queue and are lost even though the message claims only grouping is skipped.</violation>
</file>

<file name="apps/queue-worker/src/processor.ts">

<violation number="1" location="apps/queue-worker/src/processor.ts:31">
Spreading job-supplied headers after the Authorization header allows a job payload to replace the Bearer token entirely, defeating the authentication guarantee for the callback request.</violation>
</file>

<file name="apps/queue-worker/src/env.ts">

<violation number="1" location="apps/queue-worker/src/env.ts:10">
`PORT` should use a coercing number schema; otherwise any provided string env value will fail validation and crash the worker.</violation>

<violation number="2" location="apps/queue-worker/src/env.ts:24">
Use a coercing number schema for DEFAULT_CONCURRENCY so string env inputs don&#39;t fail validation.</violation>
</file>

<file name="apps/web/app/api/queue/[queueName]/route.ts">

<violation number="1" location="apps/web/app/api/queue/[queueName]/route.ts:32">
This authorization check accepts requests even when CRON_SECRET is unset: the template string resolves to &quot;Bearer undefined&quot;, so any caller using that header bypasses auth. Please require the secret to be present before matching.</violation>
</file>

<file name="apps/web/package.json">

<violation number="1" location="apps/web/package.json:10">
The new npm script points to .next/standalone/worker.js, but the build never produces that file, so the worker process will crash. Please either add a build step that outputs this bundle or point the script at the actual worker entry.</violation>
</file>

<file name="apps/web/utils/scheduled-actions/scheduler.test.ts">

<violation number="1" location="apps/web/utils/scheduled-actions/scheduler.test.ts:11">
The mocked QStash Client instance lacks publishJSON, but scheduler.ts calls client.publishJSON when creating delayed actions, so tests exercising that path will now throw at runtime.</violation>
</file>

<file name="apps/queue-worker/src/queue.ts">

<violation number="1" location="apps/queue-worker/src/queue.ts:60">
Only a subset of the provided JobsOptions are forwarded to queue.add. Any other BullMQ options passed by callers (backoff, repeat, timeout, etc.) are currently ignored, so those features break.</violation>

<violation number="2" location="apps/queue-worker/src/queue.ts:87">
bulkEnqueue drops most JobsOptions fields when building opts, so callers cannot use BullMQ features like backoff, timeout, or repeat with this helper.</violation>
</file>

<file name="biome.json">

<violation number="1" location="biome.json:108">
The exclusion pattern targets apps/web/worker.js, but the actual worker file lives at apps/web/utils/queue/worker.ts, so this rule will not exclude the worker as intended. Please update the path to the existing file.</violation>
</file>

<file name="apps/web/utils/queue/queue-manager.ts">

<violation number="1" location="apps/web/utils/queue/queue-manager.ts:76">
Returning null here means registerWorker never receives a Worker even when QUEUE_SYSTEM is &quot;redis&quot;, so the Redis startup path in instrumentation stops creating BullMQ workers and jobs will no longer be processed.</violation>
</file>

<file name="apps/web/utils/queue/queues.ts">

<violation number="1" location="apps/web/utils/queue/queues.ts:532">
QUEUE_HANDLERS is empty, so no queues will resolve to a handler. Populate this mapping with the queue-to-handler functions defined above; leaving it empty will prevent jobs from being processed.</violation>
</file>

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.


return response instanceof NextResponse
? response
: NextResponse.json(await response.json(), {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

Converting non-NextResponse results with NextResponse.json forces JSON parsing; if the handler returns plain text or another body type, this throws and breaks the route. Replace this with NextResponse.from(response) so arbitrary responses are preserved.

Prompt for AI agents
Address the following comment on apps/web/utils/queue-signature.ts at line 25:

<comment>Converting non-NextResponse results with NextResponse.json forces JSON parsing; if the handler returns plain text or another body type, this throws and breaks the route. Replace this with NextResponse.from(response) so arbitrary responses are preserved.</comment>

<file context>
@@ -0,0 +1,30 @@
+
+    return response instanceof NextResponse
+      ? response
+      : NextResponse.json(await response.json(), {
+          status: response.status,
+          headers: response.headers,
</file context>
Suggested change
: NextResponse.json(await response.json(), {
: NextResponse.from(response);
Fix with Cubic

key: `gmail-action-${emailAccountId}`,
ratePerSecond: maxRatePerSecond,
publishFlowControlled({
url: `${base}/api/clean/gmail`,
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

publishFlowControlled expects a path here; passing an absolute URL makes Upstash builds prefix the base twice and the webhook publish will fail.

Prompt for AI agents
Address the following comment on apps/web/app/api/clean/route.ts at line 280:

<comment>`publishFlowControlled` expects a path here; passing an absolute URL makes Upstash builds prefix the base twice and the webhook publish will fail.</comment>

<file context>
@@ -270,10 +274,16 @@ function getPublish({
-        key: `gmail-action-${emailAccountId}`,
-        ratePerSecond: maxRatePerSecond,
+      publishFlowControlled({
+        url: `${base}/api/clean/gmail`,
+        body: cleanGmailBody,
+        flowControl: {
</file context>
Fix with Cubic


// Constant-time compare when lengths match
const ok =
signature.length === expected.length &&
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

Guard timingSafeEqual with the actual byte length; String.length can match while the underlying buffers differ, causing timingSafeEqual to throw and the request to crash when a multibyte header is supplied.

Prompt for AI agents
Address the following comment on apps/web/utils/worker-signature.ts at line 60:

<comment>Guard timingSafeEqual with the actual byte length; `String.length` can match while the underlying buffers differ, causing timingSafeEqual to throw and the request to crash when a multibyte header is supplied.</comment>

<file context>
@@ -0,0 +1,73 @@
+
+    // Constant-time compare when lengths match
+    const ok =
+      signature.length === expected.length &amp;&amp;
+      timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
+
</file context>
Suggested change
signature.length === expected.length &&
Buffer.byteLength(signature) === Buffer.byteLength(expected) &&
Fix with Cubic

headers?: Record<string, string>;
}) {
if (env.QUEUE_SYSTEM === "upstash") {
return publishToQstash<T>(url, body, flowControl);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

Passing the full URL to publishToQstash duplicates the base URL, producing invalid Upstash webhook targets. Ensure only a relative path is forwarded to publishToQstash so the composed URL remains valid.

Prompt for AI agents
Address the following comment on apps/web/utils/queue/publish.ts at line 21:

<comment>Passing the full URL to publishToQstash duplicates the base URL, producing invalid Upstash webhook targets. Ensure only a relative path is forwarded to publishToQstash so the composed URL remains valid.</comment>

<file context>
@@ -0,0 +1,27 @@
+  headers?: Record&lt;string, string&gt;;
+}) {
+  if (env.QUEUE_SYSTEM === &quot;upstash&quot;) {
+    return publishToQstash&lt;T&gt;(url, body, flowControl);
+  }
+  return enqueueJob(redisQueueName, body, {
</file context>
Fix with Cubic

# Copy built worker app and node_modules
COPY --from=builder /app/apps/queue-worker/dist ./apps/queue-worker/dist
COPY --from=builder /app/apps/queue-worker/package.json ./apps/queue-worker/package.json
COPY --from=builder /app/node_modules ./node_modules
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

The runner image copies the entire monorepo node_modules without pruning, so all dev dependencies ship in production. Please prune to production-only dependencies before copying (e.g., run pnpm prune --prod in the builder stage and copy the pruned output).

Prompt for AI agents
Address the following comment on docker/Dockerfile.worker at line 36:

<comment>The runner image copies the entire monorepo node_modules without pruning, so all dev dependencies ship in production. Please prune to production-only dependencies before copying (e.g., run pnpm prune --prod in the builder stage and copy the pruned output).</comment>

<file context>
@@ -0,0 +1,44 @@
+# Copy built worker app and node_modules
+COPY --from=builder /app/apps/queue-worker/dist ./apps/queue-worker/dist
+COPY --from=builder /app/apps/queue-worker/package.json ./apps/queue-worker/package.json
+COPY --from=builder /app/node_modules ./node_modules
+COPY --from=builder /app/package.json ./package.json
+
</file context>
Fix with Cubic

const jobs = items.map((item) => ({
name: queueName,
data: item.data,
opts: {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

bulkEnqueue drops most JobsOptions fields when building opts, so callers cannot use BullMQ features like backoff, timeout, or repeat with this helper.

Prompt for AI agents
Address the following comment on apps/queue-worker/src/queue.ts at line 87:

<comment>bulkEnqueue drops most JobsOptions fields when building opts, so callers cannot use BullMQ features like backoff, timeout, or repeat with this helper.</comment>

<file context>
@@ -0,0 +1,100 @@
+  const jobs = items.map((item) =&gt; ({
+    name: queueName,
+    data: item.data,
+    opts: {
+      delay: item.options?.delay ?? options?.delay,
+      attempts: item.options?.attempts ?? options?.attempts ?? 5,
</file context>
Fix with Cubic

options?: JobsOptions & { parallelism?: number },
) {
const queue = getOrCreateQueue(queueName, options?.parallelism);
const job = await queue.add(queueName, data, {
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

Only a subset of the provided JobsOptions are forwarded to queue.add. Any other BullMQ options passed by callers (backoff, repeat, timeout, etc.) are currently ignored, so those features break.

Prompt for AI agents
Address the following comment on apps/queue-worker/src/queue.ts at line 60:

<comment>Only a subset of the provided JobsOptions are forwarded to queue.add. Any other BullMQ options passed by callers (backoff, repeat, timeout, etc.) are currently ignored, so those features break.</comment>

<file context>
@@ -0,0 +1,100 @@
+  options?: JobsOptions &amp; { parallelism?: number },
+) {
+  const queue = getOrCreateQueue(queueName, options?.parallelism);
+  const job = await queue.add(queueName, data, {
+    delay: options?.delay,
+    attempts: options?.attempts ?? 5,
</file context>
Fix with Cubic

"!sw.js",
"!.vscode"
"!.vscode",
"!apps/web/worker.js"
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

The exclusion pattern targets apps/web/worker.js, but the actual worker file lives at apps/web/utils/queue/worker.ts, so this rule will not exclude the worker as intended. Please update the path to the existing file.

Prompt for AI agents
Address the following comment on biome.json at line 108:

<comment>The exclusion pattern targets apps/web/worker.js, but the actual worker file lives at apps/web/utils/queue/worker.ts, so this rule will not exclude the worker as intended. Please update the path to the existing file.</comment>

<file context>
@@ -104,7 +104,8 @@
       &quot;!sw.js&quot;,
-      &quot;!.vscode&quot;
+      &quot;!.vscode&quot;,
+      &quot;!apps/web/worker.js&quot;
     ]
   },
</file context>
Suggested change
"!apps/web/worker.js"
"!apps/web/utils/queue/worker.ts"
Fix with Cubic

queueSystem: env.QUEUE_SYSTEM,
queueName: _queueName,
});
return null;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

Returning null here means registerWorker never receives a Worker even when QUEUE_SYSTEM is "redis", so the Redis startup path in instrumentation stops creating BullMQ workers and jobs will no longer be processed.

Prompt for AI agents
Address the following comment on apps/web/utils/queue/queue-manager.ts at line 76:

<comment>Returning null here means registerWorker never receives a Worker even when QUEUE_SYSTEM is &quot;redis&quot;, so the Redis startup path in instrumentation stops creating BullMQ workers and jobs will no longer be processed.</comment>

<file context>
@@ -0,0 +1,124 @@
+    queueSystem: env.QUEUE_SYSTEM,
+    queueName: _queueName,
+  });
+  return null;
+}
+
</file context>
Fix with Cubic

return Number.isNaN(index) ? null : index;
}

export const QUEUE_HANDLERS = {} as const;
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Nov 13, 2025

Choose a reason for hiding this comment

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

QUEUE_HANDLERS is empty, so no queues will resolve to a handler. Populate this mapping with the queue-to-handler functions defined above; leaving it empty will prevent jobs from being processed.

Prompt for AI agents
Address the following comment on apps/web/utils/queue/queues.ts at line 532:

<comment>QUEUE_HANDLERS is empty, so no queues will resolve to a handler. Populate this mapping with the queue-to-handler functions defined above; leaving it empty will prevent jobs from being processed.</comment>

<file context>
@@ -0,0 +1,532 @@
+  return Number.isNaN(index) ? null : index;
+}
+
+export const QUEUE_HANDLERS = {} as const;
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants