-
Notifications
You must be signed in to change notification settings - Fork 1.1k
WIP: Flexible queue system with BullMQ and Qstash #856
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@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. |
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Areas to focus during review:
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this 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 removalThe verification confirms the review comment's core issue:
cancelScheduledActionsonly updates DB status to CANCELLED but provides no mechanism to cancel queued BullMQ jobs. The queue-manager exports noremoveJoborcancelJobfunction; only QStash messages are explicitly cancelled (lines 195-215).The code is partially protected by
execute/route.tsgating execution on DB status (checkingCANCELLEDat line 67-72), but this is insufficient because:
- The handler itself (
handleScheduledActionJobat 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:
- Add
removeJob(queueName, jobId)to queue-manager exports and BullMQManager implementation- Call it from
cancelScheduledActionsfor each action with a stored jobId (after QStash cancellation)- Replace the
handleScheduledActionJobstub with actual implementation that validates DB statusapps/web/env.ts (1)
16-125: Critical: Missing QStash signing keys will break API routes at runtimeThe removal of
QSTASH_CURRENT_SIGNING_KEYandQSTASH_NEXT_SIGNING_KEYfromenv.tswill cause runtime failures. Six QStash endpoints usingverifySignatureAppRouterrequire these keys to be defined in the environment schema:
apps/web/app/api/user/categorize/senders/batch/route.tsapps/web/app/api/ai/digest/route.tsapps/web/app/api/scheduled-actions/execute/route.tsapps/web/app/api/resend/digest/route.tsapps/web/app/api/clean/route.tsapps/web/app/api/clean/gmail/route.tsAdditionally,
apps/web/app/api/queue/[queueName]/route.tshandles QStash jobs but lacks signature verification entirely, violating the requirement that all QStash endpoints must useverifySignatureAppRouter.Add
QSTASH_CURRENT_SIGNING_KEYandQSTASH_NEXT_SIGNING_KEYto the server section ofenv.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 processingSet 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 handlingEnsure 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 wordingLogs 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-specificmarkQStashActionAsExecuting 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 gatingNice 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-stringifydatain logsLogger 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: UseQueueSystemforQueueSystemInfo.systemStrengthens 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| nullThese functions either return a Worker/Queue or throw (QStash path). Drop
| nullto 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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.mdcapps/web/app/api/queue/[queueName]/route.tsapps/web/env.tsapps/web/utils/queue/worker.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/web/utils/queue/queues.tsapps/web/utils/queue/types.tsapps/web/utils/queue/queue-manager.tsapps/web/app/api/resend/summary/all/route.tsapps/web/package.jsonapps/web/utils/digest/index.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/actions/clean.tsapps/web/utils/scheduled-actions/scheduler.tsapps/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.mdcapps/web/app/api/queue/[queueName]/route.tsapps/web/env.tsapps/web/utils/queue/worker.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/web/utils/queue/queues.tsapps/web/utils/queue/types.tsapps/web/utils/queue/queue-manager.tsapps/web/app/api/resend/summary/all/route.tsapps/web/package.jsonapps/web/utils/digest/index.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/actions/clean.tsapps/web/utils/scheduled-actions/scheduler.tsapps/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.tsapps/web/env.tsapps/web/utils/queue/worker.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/web/utils/queue/queues.tsapps/web/utils/queue/types.tsapps/web/utils/queue/queue-manager.tsapps/web/app/api/resend/summary/all/route.tsapps/web/utils/digest/index.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/actions/clean.tsapps/web/utils/scheduled-actions/scheduler.tsapps/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.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/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: UsewithAuthfor user-level operations
UsewithEmailAccountfor 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 withwithAuthorwithEmailAccountmiddleware 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.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/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.tsapps/web/env.tsapps/web/utils/queue/worker.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/web/utils/queue/queues.tsapps/web/utils/queue/types.tsapps/web/utils/queue/queue-manager.tsapps/web/app/api/resend/summary/all/route.tsapps/web/utils/digest/index.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/actions/clean.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/queue/qstash-manager.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/env.tsapps/web/utils/queue/worker.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/web/utils/queue/queues.tsapps/web/utils/queue/types.tsapps/web/utils/queue/queue-manager.tsapps/web/app/api/resend/summary/all/route.tsapps/web/utils/digest/index.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/actions/clean.tsapps/web/utils/scheduled-actions/scheduler.tsapps/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).
UsewithEmailAccountmiddleware for API routes that operate on a specific email account (i.e., use or requireemailAccountId).
UsewithAuthmiddleware for API routes that operate at the user level (i.e., use or require onlyuserId).
UsewithErrormiddleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST usewithErrormiddleware and validate the cron secret usinghasCronSecret(request)orhasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts withcaptureExceptionand 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.
AllfindUniqueandfindFirstPrisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
AllfindManyPrisma 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.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/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.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/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 useelements 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.tsapps/web/env.tsapps/web/utils/queue/worker.tsapps/web/app/api/clean/route.tsapps/web/app/api/resend/digest/all/route.tsapps/web/utils/queue/queues.tsapps/web/utils/queue/types.tsapps/web/utils/queue/queue-manager.tsapps/web/app/api/resend/summary/all/route.tsapps/web/utils/digest/index.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/actions/clean.tsapps/web/utils/scheduled-actions/scheduler.tsapps/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, andturbo.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 toapps/web/env.tsin the appropriate section: useserverfor server-only variables, and for client-side variables, use theclientsection and also add toexperimental__runtimeEnv.
Client-side environment variables must be prefixed withNEXT_PUBLIC_and added to both theclientandexperimental__runtimeEnvsections inapps/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.tsapps/web/utils/queue/queues.tsapps/web/utils/queue/types.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/digest/index.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/actions/clean.tsapps/web/utils/scheduled-actions/scheduler.tsapps/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.tsapps/web/utils/queue/queues.tsapps/web/utils/queue/types.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/digest/index.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/actions/clean.tsapps/web/utils/scheduled-actions/scheduler.tsapps/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 theserver-onlymodule withvi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it withvi.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()inbeforeEach)
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-actionprovides centralized error handling
Use Zod schemas for validation on both client and server
UserevalidatePathin server actions for cache invalidation
apps/web/utils/actions/**/*.ts: Use server actions (withnext-safe-action) for all mutations (create/update/delete operations); do NOT use POST API routes for mutations.
UserevalidatePathin 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 thenext-safe-actionlibrary for type safety, input validation, context management, and error handling. Refer toapps/web/utils/actions/safe-action.tsfor client definitions (actionClient,actionClientUser,adminActionClient).
UseactionClientUserwhen only authenticated user context (userId) is needed.
UseactionClientwhen both authenticated user context and a specificemailAccountIdare needed. TheemailAccountIdmust be bound when calling the action from the client.
UseadminActionClientfor actions restricted to admin users.
Access necessary context (likeuserId,emailAccountId, etc.) provided by the safe action client via thectxobject 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.
UseSafeErrorfor expected/handled errors within actions if needed.next-safe-actionprovides centralized error handling.
Use the.metadata({ name: "actionName" })method to provide a meaningful name for monitoring. Sentry instrumentation is automatically applied viawithServerActionInstrumentationwithin the safe action clients.
If an action modifies data displayed elsewhere, userevalidatePathorrevalidateTagfromnext/cachewithin 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
publishToQstashtoenqueueJobaligns with the new queue abstraction.
274-274: Queue "clean-gmail" is properly registered.The queue name is correctly registered with handler
handleCleanGmailJobat line 168 ofapps/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
publishToQstashQueuetoenqueueJobaligns 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 dynamicqueueName(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
enqueueJobfrom 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.tswith the handlerhandleEmailDigestAllJob. 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.tswith handlerhandleDigestJob. 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 inapps/web/utils/queue/queues.ts(line 167), confirming the API route correctly uses the registered queue handlerhandleEmailSummaryAllJob. 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 delayThe switch to enqueueJob with milliseconds delay and deduplicationId looks solid.
apps/web/utils/queue/queue.test.ts (1)
54-64: Test isolation looks goodclearAllMocks + resetModules + closeQueueManager is the right combo here.
apps/web/utils/queue/worker.ts (1)
146-158: Graceful shutdown handlers look goodSIGINT/SIGTERM hooks delegating to shutdownAllWorkers and closeQueueManager are appropriate.
There was a problem hiding this 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("email-digest-all", …)` 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 "upstash", 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.
There was a problem hiding this 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.
workerusesredis, butwebinherits defaultupstash. 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.
rediscurrently usesdatabase-data, the same as Postgres. Use a dedicatedredis-datavolume 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.
queueNamecontainsemailAccountIdin 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/deleteQueueare Upstash utilities. Skip whenQUEUE_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
withErrorfor consistent errors, and only require Upstash signature whenQUEUE_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_TOKENis 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
isValidQueueNameinapps/web/utils/queue/queues.tsonly 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
isValidQueueNameand 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:
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.
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., priormockResolvedValueOnce). Keepvi.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
bulkEnqueuewithoptions.delayandjob.opts.jobIdto verify bothnotBeforeand dedup IDs flow intobatchJSONentries.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 startAlso 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
enqueueJobloop with a singlebulkEnqueueJobscall. 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
markQStashActionAsExecutingtomarkScheduledActionAsExecuting(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?.delaywhen set; fall back tooptions.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
handleScheduledActionJobandhandleAiCleanJobcontain 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
📒 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.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/app/api/resend/summary/all/route.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.tsapps/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.tsapps/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: UsewithAuthfor user-level operations
UsewithEmailAccountfor 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 withwithAuthorwithEmailAccountmiddleware 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.tsapps/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.tsapps/web/utils/upstash/categorize-senders.tsdocker/Dockerfile.prodapps/web/utils/scheduled-actions/scheduler.tsdocker-compose.ymlapps/web/app/api/resend/summary/all/route.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.tsapps/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.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/app/api/resend/summary/all/route.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.tsapps/web/worker.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/app/api/resend/summary/all/route.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.tsapps/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).
UsewithEmailAccountmiddleware for API routes that operate on a specific email account (i.e., use or requireemailAccountId).
UsewithAuthmiddleware for API routes that operate at the user level (i.e., use or require onlyuserId).
UsewithErrormiddleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST usewithErrormiddleware and validate the cron secret usinghasCronSecret(request)orhasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts withcaptureExceptionand 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.
AllfindUniqueandfindFirstPrisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
AllfindManyPrisma 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.tsapps/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.tsapps/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 useelements 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.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/app/api/resend/summary/all/route.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.tsapps/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.tsapps/web/utils/upstash/categorize-senders.tsdocker/Dockerfile.prodapps/web/utils/scheduled-actions/scheduler.tsdocker-compose.ymlapps/web/app/api/resend/summary/all/route.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.tsapps/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.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/queue/queue.test.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/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 theserver-onlymodule withvi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it withvi.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()inbeforeEach)
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 inQUEUE_HANDLERS(line 479 ofqueues.ts) and mapped tohandleScheduledActionJob. The validation functionsgetQueueHandler()andisValidQueueName()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:
- The queue-manager.ts implementation to confirm whether BullMQ or only QStash is used
- Lines 298-317 in scheduler.ts (mentioned in review as "Also applies to")
- 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
createScopedLoggerat 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.
There was a problem hiding this 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
queueSystemconsistently.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.
markQStashActionAsExecutingis architecturally generic (updates database without QStash-specific logic) but named for its current usage context. Rename tomarkScheduledActionAsExecutingto 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 theenv.QUEUE_SYSTEM === "upstash"check. Additionally, the execute route gate atapps/web/app/api/scheduled-actions/execute/route.ts:67already 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
📒 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.tsapps/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}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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 useelements 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.tsapps/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 handlestsxavailability in production.The
Dockerfile.prodexplicitly installstsxglobally vianpm install -g [email protected], and the Docker build includes all dependencies (including devDependencies) withpnpm install --no-frozen-lockfile. Theworkerservice indocker-compose.ymluses the same production Docker image as the web service, ensuringtsxis 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.jsonare required.Likely an incorrect or invalid review comment.
There was a problem hiding this 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis 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:
.dockerignorepackage.jsonapps/web/env.tsapps/web/next.config.tsapps/web/utils/queue/queue-manager.tsapps/web/instrumentation.tsdocker/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:
.dockerignorepackage.jsonapps/web/env.tsapps/web/next.config.tsapps/web/utils/queue/queue-manager.tsapps/web/instrumentation.tsdocker/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.tsapps/web/next.config.tsapps/web/utils/queue/queue-manager.tsapps/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, andturbo.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 toapps/web/env.tsin the appropriate section: useserverfor server-only variables, and for client-side variables, use theclientsection and also add toexperimental__runtimeEnv.
Client-side environment variables must be prefixed withNEXT_PUBLIC_and added to both theclientandexperimental__runtimeEnvsections inapps/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.tsapps/web/next.config.tsapps/web/utils/queue/queue-manager.tsapps/web/instrumentation.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/next.config.tsapps/web/utils/queue/queue-manager.tsapps/web/instrumentation.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements 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.tsapps/web/next.config.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/instrumentation.tsdocker/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.jsat the root, makingCMD ["node", "server.js"]the correct entrypoint.
54-58: No action needed—shxis correctly listed as a dependency.Verification confirms
shx(version^0.3.4) is present inapps/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 typefor 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 (
BullMQManagerthrows ifREDIS_URLis 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
enqueueJobandbulkEnqueueJobsare 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
nullwith 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
nullwith 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 typefor type-only exports, following the coding guidelines.
There was a problem hiding this 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="upstash". 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.
There was a problem hiding this 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
__inboxZeroWorkersStartedbefore 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-lockfileallowing 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_QUEUESbypasses 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.jsassumes 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
📒 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.jsdocker/Dockerfile.prodapps/web/instrumentation.tsapps/web/next.config.tsbiome.jsondocker-compose.ymlapps/web/package.json
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements 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.jsapps/web/instrumentation.tsapps/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.jsdocker/Dockerfile.prodapps/web/instrumentation.tsapps/web/next.config.tsbiome.jsondocker-compose.ymlapps/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.tsapps/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.tsapps/web/next.config.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/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_SYSTEMandREDIS_URLenvironment 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_URLmeets the.url()requirement with a valid PostgreSQL connection stringGOOGLE_CLIENT_ID,GOOGLE_CLIENT_SECRET, andGOOGLE_PUBSUB_TOPIC_NAMEall 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 indocker/Dockerfile.prodduring Docker image builds. It is not set in CI/CD workflows, environment files, or local development environments. The conditional logic inapps/web/next.config.tscorrectly isolates the standalone output configuration to Docker builds only, leaving regular and local development builds unaffected.
There was a problem hiding this 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
: QueueSystemInforeturn 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
📒 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.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsbiome.jsonapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements 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.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsbiome.jsonapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/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.tsapps/web/utils/queue/bullmq-manager.tsapps/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.tsapps/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.tsapps/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.jsexcluded from linting but not from formatting.Line 108 adds
"!apps/web/worker.js"tofiles.includes, which excludes it from linting. However, this exclusion is not present informatter.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.
- Clarify the exclusion intent: Should
worker.jsbe completely excluded from Biome processing (both linting and formatting), or is the asymmetric exclusion intentional?- If full exclusion is needed, apply this diff to add
worker.jstoformatter.includesfor consistency:"formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2, "includes": [ "apps/**", "packages/**", "!node_modules/**", "!*.config.*", "!*.json", "!**/tsconfig*.json", "!.turbo/**", "!.next/**", + "!apps/web/worker.js" ] },
- Clarify the
.vscodeaddition: Please explain why!.vscodeis 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 explicit0andfalsevalues. 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
QueueSystemInforeturn type annotation, providing clear documentation and type safety.
There was a problem hiding this 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 "true"/"false" 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.
There was a problem hiding this 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
There was a problem hiding this 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.tsactually 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 enableENABLE_WORKER_QUEUES=truefor 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
📒 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}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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 useelements 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 theserver-onlymodule withvi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it withvi.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()inbeforeEach)
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.
There was a problem hiding this 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 thatapps/web/worker.jsexists in this PR.The build copies
apps/web/worker.jsinto the standalone bundle. A past review comment indicated this file doesn't exist. Ifworker.jsis 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-lockfileand reconsider--shamefully-hoistfor production.Two concerns with the installation flags:
--no-frozen-lockfileallows the lockfile to be modified during installation, leading to non-reproducible builds. Production images should use--frozen-lockfileto ensure exact dependency versions match the lockfile.
--shamefully-hoistflattens 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-offlineapps/web/utils/queue/bullmq-manager.ts (3)
36-38: Remove unnecessary type cast.The type cast
as unknown as ConnectionOptionsis unnecessary. BullMQ'sConnectionOptionsnatively supports theurlfield (typestring). After validation on line 32,env.REDIS_URLis 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 forjob.optsaccess.Line 53 uses
job?.opts.attempts, which will throw ifjobis defined butoptsis 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:
- Any build-time validation or feature flags that depend on these values will use dummy data
- Ensure none of these dummy values are inadvertently inlined into the client bundle (especially concerning for values that might look like real secrets)
- 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
📒 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.tsapps/web/next.config.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/next.config.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.tsdocker/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.tsapps/web/next.config.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/next.config.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements 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.tsapps/web/next.config.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/next.config.tsapps/web/utils/queue/bullmq-manager.tsapps/web/utils/queue/qstash-manager.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/queues.tsdocker/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.tsapps/web/utils/queue/bullmq-manager.tsapps/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.tsapps/web/utils/queue/bullmq-manager.tsapps/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.tsapps/web/utils/queue/qstash-manager.tsapps/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.tsapps/web/utils/queue/bullmq-manager.tsapps/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.tsapps/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.tsapps/web/utils/queue/qstash-manager.tsapps/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.tsapps/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 tofalseand no additional config is applied.docker/Dockerfile.prod (2)
28-33: Review memory allocation and default base URL.Two observations:
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.Default
NEXT_PUBLIC_BASE_URLofhttp://localhost:3000will 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
emailAccountIdand 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
deduplicationIdviaoptions.jobId. The delay path usespublishJSONwithnotBefore, while the non-delay path creates/updates a queue and usesenqueueJSON. ThenotBeforecalculation has been corrected to useMath.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
emailAccountIdand creating per-account queues withparallelism=3. Each job is enqueued with proper support for delays (notBefore) and deduplication (deduplicationId). The fallback for other queues usesbatchJSONappropriately.
153-178: LGTM! Correct error handling for unsupported operations.The methods
createWorker,createQueue, andgetQueueEventscorrectly 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
processedOnfield (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
enqueueJobandbulkEnqueueJobsfunctions 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
nullwith 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
nullwith 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
skipsobject inAiCleanJobDataprovides granular control over processing rules.
97-127: LGTM! Direct batch categorization call avoids HTTP overhead.The function correctly calls
handleBatchRequestdirectly with a syntheticRequestobject. 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
handleEmailDigestAllJobfunction correctly delegates tosendDigestEmailForAccountwith appropriate error handling and logging.getDigestScheduleproperly 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
emailAccountIdalways 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_HANDLERSmapping explicitly defines all queue names and their handlers, including the distributed queues. This provides type safety through theQueueNametype and makes the queue routing explicit.
558-629: LGTM! Backend-aware queue routing and validation.Both
getQueueHandlerandisValidQueueNamecorrectly 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.
There was a problem hiding this 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 "false" to coerce to `true` because Zod uses JavaScript truthiness, so `"false"` 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.
There was a problem hiding this 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
📒 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.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/qstash-manager.ts
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/*.{ts,tsx}: UsecreateScopedLoggerfor logging in backend TypeScript files
Typically add the logger initialization at the top of the file when usingcreateScopedLogger
Only use.with()on a logger instance within a specific function, not for a global loggerImport 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.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/queue-manager.tsapps/web/utils/queue/qstash-manager.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements 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.tsapps/web/utils/queue/queue-manager.tsapps/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.tsapps/web/utils/queue/queue-manager.tsapps/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 theserver-onlymodule withvi.mock("server-only", () => ({}));
When testing code that uses Prisma, mock it withvi.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()inbeforeEach)
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.tsapps/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
There was a problem hiding this 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
There was a problem hiding this 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.
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
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.
|
There was a problem hiding this 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 | |||
There was a problem hiding this comment.
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
Jit Bot commands and options (e.g., ignore issue)
You can trigger Jit actions by commenting on this PR review:
#jit_ignore_fpIgnore and mark this specific single instance of finding as “False Positive”#jit_ignore_acceptIgnore and mark this specific single instance of finding as “Accept Risk”#jit_ignore_type_in_fileIgnore any finding of type "Image user should not be 'root'" in docker/Dockerfile.worker; future occurrences will also be ignored.#jit_undo_ignoreUndo ignore command
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this 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'sBoolean("false")which evaluates totrue(any non-empty string is truthy). This means settingENABLE_WORKER_QUEUES=falsein 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
bodydirectly 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
verifyWorkerSignatureAppRouterimport is not used in this file. It's called internally byverifyQueueSignatureAppRouterfrom 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
sendDigestEmailForAccountfunction 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-256fetchAndBatchMessages(messageIds, emailProvider)- lines 271-289transformDigestItems(pendingDigests, messageMap)- lines 297-353updateDigestStatusAfterSend(processedDigestIds, digestScheduleData)- lines 396-432This 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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| const jobId = await enqueue( | ||
| body.queueName, | ||
| { targetPath, payload: data, headers: body.headers }, | ||
| { | ||
| delay: delayMs, | ||
| parallelism: body.options?.parallelism, | ||
| jobId: dedupId, | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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(), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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_SYSTEMENABLE_WORKER_QUEUESWORKER_BASE_URLWORKER_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.exampleLength 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.jsonLength of output: 5749
Add missing environment variables to .env.example and turbo.json.
Four new variables are missing from required configuration files:
-
apps/web/.env.example – Add to the redis config section (near existing
QSTASH_TOKENentries):QUEUE_SYSTEM=upstash(orredis)ENABLE_WORKER_QUEUES=falseWORKER_BASE_URL=(with comment if needed)WORKER_SIGNING_SECRET=(with comment if needed)
-
turbo.json – Add these four variables to the
tasks.build.envarray.
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(), |
There was a problem hiding this comment.
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.
| 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 }); | ||
| } |
There was a problem hiding this comment.
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:
- Return type mismatch: Queue handlers shouldn't return
NextResponseobjects. They should return simple objects or void. TheNextResponse.json()return adds unnecessary serialization overhead for queue processing. - Placeholder implementation: The TODO and
setTimeoutindicate this is incomplete. - Inconsistent sleep usage: Use the imported
sleeputility instead of rawsetTimeoutfor 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.
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
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:
- Extract the core business logic from
handleBatchRequestinto a separate function (e.g.,categorizeSendersBatch) - Call that function directly from this handler
- 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.
| 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 }); | ||
| } |
There was a problem hiding this comment.
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:
- Placeholder implementations: TODO comments with
setTimeoutdelays - Inconsistent sleep usage: Should use the imported
sleeputility - 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?
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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>
| ? Math.max(0, body.options.notBefore * 1000 - Date.now()) | ||
| : undefined; | ||
|
|
||
| const jobId = await enqueue( |
There was a problem hiding this comment.
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>
| WORKER_SIGNING_SECRET: z.string().optional(), | ||
|
|
||
| // Tuning | ||
| DEFAULT_CONCURRENCY: z.number().default(3), |
There was a problem hiding this comment.
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(["fatal", "error", "warn", "info", "debug", "trace", "silent"])
</file context>
| DEFAULT_CONCURRENCY: z.number().default(3), | |
| DEFAULT_CONCURRENCY: z.coerce.number().default(3), |
| NODE_ENV: z | ||
| .enum(["development", "production", "test"]) | ||
| .default("development"), | ||
| PORT: z.number().default(5070), |
There was a problem hiding this comment.
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(["development", "production", "test"])
+ .default("development"),
+ PORT: z.number().default(5070),
+
+ // Redis/BullMQ
</file context>
| PORT: z.number().default(5070), | |
| PORT: z.coerce.number().default(5070), |
| delayInMinutes, | ||
| deduplicationId, | ||
| }); | ||
| const job = await enqueueJob("scheduled-actions", payload, { |
There was a problem hiding this comment.
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("scheduled-actions", 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, { |
There was a problem hiding this comment.
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 & { parallelism?: number },
+) {
+ const queue = getOrCreateQueue(queueName, options?.parallelism);
+ const job = await queue.add(queueName, data, {
+ delay: options?.delay,
+ attempts: options?.attempts ?? 5,
</file context>
| UPSTASH_REDIS_TOKEN: "${UPSTASH_REDIS_TOKEN}" | ||
|
|
||
| UPSTASH_REDIS_TOKEN: ${UPSTASH_REDIS_TOKEN} | ||
| WORKER_BASE_URL: "http://queue-worker:5070" |
There was a problem hiding this comment.
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: "${UPSTASH_REDIS_TOKEN}"
-
+ UPSTASH_REDIS_TOKEN: ${UPSTASH_REDIS_TOKEN}
+ WORKER_BASE_URL: "http://queue-worker:5070"
+ CRON_SECRET: ${CRON_SECRET}
+ restart: unless-stopped
</file context>
| WORKER_BASE_URL: "http://queue-worker:5070" | |
| WORKER_BASE_URL: "http://worker:5070" |
| depends_on: | ||
| - db | ||
| - redis | ||
| - queue-worker |
There was a problem hiding this comment.
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>
| - queue-worker | |
| - worker |
|
|
||
| return response instanceof NextResponse | ||
| ? response | ||
| : NextResponse.json(await response.json(), { |
There was a problem hiding this comment.
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>
| "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", |
There was a problem hiding this comment.
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 @@
"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",
"lint": "biome check .",
"test": "cross-env RUN_AI_TESTS=false vitest",
</file context>
There was a problem hiding this 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
cancelScheduledActionsis called, the database status is updated toCANCELLED, 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
UpdatecancelScheduledActionsto 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
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, andremoveOnFailin both the single and bulk handlers, so callers cannot tune retries or cleanup. Please pass the full option set through toenqueue/bulkEnqueuejust likequeue.tsexpects.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: GuardQSTASH_TOKENinstead 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-jobnotBeforein bulk enqueues.Bulk paths still ignore each job’s own
notBefore, so scheduled messages fire immediately unless the top-level option is set. Please pickjob.opts?.notBefore ?? options.notBeforeeverywhere 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
setTimeoutinstead of the importedsleeputility, and returnsNextResponse.jsonwhich 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
Requestobject 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 fromhandleBatchRequestand call it directly.
129-154: Complete placeholder implementations.Both
handleScheduledActionJobandhandleAiCleanJobcontain TODO comments, usesetTimeoutinstead of the importedsleeputility, and returnNextResponse.jsonwhich 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
markQStashActionAsExecutingreferences QStash specifically, but it's now used for all queue systems (QStash and Redis/BullMQ). Consider renaming tomarkScheduledActionAsExecutingfor 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_SECRETfor authenticating with the worker service. However,env.tsdefines a separateWORKER_SIGNING_SECRETfor 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
sleepand return plain objects instead ofNextResponse.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
📒 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.tsapps/web/app/api/resend/digest/all/route.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/queue/publish.tsapps/web/app/api/resend/summary/all/route.tsapps/web/app/api/clean/route.tsapps/web/utils/queue/providers/qstash-manager.tsapps/web/app/api/queue/[queueName]/route.tsapps/web/utils/queue/providers/bullmq-manager.tsapps/web/app/api/clean/gmail/route.tsapps/web/utils/queue/queues.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useelements 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.tsapps/web/app/api/resend/digest/all/route.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/queue/publish.tsapps/web/app/api/resend/summary/all/route.tsapps/web/app/api/clean/route.tsapps/web/utils/queue/providers/qstash-manager.tsapps/web/app/api/queue/[queueName]/route.tsapps/web/utils/queue/providers/bullmq-manager.tsapps/web/app/api/clean/gmail/route.tsapps/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.tsapps/web/app/api/resend/digest/all/route.tsapps/web/utils/upstash/categorize-senders.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/queue/publish.tsapps/web/app/api/resend/summary/all/route.tsapps/web/app/api/clean/route.tsapps/web/utils/queue/providers/qstash-manager.tsapps/web/app/api/queue/[queueName]/route.tsapps/web/utils/queue/providers/bullmq-manager.tsapps/web/app/api/clean/gmail/route.tsapps/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).
UsewithEmailAccountmiddleware for API routes that operate on a specific email account (i.e., use or requireemailAccountId).
UsewithAuthmiddleware for API routes that operate at the user level (i.e., use or require onlyuserId).
UsewithErrormiddleware (with proper validation) for public endpoints, custom authentication, or cron endpoints.
Cron endpoints MUST usewithErrormiddleware and validate the cron secret usinghasCronSecret(request)orhasPostCronSecret(request).
Cron endpoints MUST capture unauthorized attempts withcaptureExceptionand 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.
AllfindUniqueandfindFirstPrisma calls in API routes MUST include ownership filters (e.g., userId or emailAccountId).
AllfindManyPrisma 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.tsapps/web/app/api/resend/summary/all/route.tsapps/web/app/api/clean/route.tsapps/web/app/api/queue/[queueName]/route.tsapps/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.tsapps/web/app/api/resend/summary/all/route.tsapps/web/app/api/clean/route.tsapps/web/app/api/queue/[queueName]/route.tsapps/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.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/queue/publish.tsapps/web/utils/queue/providers/qstash-manager.tsapps/web/utils/queue/providers/bullmq-manager.tsapps/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.tsapps/web/utils/scheduled-actions/scheduler.tsapps/web/utils/queue/publish.tsapps/web/utils/queue/providers/qstash-manager.tsapps/web/utils/queue/providers/bullmq-manager.tsapps/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.tsapps/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
EnqueueOptionsinterface defined inapps/web/utils/queue/types.tsincludes:
notBefore?: number(seconds since epoch, QStash style, takes precedence over delay at worker)deduplicationId?: stringtargetPath?: stringheaders?: Record<string, string>The code in the review comment correctly uses all three supported parameters:
- Line 268-269 calculates
notBeforeas a Unix timestamp in seconds, matching the expected QStash format- Line 302-306 passes
notBefore,deduplicationId, andtargetPathtoenqueueJob(), which aligns with the interface definitionThe 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
withErrormiddleware 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/bulkendpoint 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
PENDINGwhen 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 whathandleEmailDigestAllJobexpects. The handler implementation inqueues.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
hasCronSecretandhasPostCronSecretwithcaptureExceptionfor unauthorized attempts.Based on coding guidelines
| // Check for cron secret (for scheduled jobs) | ||
| const authHeader = request.headers.get("authorization"); | ||
| if (authHeader === `Bearer ${env.CRON_SECRET}`) { | ||
| return true; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Missing Zod validation and incomplete job processing.
This function has multiple issues that must be addressed:
-
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."
-
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. -
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:
- Define Zod schemas for each queue's job data type
- Validate the request body against the appropriate schema based on
queueName - 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.
| 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, | ||
| }); | ||
| }; |
There was a problem hiding this comment.
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:
-
Redundant validation: Lines 81 and 55 both call
validateInternalRequestfor Redis paths, checking authentication twice. -
Duplicated response conversion: Lines 99-104 and 119-124 contain identical response conversion logic.
-
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.
| 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 }, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 summaryLength 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.tsLength 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 prismaLength 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.
| 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; | ||
| } | ||
| }; |
There was a problem hiding this comment.
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.
| 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.
| if (env.QUEUE_SYSTEM === "upstash") { | ||
| return publishToQstash<T>(url, body, flowControl); | ||
| } | ||
| return enqueueJob(redisQueueName, body, { | ||
| targetPath: url, | ||
| headers, | ||
| }); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| interface ScheduledActionPayload { | ||
| scheduledActionId: string; | ||
| [key: string]: unknown; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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'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 "Bearer undefined", 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 "redis", 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(), { |
There was a problem hiding this comment.
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>
| : NextResponse.json(await response.json(), { | |
| : NextResponse.from(response); |
| key: `gmail-action-${emailAccountId}`, | ||
| ratePerSecond: maxRatePerSecond, | ||
| publishFlowControlled({ | ||
| url: `${base}/api/clean/gmail`, |
There was a problem hiding this comment.
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>
|
|
||
| // Constant-time compare when lengths match | ||
| const ok = | ||
| signature.length === expected.length && |
There was a problem hiding this comment.
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 &&
+ timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
+
</file context>
| signature.length === expected.length && | |
| Buffer.byteLength(signature) === Buffer.byteLength(expected) && |
| headers?: Record<string, string>; | ||
| }) { | ||
| if (env.QUEUE_SYSTEM === "upstash") { | ||
| return publishToQstash<T>(url, body, flowControl); |
There was a problem hiding this comment.
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<string, string>;
+}) {
+ if (env.QUEUE_SYSTEM === "upstash") {
+ return publishToQstash<T>(url, body, flowControl);
+ }
+ return enqueueJob(redisQueueName, body, {
</file context>
| # 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 |
There was a problem hiding this comment.
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>
| const jobs = items.map((item) => ({ | ||
| name: queueName, | ||
| data: item.data, | ||
| opts: { |
There was a problem hiding this comment.
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) => ({
+ name: queueName,
+ data: item.data,
+ opts: {
+ delay: item.options?.delay ?? options?.delay,
+ attempts: item.options?.attempts ?? options?.attempts ?? 5,
</file context>
| options?: JobsOptions & { parallelism?: number }, | ||
| ) { | ||
| const queue = getOrCreateQueue(queueName, options?.parallelism); | ||
| const job = await queue.add(queueName, data, { |
There was a problem hiding this comment.
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 & { parallelism?: number },
+) {
+ const queue = getOrCreateQueue(queueName, options?.parallelism);
+ const job = await queue.add(queueName, data, {
+ delay: options?.delay,
+ attempts: options?.attempts ?? 5,
</file context>
| "!sw.js", | ||
| "!.vscode" | ||
| "!.vscode", | ||
| "!apps/web/worker.js" |
There was a problem hiding this comment.
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 @@
"!sw.js",
- "!.vscode"
+ "!.vscode",
+ "!apps/web/worker.js"
]
},
</file context>
| "!apps/web/worker.js" | |
| "!apps/web/utils/queue/worker.ts" |
| queueSystem: env.QUEUE_SYSTEM, | ||
| queueName: _queueName, | ||
| }); | ||
| return null; |
There was a problem hiding this comment.
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 "redis", 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>
| return Number.isNaN(index) ? null : index; | ||
| } | ||
|
|
||
| export const QUEUE_HANDLERS = {} as const; |
There was a problem hiding this comment.
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>
Summary by CodeRabbit
New Features
Infrastructure
Configuration
Documentation