Claude/prisma notification migration h qw rf#1170
Claude/prisma notification migration h qw rf#1170don4of4 wants to merge 23 commits intoelie222:mainfrom
Conversation
- Add Azure AI Foundry as LLM provider option (azure-foundry) - Enhanced bulk email processing with: - Parallel processing with configurable concurrency (default: 5) - Streaming pagination (100 emails per page) - Date filtering (after/before parameters) - Skip already processed emails option - Process oldest first option for chronological processing - Updated dependencies in pnpm-lock.yaml 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
- Uses standard Docker Buildx (no Depot dependency) - Builds for linux/amd64 and linux/arm64 - Publishes to ghcr.io/don4of4/inbox-zero - Configures NEXT_PUBLIC_BASE_URL for self-hosted deployment - Enables BYPASS_PREMIUM_CHECKS for self-hosted use 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
- Remove repository check that blocked fork builds - Replace Depot with standard Docker Buildx - Use github.repository for dynamic image naming - Remove Docker Hub publishing (GHCR only) - Add build caching for faster subsequent builds - Configure for inbox.donaldscott.com deployment 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
TrueNAS target is x86_64 - no need for multi-arch. Cuts build time roughly in half. 🤖 Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
- Remove marketing sections: Pricing, Testimonials, Awards, FinalCTA, BrandScroller - Simplify footer from 50+ links to 5 essentials (Docs, GitHub, Discord, Terms, Privacy) - Change CTA buttons to single "Sign In" button - Update copyright to "Self-hosted instance" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Previously, if GOOGLE_PUBSUB_VERIFICATION_TOKEN was not set, the webhook would accept all requests without verification. This change rejects requests when the token is not configured, preventing unauthenticated webhook abuse.
The getProgressMessage function was refactored to use completedThreadIds from state instead of a remainingCount parameter. Updated tests to: - Remove the unused second parameter - Set up completedThreadIds in test state
Changed `prompt: "consent"` to `prompt: "select_account"` in three places: - Main Google OAuth config (auth.ts) - Google account linking (linking/auth-url/route.ts) - Google calendar auth (calendar/auth-url/route.ts) Previously, the consent screen was shown on every login even when no new permissions were requested. Now it only shows when permissions change.
…ture This plan details how to implement automatic cleanup of "aged out" emails (notifications, newsletters, promotions) with configurable expiration periods. Key features: - Per-category expiration settings (Notifications: 7d, Newsletters: 30d, etc.) - Daily cron job via QStash for processing - Archive + "Inbox Zero/Expired" label for audit trail - Settings UI for user configuration - Optional AI-based custom expiration rules - Integration with existing cleanup infrastructure
Major changes to the implementation plan: - LLM analyzes email content to extract relevant dates (package delivery, events, sale end dates) - Per-email expiresAt stored on EmailMessage model - Two processing paths: 1. Real-time: Hook after runRules() in webhook processing 2. Batch backfill: Cron for emails without expiration set - Cleanup cron finds emails past expiresAt and archives them - Stores LLM reasoning for transparency - Falls back to category defaults when no date found - Hooks into existing cron pattern (/api/cron/expiration/*) Example transformations: - "Package arrives Nov 5" → expiresAt: Nov 12 - "Meeting tomorrow" → expiresAt: day after meeting - "Sale ends tonight" → expiresAt: tomorrow
Key changes: - Phase 4: Use existing /api/watch/all cron for cleanup instead of new endpoints - Remove Phase 7: No new cron schedules needed - Update file structure: Clarify new files vs files to modify - Cleanup runs in existing cron, backfill uses existing bulk button
Phase 1: Schema changes - Add expiresAt, expiredAt, expirationReason to EmailMessage - Add EmailExpirationSettings model for user configuration - Add ExpiredEmailLog model for audit trail - Add composite index for expiration queries Phase 2: Core utilities - Add utils/expiration/categories.ts for category detection - Add utils/expiration/process-expired.ts for cleanup logic - Add "expired" label to inboxZeroLabels Phase 3: Hook into existing cron - Modify /api/watch/all to call cleanupExpiredEmails() - Graceful error handling - cleanup failures don't break watch Note: LLM-based expiration analysis (Phase 2 of plan) not yet implemented. This commit provides the infrastructure for cleanup; expiration dates need to be set by a future analyzeExpiration() implementation.
Adds the "smart" part of email expiration - LLM analyzes email content
to extract relevant dates and set context-aware expiration.
New file:
- utils/expiration/analyze-expiration.ts
- analyzeExpirationWithLLM(): Calls LLM to extract dates from content
- analyzeAndSetExpiration(): Main entry point that checks settings,
detects category, runs LLM analysis, and stores result
Hooks into existing flows:
- process-history-item.ts: Runs expiration analysis in background via
after() for new incoming emails
- bulk-process-emails.ts: Runs expiration analysis after runRules()
for each message during bulk processing
Examples of LLM analysis:
- "Your package arrives November 5th" -> expires Nov 12 (7 days after)
- "Event tomorrow at 3pm" -> expires day after event
- "Weekly newsletter" -> expires in 30 days (default)
- Add API endpoint: /api/user/expiration-settings (GET/POST) - Add ExpirationSection.tsx component with: - Master toggle to enable/disable auto-expiration - Option to apply "Expired" label when archiving - Per-category toggles and default day settings - AI analyzes email content for specific dates as fallback - Integrate into Settings page under "Email Account" tab
Create migration to add: - EmailExpirationSettings table for per-account expiration preferences - ExpiredEmailLog table for audit logging of archived emails - expiresAt, expiredAt, expirationReason columns to EmailMessage - Composite index for efficient expiration queries
The content-type header is not part of ParsedMessageHeaders type. Check attachments for calendar mimeType or .ics/.ical extensions instead.
- Replace postRequest with native fetch API - Use registerProps instead of spreading register result - Add required name prop to Input component
- Fix Prisma import path in bulk-process-emails.ts - Update OutlookProvider.getInboxMessages to match EmailProvider interface - Fix getInboxMessages call in fetch.ts to use options object
- Add createTestLogger helper for e2e tests - Add logger to createEmailProvider calls in e2e tests - Fix getInboxMessages calls to use options object - Add exclude property to group items in tests
The API middleware requires this header for authentication. Without it, requests return 403 Forbidden.
|
@claude 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. |
|
|
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughThis PR introduces an email expiration feature with LLM-powered analysis, refactors email provider APIs to support date-range filtering, simplifies landing pages by removing sections, adds Azure Foundry LLM support, increases queue concurrency, updates Docker CI for GHCR, and instruments logging throughout AI processing pipelines. Changes
Sequence Diagram(s)sequenceDiagram
participant W as Webhook<br/>(history item)
participant MP as Message<br/>Processor
participant EA as Expiration<br/>Analysis
participant LLM as LLM
participant DB as Database
participant L as Logger
W->>MP: process history item
Note over MP: Check hasAiAccess & inbox
alt inbox message
MP->>EA: analyzeAndSetExpiration()<br/>(background)
Note over EA: Fetch expiration settings
alt settings enabled
EA->>EA: Detect category
alt category detected & enabled
EA->>LLM: Analyze expiration<br/>(with system/message prompts)
LLM-->>EA: { shouldExpire, expiresAt, reason }
EA->>DB: Upsert EmailMessage<br/>(expiresAt, expirationReason)
EA->>L: Log success
else no expirable category
EA->>L: Log skip (no category)
end
else settings disabled
EA->>L: Log skip (disabled)
end
end
MP-->>W: return (non-blocking)
sequenceDiagram
participant UI as BulkRunRulesServerSide<br/>(UI)
participant Action as bulkProcessRulesAction<br/>(Server Action)
participant Bulk as bulkProcessInboxEmails
participant Gmail as Gmail API
participant Rules as Rule Matching
participant Exp as Expiration Analysis
participant DB as Database
UI->>Action: Call with { daysBack, maxEmails, concurrency, ... }
Action->>Bulk: Invoke with computed date filters
loop Pages (paginated fetch)
Bulk->>Gmail: queryBatchMessagesPages(after, before)
Gmail-->>Bulk: Messages (paginated)
Note over Bulk: Deduplicate by thread<br/>(cross-page)
alt skipAlreadyProcessed
Bulk->>DB: Filter out ExecutedRule matches
end
Note over Bulk: Sort by date (processOldestFirst)
loop Batches (concurrency)
Bulk->>Rules: Run rules per message
Rules-->>Bulk: Action result
alt hasAiAccess
Bulk->>Exp: analyzeAndSetExpiration()
Exp-->>DB: Persist expiresAt
end
end
end
Action-->>UI: { success: true }
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
✨ Finishing touches
📜 Recent review detailsConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (60)
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 |
|
Disregard -- unintentional PR. |
There was a problem hiding this comment.
12 issues found across 61 files
Prompt for AI agents (all issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="docs/plans/email-expiration-cleanup.md">
<violation number="1" location="docs/plans/email-expiration-cleanup.md:1037">
P1: The `updateConfig` function is called but never defined in the component. This code example would cause a runtime error if used as-is.</violation>
</file>
<file name="apps/web/app/api/google/calendar/auth-url/route.ts">
<violation number="1" location="apps/web/app/api/google/calendar/auth-url/route.ts:25">
P2: Changing `prompt` from `"consent"` to `"select_account"` may prevent refresh tokens from being returned on re-authorization. Since `access_type: "offline"` is set, the application needs refresh tokens for long-term access. Google only issues refresh tokens on the initial consent grant; subsequent auth flows with `"select_account"` won't return one. If this is intentional (e.g., for better UX when users already have valid tokens), consider adding a comment explaining this. Otherwise, consider using `"consent select_account"` to get both behaviors.</violation>
</file>
<file name="apps/web/utils/ai/choose-rule/ai-choose-rule.ts">
<violation number="1" location="apps/web/utils/ai/choose-rule/ai-choose-rule.ts:10">
P2: Per codebase logging conventions, helper functions should receive the logger as a parameter from the route/action rather than using `createScopedLogger`. This loses request context (requestId, userId, emailAccountId) that would be available from middleware. Consider adding a `logger: Logger` parameter to these functions and passing it from the caller.</violation>
</file>
<file name="apps/web/next.config.ts">
<violation number="1" location="apps/web/next.config.ts:18">
P1: Disabling TypeScript build errors (`ignoreBuildErrors: true`) allows type errors to reach production, which can cause runtime failures. Instead of bypassing type checking, the underlying AI SDK version mismatch should be resolved by aligning dependency versions or adding proper type declarations. This is a workaround that masks technical debt.</violation>
</file>
<file name="apps/web/utils/auth.ts">
<violation number="1" location="apps/web/utils/auth.ts:105">
P1: Removing `consent` from the prompt will prevent Google from returning refresh tokens for returning users. When `accessType: "offline"` is set, Google only returns a refresh token on the first authorization or when the user re-consents. Without `consent` in the prompt, if a user's existing refresh token becomes invalid and they need to re-authenticate, they won't receive a new refresh token, breaking the app's ability to maintain long-term access.</violation>
</file>
<file name="apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRulesServerSide.tsx">
<violation number="1" location="apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRulesServerSide.tsx:49">
P2: The `data` from `useThreads` is fetched but never used beyond a truthy check. This causes an unnecessary API call to fetch inbox threads. Consider removing this hook if only premium/loading state is needed, or use the data if it's intended to show thread counts.</violation>
</file>
<file name="apps/web/app/(landing)/home/Footer.tsx">
<violation number="1" location="apps/web/app/(landing)/home/Footer.tsx:67">
P2: Social links are missing `target={item.target}`. The social items define `target: "_blank"` but it's not applied to the Link component, unlike the main navigation items above. This will cause social links to open in the same tab instead of a new tab.</violation>
</file>
<file name="apps/web/app/api/google/linking/auth-url/route.ts">
<violation number="1" location="apps/web/app/api/google/linking/auth-url/route.ts:21">
P2: Changing `prompt` from `"consent"` to `"select_account"` may prevent refresh tokens from being returned. With `access_type: "offline"`, Google only provides a refresh token on the first authorization or when consent is explicitly forced. Returning users selecting an already-authorized account won't receive a new refresh token, which could cause authentication failures when the access token expires.
Consider using `prompt: "consent select_account"` to allow account selection while still forcing consent, or ensure your token handling logic accounts for cases where no refresh token is returned.</violation>
</file>
<file name="apps/web/utils/actions/ai-rule.ts">
<violation number="1" location="apps/web/utils/actions/ai-rule.ts:651">
P2: The action always returns `{ success: true }` even if `bulkProcessInboxEmails` fails, since that function catches and swallows errors internally. Consider wrapping the call in try-catch to return appropriate error status, or returning processing statistics to give callers meaningful feedback.</violation>
</file>
<file name="apps/web/utils/expiration/analyze-expiration.ts">
<violation number="1" location="apps/web/utils/expiration/analyze-expiration.ts:116">
P2: LLM-returned `expiresAt` string is converted to Date without validation. If the LLM returns a malformed date string, `new Date()` creates an Invalid Date that propagates to the database. Consider validating the parsed date before returning it.</violation>
</file>
<file name="apps/web/app/(app)/[emailAccountId]/settings/ExpirationSection.tsx">
<violation number="1" location="apps/web/app/(app)/[emailAccountId]/settings/ExpirationSection.tsx:71">
P1: The SWR fetch doesn't include the `EMAIL_ACCOUNT_HEADER` that is used in the POST request. If the API requires this header for account-specific data, the GET will return incorrect results. Consider using a custom fetcher or including the header.</violation>
<violation number="2" location="apps/web/app/(app)/[emailAccountId]/settings/ExpirationSection.tsx:226">
P2: Input is missing the `error` prop. Validation errors from the zod schema (min 1, max 365/30) won't be displayed to users.</violation>
</file>
Since this is your first cubic review, here's how it works:
- cubic automatically reviews your code and comments on bugs and improvements
- Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
- Ask questions if you need clarification on any suggestion
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| key={category.id} | ||
| category={category} | ||
| config={configs.find(c => c.category === category.id)} | ||
| onUpdate={(config) => updateConfig(category.id, config)} |
There was a problem hiding this comment.
P1: The updateConfig function is called but never defined in the component. This code example would cause a runtime error if used as-is.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/plans/email-expiration-cleanup.md, line 1037:
<comment>The `updateConfig` function is called but never defined in the component. This code example would cause a runtime error if used as-is.</comment>
<file context>
@@ -0,0 +1,1237 @@
+ key={category.id}
+ category={category}
+ config={configs.find(c => c.category === category.id)}
+ onUpdate={(config) => updateConfig(category.id, config)}
+ />
+ ))}
</file context>
| scope: CALENDAR_SCOPES, | ||
| state, | ||
| prompt: "consent", | ||
| prompt: "select_account", |
There was a problem hiding this comment.
P2: Changing prompt from "consent" to "select_account" may prevent refresh tokens from being returned on re-authorization. Since access_type: "offline" is set, the application needs refresh tokens for long-term access. Google only issues refresh tokens on the initial consent grant; subsequent auth flows with "select_account" won't return one. If this is intentional (e.g., for better UX when users already have valid tokens), consider adding a comment explaining this. Otherwise, consider using "consent select_account" to get both behaviors.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/google/calendar/auth-url/route.ts, line 25:
<comment>Changing `prompt` from `"consent"` to `"select_account"` may prevent refresh tokens from being returned on re-authorization. Since `access_type: "offline"` is set, the application needs refresh tokens for long-term access. Google only issues refresh tokens on the initial consent grant; subsequent auth flows with `"select_account"` won't return one. If this is intentional (e.g., for better UX when users already have valid tokens), consider adding a comment explaining this. Otherwise, consider using `"consent select_account"` to get both behaviors.</comment>
<file context>
@@ -22,7 +22,7 @@ const getAuthUrl = ({ emailAccountId }: { emailAccountId: string }) => {
scope: CALENDAR_SCOPES,
state,
- prompt: "consent",
+ prompt: "select_account",
});
</file context>
| import { getUserInfoPrompt, getUserRulesPrompt } from "@/utils/ai/helpers"; | ||
| import { createScopedLogger } from "@/utils/logger"; | ||
|
|
||
| const logger = createScopedLogger("ai-choose-rule"); |
There was a problem hiding this comment.
P2: Per codebase logging conventions, helper functions should receive the logger as a parameter from the route/action rather than using createScopedLogger. This loses request context (requestId, userId, emailAccountId) that would be available from middleware. Consider adding a logger: Logger parameter to these functions and passing it from the caller.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/ai/choose-rule/ai-choose-rule.ts, line 10:
<comment>Per codebase logging conventions, helper functions should receive the logger as a parameter from the route/action rather than using `createScopedLogger`. This loses request context (requestId, userId, emailAccountId) that would be available from middleware. Consider adding a `logger: Logger` parameter to these functions and passing it from the caller.</comment>
<file context>
@@ -5,6 +5,9 @@ import { isDefined, type EmailForLLM } from "@/utils/types";
import { getUserInfoPrompt, getUserRulesPrompt } from "@/utils/ai/helpers";
+import { createScopedLogger } from "@/utils/logger";
+
+const logger = createScopedLogger("ai-choose-rule");
type GetAiResponseOptions = {
</file context>
| reactStrictMode: true, | ||
| typescript: { | ||
| // Skip type checking during build (type errors in AI SDK version mismatches) | ||
| ignoreBuildErrors: true, |
There was a problem hiding this comment.
P1: Disabling TypeScript build errors (ignoreBuildErrors: true) allows type errors to reach production, which can cause runtime failures. Instead of bypassing type checking, the underlying AI SDK version mismatch should be resolved by aligning dependency versions or adding proper type declarations. This is a workaround that masks technical debt.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/next.config.ts, line 18:
<comment>Disabling TypeScript build errors (`ignoreBuildErrors: true`) allows type errors to reach production, which can cause runtime failures. Instead of bypassing type checking, the underlying AI SDK version mismatch should be resolved by aligning dependency versions or adding proper type declarations. This is a workaround that masks technical debt.</comment>
<file context>
@@ -13,6 +13,10 @@ const withMDX = nextMdx({
reactStrictMode: true,
+ typescript: {
+ // Skip type checking during build (type errors in AI SDK version mismatches)
+ ignoreBuildErrors: true,
+ },
output: process.env.DOCKER_BUILD === "true" ? "standalone" : undefined,
</file context>
| scope: [...GMAIL_SCOPES], | ||
| accessType: "offline", | ||
| prompt: "select_account consent", | ||
| prompt: "select_account", |
There was a problem hiding this comment.
P1: Removing consent from the prompt will prevent Google from returning refresh tokens for returning users. When accessType: "offline" is set, Google only returns a refresh token on the first authorization or when the user re-consents. Without consent in the prompt, if a user's existing refresh token becomes invalid and they need to re-authenticate, they won't receive a new refresh token, breaking the app's ability to maintain long-term access.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/auth.ts, line 105:
<comment>Removing `consent` from the prompt will prevent Google from returning refresh tokens for returning users. When `accessType: "offline"` is set, Google only returns a refresh token on the first authorization or when the user re-consents. Without `consent` in the prompt, if a user's existing refresh token becomes invalid and they need to re-authenticate, they won't receive a new refresh token, breaking the app's ability to maintain long-term access.</comment>
<file context>
@@ -102,7 +102,7 @@ export const betterAuthConfig = betterAuth({
scope: [...GMAIL_SCOPES],
accessType: "offline",
- prompt: "select_account consent",
+ prompt: "select_account",
disableIdTokenSignIn: true,
},
</file context>
| access_type: "offline", | ||
| scope: [...new Set([...SCOPES, "openid", "email"])].join(" "), | ||
| prompt: "consent", | ||
| prompt: "select_account", |
There was a problem hiding this comment.
P2: Changing prompt from "consent" to "select_account" may prevent refresh tokens from being returned. With access_type: "offline", Google only provides a refresh token on the first authorization or when consent is explicitly forced. Returning users selecting an already-authorized account won't receive a new refresh token, which could cause authentication failures when the access token expires.
Consider using prompt: "consent select_account" to allow account selection while still forcing consent, or ensure your token handling logic accounts for cases where no refresh token is returned.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/api/google/linking/auth-url/route.ts, line 21:
<comment>Changing `prompt` from `"consent"` to `"select_account"` may prevent refresh tokens from being returned. With `access_type: "offline"`, Google only provides a refresh token on the first authorization or when consent is explicitly forced. Returning users selecting an already-authorized account won't receive a new refresh token, which could cause authentication failures when the access token expires.
Consider using `prompt: "consent select_account"` to allow account selection while still forcing consent, or ensure your token handling logic accounts for cases where no refresh token is returned.</comment>
<file context>
@@ -18,7 +18,7 @@ const getAuthUrl = ({ userId }: { userId: string }) => {
access_type: "offline",
scope: [...new Set([...SCOPES, "openid", "email"])].join(" "),
- prompt: "consent",
+ prompt: "select_account",
state,
});
</file context>
| processOldestFirst, | ||
| }); | ||
|
|
||
| return { success: true }; |
There was a problem hiding this comment.
P2: The action always returns { success: true } even if bulkProcessInboxEmails fails, since that function catches and swallows errors internally. Consider wrapping the call in try-catch to return appropriate error status, or returning processing statistics to give callers meaningful feedback.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/actions/ai-rule.ts, line 651:
<comment>The action always returns `{ success: true }` even if `bulkProcessInboxEmails` fails, since that function catches and swallows errors internally. Consider wrapping the call in try-catch to return appropriate error status, or returning processing statistics to give callers meaningful feedback.</comment>
<file context>
@@ -579,3 +581,73 @@ export const generateRulesPromptAction = actionClient
+ processOldestFirst,
+ });
+
+ return { success: true };
+ },
+ );
</file context>
|
|
||
| return { | ||
| shouldExpire, | ||
| expiresAt: expiresAt ? new Date(expiresAt) : null, |
There was a problem hiding this comment.
P2: LLM-returned expiresAt string is converted to Date without validation. If the LLM returns a malformed date string, new Date() creates an Invalid Date that propagates to the database. Consider validating the parsed date before returning it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/utils/expiration/analyze-expiration.ts, line 116:
<comment>LLM-returned `expiresAt` string is converted to Date without validation. If the LLM returns a malformed date string, `new Date()` creates an Invalid Date that propagates to the database. Consider validating the parsed date before returning it.</comment>
<file context>
@@ -0,0 +1,229 @@
+
+ return {
+ shouldExpire,
+ expiresAt: expiresAt ? new Date(expiresAt) : null,
+ reason,
+ };
</file context>
| </div> | ||
|
|
||
| <div className="flex items-center gap-2"> | ||
| <Input |
There was a problem hiding this comment.
P2: Input is missing the error prop. Validation errors from the zod schema (min 1, max 365/30) won't be displayed to users.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/(app)/[emailAccountId]/settings/ExpirationSection.tsx, line 226:
<comment>Input is missing the `error` prop. Validation errors from the zod schema (min 1, max 365/30) won't be displayed to users.</comment>
<file context>
@@ -0,0 +1,257 @@
+ </div>
+
+ <div className="flex items-center gap-2">
+ <Input
+ type="number"
+ name={category.field}
</file context>
|
|
||
| export function ExpirationSection() { | ||
| const { emailAccountId } = useAccount(); | ||
| const { data, isLoading, mutate } = useSWR<ExpirationSettingsResponse>( |
There was a problem hiding this comment.
P1: The SWR fetch doesn't include the EMAIL_ACCOUNT_HEADER that is used in the POST request. If the API requires this header for account-specific data, the GET will return incorrect results. Consider using a custom fetcher or including the header.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/web/app/(app)/[emailAccountId]/settings/ExpirationSection.tsx, line 71:
<comment>The SWR fetch doesn't include the `EMAIL_ACCOUNT_HEADER` that is used in the POST request. If the API requires this header for account-specific data, the GET will return incorrect results. Consider using a custom fetcher or including the header.</comment>
<file context>
@@ -0,0 +1,257 @@
+
+export function ExpirationSection() {
+ const { emailAccountId } = useAccount();
+ const { data, isLoading, mutate } = useSWR<ExpirationSettingsResponse>(
+ "/api/user/expiration-settings",
+ );
</file context>
Add LLM-driven email expiration with per-account settings, Prisma models and cleanup jobs, integrate server-side bulk rule processing with concurrency and date filters, and increase AI queue concurrency to 30
Introduce expiration analysis and cleanup paths, including new Prisma models and API/UI for settings; refactor bulk processing to paginated, concurrent server-side execution with expiration updates; add Azure AI Foundry provider support; adjust OAuth prompts and queues; and update
EmailProvider.getInboxMessagesto accept{ maxResults, after, before }.📍Where to Start
Start with
analyzeAndSetExpirationin apps/web/utils/expiration/analyze-expiration.ts, then reviewcleanupExpiredEmailsin apps/web/utils/expiration/process-expired.ts andbulkProcessInboxEmailsin apps/web/utils/ai/choose-rule/bulk-process-emails.ts.📊 Macroscope summarized acb083f. 41 files reviewed, 41 issues evaluated, 21 issues filtered, 0 comments posted
🗂️ Filtered Issues
.github/workflows/build_and_publish_docker.yml — 0 comments posted, 3 evaluated, 1 filtered
NEXT_PUBLIC_BYPASS_PREMIUM_CHECKS=trueinbuild-argspermanently disables premium checks in all built images. This may cause unintended access to premium features or break licensing enforcement at runtime. [ Low confidence ]apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRules.tsx — 0 comments posted, 4 evaluated, 3 filtered
onRunfunction is now called with a new fourth callback parameter(threadId) => dispatch({ type: "THREAD_COMPLETED", threadId })to track completed threads. If theonRunfunction implementation was not updated to invoke this callback when threads complete,state.completedThreadIdswill remain empty. This would cause the progress message to always show 0 completed emails, andhandleStopwould always reportcompletedCount: 0since it relies onstate.completedThreadIds.size. [ Low confidence ]handleStopfunction capturesstate.completedThreadIds.sizesynchronously at the moment of invocation. If multipleTHREAD_COMPLETEDactions are dispatched in quick succession (e.g., from async queue callbacks) but haven't been processed by React's state batching when the user clicks Stop, the captured count may be slightly stale and not reflect threads that completed in the same event loop tick. [ Low confidence ]aiQueuemay trigger Gmail API rate limits. The original comment explicitly stated the delay was to "avoid gmail api rate limits". Removing this protection while increasing parallelism could cause fetch failures on subsequent batches. [ Low confidence ]apps/web/app/(app)/[emailAccountId]/assistant/BulkRunRulesServerSide.tsx — 0 comments posted, 3 evaluated, 1 filtered
Number(e.target.value) || 10will reset concurrency to 10 if the user tries to enter 0, since0is falsy. While the min attribute is 1, a user could still type 0 and be surprised when it becomes 10. Consider usingNumber(e.target.value)with explicit validation orNumber.isNaN()check instead. [ Low confidence ]apps/web/app/(app)/[emailAccountId]/settings/ExpirationSection.tsx — 0 comments posted, 4 evaluated, 1 filtered
toggleCategorycallback hasenabledCategoriesin its dependency array but captures a potentially stale value. IftoggleCategoryis called multiple times in quick succession before React re-renders, thecurrentarray on line 106 will use a staleenabledCategoriesvalue, causing toggle operations to be lost. [ Low confidence ]apps/web/app/(app)/[emailAccountId]/settings/page.tsx — 0 comments posted, 2 evaluated, 2 filtered
formSchemaconstant is referenced inzodResolver(formSchema)withinExpirationSection.tsxbut is never defined or imported in the visible code. This will cause aReferenceErrorat runtime when the component renders. [ Low confidence ]ExpirationSettingsResponsetype declaressettingscan benull, but whenprisma.emailExpirationSettings.findUniquereturnsnull, the response{ settings }will havesettings: null. However, inExpirationSection.tsx, the form usesdata?.settings?.enabled ?? falsewhich handles null correctly, BUT theCATEGORIESconstant referenced in the form is never defined in the provided code. The form iterates overCATEGORIES.map((category) => ...)butCATEGORIESis not imported or defined, causing a runtimeReferenceError. [ Low confidence ]apps/web/app/api/google/calendar/auth-url/route.ts — 0 comments posted, 1 evaluated, 1 filtered
promptfrom"consent"to"select_account"will cause Google to NOT issue a new refresh token for users who have previously authorized this application. Withaccess_type: "offline", Google only issues a refresh token on initial consent or when consent is forced. If the user previously authorized the app, the callback will not receive a refresh token, causing the calendar integration to fail when the short-lived access token expires and the application attempts to refresh it. [ Low confidence ]apps/web/app/api/google/webhook/route.ts — 0 comments posted, 1 evaluated, 1 filtered
GOOGLE_PUBSUB_VERIFICATION_TOKENis not configured, but existing deployments that worked without this token configured will now fail. This is a breaking change that could cause all Google webhooks to fail after deployment if the environment variable wasn't previously required. [ Low confidence ]apps/web/app/api/user/expiration-settings/route.ts — 0 comments posted, 1 evaluated, 1 filtered
POSThandler in expiration-settings usesupdateSettingsSchema.parse(body)which will throw a Zod error if validation fails. This error is not caught, so invalid requests will result in an unhandled exception rather than a proper 400 response with validation details. [ Low confidence ]apps/web/utils/actions/ai-rule.ts — 0 comments posted, 1 evaluated, 1 filtered
bulkProcessRulesActionalways returns{ success: true }regardless of whetherbulkProcessInboxEmailssucceeded or failed. Looking atbulkProcessInboxEmails, it wraps all processing in a try-catch that logs errors but doesn't re-throw them, causing the function to returnundefinedon failure. The action then proceeds to return{ success: true }, misleading callers into thinking the bulk processing completed successfully when it may have failed entirely. [ Exceeded comment limit ]apps/web/utils/actions/ai-rule.validation.ts — 0 comments posted, 3 evaluated, 1 filtered
daysBackandstartDate/endDateare provided, the intended behavior is ambiguous. Downstream code must handle this priority, but the schema doesn't enforce mutual exclusivity which could lead to user confusion or inconsistent behavior depending on implementation. [ Low confidence ]apps/web/utils/ai/choose-rule/ai-choose-rule.ts — 0 comments posted, 4 evaluated, 1 filtered
modelOptions.modelobject instead ofmodelOptions.modelNamestring in the multi-rule start log. [ Low confidence ]apps/web/utils/ai/choose-rule/bulk-process-emails.ts — 0 comments posted, 2 evaluated, 2 filtered
run-rules.tsreference aMODULEconstant (e.g.,module: MODULE) but the constant is not shown as defined or imported in the provided code. IfMODULEis not defined elsewhere in the file, this will cause aReferenceErrorat runtime when the logging code executes. [ Low confidence ]emailProvider.getMessagesWithPagination()but the diff shows the previous implementation usedemailProvider.getInboxMessages(maxEmails). IfOutlookProvider(orGmailProvider) does not implement the newgetMessagesWithPaginationmethod with the expected signature returning{ messages, nextPageToken }, this will throw a runtime error when attempting to process emails for accounts using that provider. [ Low confidence ]apps/web/utils/auth.ts — 0 comments posted, 1 evaluated, 1 filtered
promptfrom"select_account consent"to"select_account"removes the explicit consent prompt during Google OAuth. This may cause issues where users who previously granted consent don't get prompted to re-consent when scopes change, potentially resulting in missing permissions if the app's required scopes have been updated since the user last authenticated. [ Low confidence ]apps/web/utils/expiration/analyze-expiration.ts — 0 comments posted, 5 evaluated, 3 filtered
settings.enabledCategories.includes(category)assumesenabledCategoriesis always a defined array. If this field isnull,undefined, or not initialized in the database record, this will throwTypeError: Cannot read properties of undefined (reading 'includes'). [ Low confidence ]getDefaultExpirationDays(category, settings)is called with the Prismasettingsobject, butgetDefaultExpirationDaysexpects an object with specific property names likenotificationDays,newsletterDays, etc. If the Prisma model uses different column names (e.g.,notification_daysor camelCase variations), the function will ignore user settings and always fall back to category defaults. [ Low confidence ]/@([^>]+)/inextractDomaincaptures everything after@until a>character, but doesn't account for email addresses with multiple@symbols in display names (e.g.,"user@display" <real@example.com>), which could extract the wrong domain. Also, addresses without@will return an empty string, which may cause downstream issues if domain is required. [ Low confidence ]apps/web/utils/gmail/message.ts — 0 comments posted, 1 evaluated, 1 filtered
maxResultsisundefined,queryBatchMessagesPageswill loop indefinitely until all pages are exhausted (lines 317-320). For users with large inboxes, this could fetch millions of messages, causing memory exhaustion, rate limit errors, or extremely long execution times. The function lacks any safeguard like a maximum page count or total message limit. [ Low confidence ]Summary by CodeRabbit
New Features
Improvements
Tests
✏️ Tip: You can customize this high-level summary in your review settings.