-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: Add Claude Code CLI as LLM provider option #1098
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?
feat: Add Claude Code CLI as LLM provider option #1098
Conversation
Introduces an HTTP wrapper service that bridges Claude Code CLI for container-to-container communication. This enables using Claude Code as an LLM provider option in the Inbox Zero app. New package: apps/claude-code-wrapper/ - Express server with health, generate, and stream endpoints - CLI subprocess execution with JSON output parsing - Session tracking support for multi-turn conversations - Server-Sent Events for streaming responses - Docker integration with health checks Related: elie222#8
- Fix CLI flag: use --resume instead of --continue for session IDs - Remove unsupported --max-tokens CLI flag (not in Claude CLI) - Add timing-safe API key comparison to prevent timing attacks - Fix Dockerfile: use npm (package-lock.json) consistently - Fix health check race condition with safe resolve pattern - Add safe JSON stringify to handle circular references in logger Addresses feedback from code review on Phase 1.
- Add env_file to load ANTHROPIC_API_KEY from .env - Document two auth approaches in docker-compose comments: 1. API key via ANTHROPIC_API_KEY env var 2. Max subscription via bind-mounted ~/.claude directory
Adds buildClaudeEnv() helper that removes ANTHROPIC_API_KEY when CLAUDE_CODE_OAUTH_TOKEN is present, ensuring Max subscription auth takes precedence over pay-per-token API auth.
Clearer naming to distinguish wrapper service auth from Claude auth.
Add Claude Code as a new LLM provider option in the web app's LLM abstraction layer. This enables routing AI inference through the claude-code-wrapper service when configured. Changes: - Add CLAUDE_CODE to Provider enum and providerOptions in config.ts - Add claudecode to llmProviderEnum validation in env.ts - Add CLAUDE_CODE_BASE_URL and CLAUDE_CODE_TIMEOUT env vars - Add ClaudeCodeConfig interface and extend SelectModel type - Add Provider.CLAUDE_CODE case in selectModel() switch - Add CLAUDE_CODE to getProviderApiKey() mapping - Register new env vars in turbo.json Note: This commit adds the provider constants and model selection logic. The HTTP client adapter and core function integration will follow in subsequent commits.
Add CLAUDE_CODE_BASE_URL and CLAUDE_CODE_TIMEOUT to the LLM configuration section with documentation about the wrapper service and Max subscription support.
Create HTTP client adapter and LLM wrapper functions for Claude Code provider, enabling the web app to route AI inference through the claude-code-wrapper service. Architecture designed to minimize upstream merge conflicts: - claude-code.ts: HTTP client for wrapper service API - claude-code-llm.ts: LLM wrapper functions (separate from index.ts) - index.ts: Minimal 10-line branch to delegate to new module New files: - apps/web/utils/llms/claude-code.ts - HTTP client with claudeCodeGenerateText() and claudeCodeGenerateObject() - apps/web/utils/llms/claude-code-llm.ts - LLM wrappers that return Vercel AI SDK compatible result shapes Changes to upstream files (minimal): - index.ts: Add imports and delegation branches for Claude Code - package.json: Add zod-to-json-schema dependency This completes Phase 2 - Claude Code is now a fully functional LLM provider option. Set DEFAULT_LLM_PROVIDER=claudecode to use.
Explains why toolCalls, steps, reasoning, and other fields are empty/undefined in Claude Code provider results. These stubs ensure callers can safely access properties without null checks.
Add dedicated test file for Claude Code provider integration with tests for configuration, error handling, and timeout settings. Also add required env var mocks to existing model tests.
- cli.ts: Add 5-minute default timeout to prevent hung processes - cli.ts: Log JSON parse errors instead of silently ignoring - cli.ts: Throw error when no result found instead of fabricating zeros - stream.ts: Log parse failures when falling back to raw text - claude-code.ts: Handle non-JSON error responses gracefully
- Wrapper service now requires API_KEY env var to start (security by default) - HTTP client sends Authorization header with auth key - Web app validates CLAUDE_CODE_WRAPPER_AUTH_KEY when provider is selected - Updated env.ts, turbo.json, .env.example, docker-compose.yml - Added test for missing auth key validation
Add comprehensive test coverage for the Claude Code HTTP client: - ClaudeCodeError class construction and inheritance - claudeCodeGenerateText success and error handling - claudeCodeGenerateObject with Zod schema validation - HTTP error codes (4xx, 5xx, 401 unauthorized) - Timeout/abort behavior - Network failures - Empty response edge cases 24 tests covering all critical paths identified by test reviewer.
Enable conversation continuity across related AI operations by persisting Claude CLI session IDs in Redis. Sessions are scoped by workflow group: - report: All email-report-* tasks share context - rules: Rule creation/management tasks share context - clean: Inbox cleaning operations share context - default: All other standalone tasks Key features: - 30-minute TTL with refresh on each use - Graceful degradation (session failures don't break AI operations) - Automatic session handling (no changes to 40+ AI call sites) Closes: Phase 6 of elie222#8
- Add "server-only" import to prevent client-side usage - Extract session retrieval/persistence into helper functions - Add sessionId to save failure log messages for debugging - Add error propagation tests for Redis failures
Verify session IDs flow through the LLM layer correctly: - Session retrieval before HTTP calls - Session persistence after successful calls - Graceful degradation when Redis fails - Workflow group routing based on label
Enable different models for default vs economy tasks: - Add CLAUDE_CODE_MODEL for default tasks (e.g., 'sonnet') - Add CLAUDE_CODE_ECONOMY_MODEL for high-volume tasks (e.g., 'haiku') Changes: - Wrapper service accepts model parameter in requests - CLI builder passes --model flag to Claude CLI - HTTP client forwards model in request body - selectEconomyModel routes to economy model for Claude Code - Add 3 tests for model selection behavior
Change economy model selection to check ECONOMY_LLM_PROVIDER explicitly instead of inferring from DEFAULT_LLM_PROVIDER. This allows flexibility to use different providers for default vs economy tasks.
Add tests for when DEFAULT_LLM_PROVIDER is claudecode but ECONOMY_LLM_PROVIDER is a different provider (e.g., anthropic). This validates the flexibility to use Claude Code for complex tasks while using API-based providers for high-volume economy tasks.
Make CLAUDE_CODE_MODEL and CLAUDE_CODE_ECONOMY_MODEL optional with sensible defaults: - Default tasks: "sonnet" (Claude Sonnet for complex reasoning) - Economy tasks: "haiku" (Claude Haiku for high-volume/bulk) This eliminates unnecessary configuration while preserving flexibility to override via env vars if needed.
Document the OAuth token env var as a proper entry instead of just a comment. This token is used by the wrapper for Max subscription authentication.
Remove the hardcoded ANTHROPIC_API_KEY: "" that was overriding the env_file value. Now the wrapper correctly supports: - CLAUDE_CODE_OAUTH_TOKEN (Max subscription) - ANTHROPIC_API_KEY (API auth) - OAuth takes precedence if both are set
Clarify that mounting host's ~/.claude directory exposes credentials and should only be used for local development/testing. Production deployments should use env vars (CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY) instead.
Most users don't need the Claude Code wrapper service. Keep it in its own 'claude-code' profile so it must be explicitly requested.
Automatically selects faster models (haiku) for simple tasks like classification and extraction, while keeping sonnet for complex tasks. This optimization happens at the provider level without modifying upstream code. - Add LABEL_MODEL_OVERRIDES config mapping labels to models - Add getModelForLabel() to select appropriate model per task - Add model logging to wrapper for debugging/verification
Implement text streaming for Claude Code provider with minimal upstream impact: - Add claudeCodeStreamText() HTTP client for SSE streaming from wrapper - Add createClaudeCodeStreamText() adapter with AI SDK compatible interface - Create chat-completion-stream.ts wrapper for provider routing - Change session key from emailAccountId to userEmail for simplicity Upstream changes limited to import path in 2 files: - summarise/controller.ts - compose-autocomplete/route.ts Streaming supports toTextStreamResponse() for summarise and autocomplete. Tool-based features (chat assistant) not yet supported.
- Add comprehensive tests for claudeCodeStreamText() HTTP client function - SSE event parsing (text, session, result, error, done) - Stream consumption and text accumulation - Usage statistics tracking - Error handling (HTTP errors, network failures, null body) - Timeout signal verification - Add streaming adapter tests for createClaudeCodeStreamText() - Session retrieval and persistence - AsyncIterable textStream interface - toTextStreamResponse() HTTP response generation - Message content extraction - onFinish callback invocation - Label-based model override selection - Fix existing tests to use userEmail instead of emailAccountId - Update config expectations to account for getModelForLabel() model injection
Critical fixes: - Fix double stream consumption bug using tee() to split stream - Propagate errors for critical SSE events (session, result, error, done) - Wrap session persistence in try-catch to prevent text delivery failures High priority fixes: - Add usageReceived flag to properly reject usage promise in flush() - Change session failure logging from warn to error for Sentry tracking Medium priority fixes: - Create TextStreamResult interface for explicit AI SDK type compatibility - Capture parse error details in safeJsonParse for better debugging - Separate onFinish callback errors from usage tracking errors - Add logging to toTextStreamResponse error handler - Warn when SSE error events lack error codes
The ModelMessage type from AI SDK defines system message content as string-only. TypeScript was inferring 'never' for the array branch after type narrowing. Simplified system message handling and added proper type assertions for user message content parts.
The Claude CLI requires --verbose when using --output-format stream-json with --print. Without it, the CLI fails immediately on argument validation, causing "CLI exited with code null" errors. Also adds: - Model parameter passthrough to streaming endpoint - Test endpoint for manual streaming verification
- Replace direct string comparison with crypto.timingSafeEqual to prevent timing attacks on token validation - Add Zod schemas for getLearnedPatterns, updateAbout, and addToKnowledgeBase inputs to replace unsafe 'as' casts - Add try-catch error handling around partialUpdateRule, updateRuleActions, and saveLearnedPatterns calls to prevent silent failures
Add debug-level logging when final buffer fails to parse as JSON. This is expected during user cancellations but provides visibility for debugging.
- Test for empty Bearer token returning 401 - Test for getLearnedPatterns with rule not found - Test input validation for getLearnedPatterns, updateAbout, and addToKnowledgeBase with missing required fields
- Move updateRuleConditions, updateRuleActions, and updateLearnedPatterns input schemas from inline definitions to validation.ts - Fix createRule error response format (was using separate error + message fields) - Add literal union for ErrorResponse.code in wrapper types (7 error codes) - Remove unused imports from route.ts
- Add test for EXECUTION_ERROR when tool throws unexpected error - Add test for database error handling in addToKnowledgeBase
- Extract ErrorCode type alias for shared use between ErrorResponse and ClaudeCliError - Update ClaudeCliError.code to use ErrorCode type for type safety - Replace logger.debug with logger.info (debug method doesn't exist) - Update test to use valid error code (INTERNAL_ERROR instead of TEST_CODE)
- Document core vs app-specific code separation - Explain skills system and extensibility - Include complete API reference - Add development and Docker deployment instructions - Prepare for potential open source release
Bun 1.2+ uses lockfile v1 format. Update to latest stable (1.3.3) for security patches and performance improvements. This fixes the "Unknown lockfile version" warning during builds.
- Rename API_KEY to CLAUDE_CODE_WRAPPER_API_KEY for clarity - Extract buildClaudeCodeConfig() and isClaudeCodeAvailable() helpers - Add override logic: when DEFAULT_LLM_PROVIDER=claudecode, use Claude Code for all generateText/generateObject calls regardless of model type - Add comprehensive test coverage (13 tests) to protect against upstream reverts of override logic - Fix Docker container issues: remove read_only, fix volume permissions - Add error logging for CLI failures
|
@matthew-petty 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 new Claude Code wrapper service (Bun + Docker) and integrates Claude Code as a first-class LLM provider across the web app: HTTP wrapper (generate/stream/health), CLI orchestration, SSE streaming, Redis session management, an LLM tools proxy endpoint, tests, docs, and deployment config. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Client
participant Web as Web App
participant Redis as Redis
participant Wrapper as Claude Code Wrapper
participant CLI as Claude CLI
Client->>Web: POST /api/llm-tools/invoke (Bearer token, tool request)
Web->>Web: Validate token & request body
Web->>Redis: (optional) get session by label
Redis-->>Web: sessionId or null
Web->>Wrapper: POST /generate-text or /stream (prompt, sessionId, model)
Wrapper->>CLI: spawn claude with args + prompt
CLI-->>Wrapper: stdout (JSON lines / stream)
Wrapper->>Web: { text/object, usage, sessionId }
Web->>Redis: save sessionId (if present)
Web-->>Client: 200 OK with result
sequenceDiagram
participant Client as HTTP Client
participant Wrapper as Claude Code Wrapper
participant CLI as Claude CLI
Client->>Wrapper: POST /stream (prompt, optional sessionId)
Wrapper->>Wrapper: validate & spawn claude (--output-format stream-json)
Wrapper->>Client: 200 OK + SSE headers
loop stream
CLI-->>Wrapper: stdout (JSON line)
Wrapper->>Wrapper: parse event (session/text/result)
Wrapper->>Client: SSE event (session/text/result)
end
CLI-->>Wrapper: close (exit 0)
Wrapper->>Client: SSE event done
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches✅ Passed checks (3 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 |
Add Claude Code CLI provider and route app LLM calls through new authenticated wrapper service and web SDK integrationsIntroduce a Bun/Express wrapper exposing 📍Where to StartStart with the Express server and auth in apps/claude-code-wrapper/src/index.ts, then review request handling in apps/claude-code-wrapper/src/routes/generate.ts and apps/claude-code-wrapper/src/routes/stream.ts, followed by provider routing in apps/web/utils/llms/index.ts and stream wrapper in apps/web/utils/llms/chat-completion-stream.ts. Macroscope summarized d0a0052. |
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
🧹 Nitpick comments (31)
apps/claude-code-wrapper/src/routes/health.ts (1)
44-46: Consider consuming stdout/stderr to prevent backpressure.The spawned process pipes stdout and stderr but never reads from them. While unlikely to cause issues with
claude --version(which produces minimal output), unconsumed streams can theoretically cause the child process to block on write if the pipe buffer fills.Consider consuming the streams:
const proc = spawn("claude", ["--version"], { - stdio: ["ignore", "pipe", "pipe"], + stdio: ["ignore", "ignore", "ignore"], });Or if you need the output for logging:
const proc = spawn("claude", ["--version"], { stdio: ["ignore", "pipe", "pipe"], }); + +proc.stdout?.on("data", () => {}); // Drain stdout +proc.stderr?.on("data", () => {}); // Drain stderrapps/claude-code-wrapper/src/logger.ts (1)
17-31: Consider adding a space before stringified args.The args are concatenated directly after the message without a clear separator, which could make logs harder to read when both message and args are present.
Apply this diff to improve readability:
info: (message: string, ...args: unknown[]) => { process.stderr.write( - `[INFO] [${new Date().toISOString()}] ${message} ${args.length ? safeStringify(args) : ""}\n`, + `[INFO] [${new Date().toISOString()}] ${message}${args.length ? " " + safeStringify(args) : ""}\n`, ); }, error: (message: string, ...args: unknown[]) => { process.stderr.write( - `[ERROR] [${new Date().toISOString()}] ${message} ${args.length ? safeStringify(args) : ""}\n`, + `[ERROR] [${new Date().toISOString()}] ${message}${args.length ? " " + safeStringify(args) : ""}\n`, ); }, warn: (message: string, ...args: unknown[]) => { process.stderr.write( - `[WARN] [${new Date().toISOString()}] ${message} ${args.length ? safeStringify(args) : ""}\n`, + `[WARN] [${new Date().toISOString()}] ${message}${args.length ? " " + safeStringify(args) : ""}\n`, ); },apps/claude-code-wrapper/.claude/skills/inbox-zero-tools/SKILL.md (1)
32-34: Add language specifiers to fenced code blocks.The static analysis tool flagged these code blocks as missing language specifiers. For consistency and proper rendering, add
textorhttpas the language.-``` +```text POST ${INBOX_ZERO_API_URL}/api/llm-tools/invoke```diff -``` +```text Authorization: Bearer <LLM_TOOL_PROXY_TOKEN> Content-Type: application/jsonAlso applies to: 48-51 </blockquote></details> <details> <summary>apps/claude-code-wrapper/src/index.ts (1)</summary><blockquote> `55-59`: **Request logging occurs after authentication.** The request logging middleware is placed after the auth middleware, which means failed auth attempts are not logged. Consider moving the logging middleware before auth if you want visibility into unauthorized access attempts for security monitoring. </blockquote></details> <details> <summary>apps/claude-code-wrapper/__tests__/cli.test.ts (2)</summary><blockquote> `36-45`: **Environment restoration should use `afterEach` instead of `afterAll`.** The environment is reset in `beforeEach`, but restored only in `afterAll`. If a test fails mid-execution, subsequent tests in other describe blocks might run with a polluted environment. Consider using `afterEach` for the restoration to ensure proper isolation. ```diff - afterAll(() => { + afterEach(() => { process.env = originalEnv; });Alternatively, keep
afterAllbut also add cleanup inafterEachfor defensive isolation.
173-195: Ensure fake timers are cleaned up on test failure.If an assertion fails before
vi.useRealTimers()is called, subsequent tests may behave unexpectedly. Consider using atry/finallypattern orafterEachto ensure timers are always restored.it("times out and kills process when execution exceeds timeout", async () => { vi.useFakeTimers(); - const mockProc = createMockChildProcess(); - mockSpawn.mockReturnValue(mockProc as never); - - const resultPromise = executeClaudeCli({ - prompt: "Test", - timeoutMs: 1000, - }); - - // Advance time past the timeout - vi.advanceTimersByTime(1001); - - await expect(resultPromise).rejects.toThrow(ClaudeCliError); - await expect(resultPromise).rejects.toMatchObject({ - code: "TIMEOUT_ERROR", - message: expect.stringContaining("timed out"), - }); - - expect(mockProc.kill).toHaveBeenCalledWith("SIGTERM"); - - vi.useRealTimers(); + try { + const mockProc = createMockChildProcess(); + mockSpawn.mockReturnValue(mockProc as never); + + const resultPromise = executeClaudeCli({ + prompt: "Test", + timeoutMs: 1000, + }); + + // Advance time past the timeout + vi.advanceTimersByTime(1001); + + await expect(resultPromise).rejects.toThrow(ClaudeCliError); + await expect(resultPromise).rejects.toMatchObject({ + code: "TIMEOUT_ERROR", + message: expect.stringContaining("timed out"), + }); + + expect(mockProc.kill).toHaveBeenCalledWith("SIGTERM"); + } finally { + vi.useRealTimers(); + } });apps/claude-code-wrapper/__tests__/e2e/real-cli.test.ts (2)
22-39: Consider making the prompt more deterministic.The prompt asks Claude to respond with only "test", but the assertion only checks that
result.text.length > 0. For more reliable E2E tests, you could assert that the response contains "test" (case-insensitive) to validate the model followed instructions.expect(result.text).toBeDefined(); expect(result.text.length).toBeGreaterThan(0); + // Verify model attempted to follow the instruction + expect(result.text.toLowerCase()).toContain("test");This makes the test more meaningful while still being resilient to minor variations.
101-115: Timeout E2E test may be flaky.A 100ms timeout might occasionally succeed if the CLI responds very quickly (e.g., from cache or error). Consider using an even shorter timeout (e.g., 1ms) or mocking time for more reliable behavior, though that would defeat the E2E purpose.
Alternatively, document that this test may occasionally pass if the CLI fails fast for other reasons.
apps/claude-code-wrapper/__tests__/routes/generate.test.ts (1)
63-97: Consider usingvi.useFakeTimers()instead of realsetTimeoutfor more deterministic tests.The current approach using
setTimeout(..., 10)to simulate async CLI responses works but can be flaky under CPU load. Using fake timers would make tests more deterministic and faster.Example approach:
it("returns text response on successful CLI execution", async () => { + vi.useFakeTimers(); const mockProc = createMockChildProcess(); mockSpawn.mockReturnValue(mockProc as never); const app = createTestApp(); const responsePromise = request(app) .post("/generate-text") .send({ prompt: "Hello" }); - // Simulate successful CLI response - setTimeout(() => { - simulateCliSuccess( - mockProc, - createCliResultJson({ - result: "Hi there!", - total_tokens_in: 5, - total_tokens_out: 10, - session_id: "session-123", - }), - ); - }, 10); + // Simulate successful CLI response + await vi.advanceTimersByTimeAsync(1); + simulateCliSuccess( + mockProc, + createCliResultJson({ + result: "Hi there!", + total_tokens_in: 5, + total_tokens_out: 10, + session_id: "session-123", + }), + ); const res = await responsePromise; + vi.useRealTimers(); // ...assertions });apps/claude-code-wrapper/__tests__/integration/app.test.ts (1)
233-244: Consider verifying the error response structure more thoroughly.The malformed JSON test checks for status 500 and generic error message, but it might be worth verifying the error code as well for consistency with other error responses.
// Express JSON parser error is caught by error handler middleware expect(res.status).toBe(500); expect(res.body.error).toBe("Internal server error"); + // Optionally verify error code for consistency + expect(res.body.code).toBeDefined();apps/web/utils/llms/index.ts (1)
176-199: Consider extracting the Claude Code routing logic to reduce duplication.The
shouldUseClaudeCodecondition andclaudeCodeConfigresolution logic is duplicated betweencreateGenerateText(lines 69-78) andcreateGenerateObject(lines 181-190). Consider extracting a helper function.// Helper to determine Claude Code routing function resolveClaudeCodeConfig( modelOptions: ReturnType<typeof getModel> ): { shouldUse: true; config: ClaudeCodeConfig } | { shouldUse: false } { const shouldUseClaudeCode = (modelOptions.provider === Provider.CLAUDE_CODE && modelOptions.claudeCodeConfig) || (env.DEFAULT_LLM_PROVIDER === Provider.CLAUDE_CODE && isClaudeCodeAvailable()); if (shouldUseClaudeCode) { const config = modelOptions.claudeCodeConfig || buildClaudeCodeConfig(); return { shouldUse: true, config }; } return { shouldUse: false }; }apps/web/app/api/llm-tools/invoke/route.test.ts (1)
383-424: Add mock cleanup inside the loop to prevent test pollution.The
forloop iterates through valid tools and callsmockResolvedValueOnce, but if a previous iteration's assertions fail, subsequent iterations may use stale mock state. Consider adding explicit mock resets.for (const toolName of validTools) { + vi.clearAllMocks(); // Ensure clean state for each tool test vi.mocked(prisma.emailAccount.findUnique).mockResolvedValueOnce({ id: "account-123", email: "[email protected]", account: { provider: "google" }, } as any);apps/web/utils/llms/chat-completion-stream.ts (2)
29-39: Consider makingonStepFinishtype explicit about Claude Code support limitations.The
onStepFinishcallback is included in the interface but won't be called when using Claude Code path. Consider adding a JSDoc note on the property indicating this limitation.interface ChatCompletionStreamOptions { userAi: UserAIFields; modelType?: ModelType; messages: ModelMessage[]; tools?: Record<string, Tool>; maxSteps?: number; userEmail: string; usageLabel: string; onFinish?: StreamTextOnFinishCallback<Record<string, Tool>>; + /** Note: Not called when using Claude Code provider */ onStepFinish?: StreamTextOnStepFinishCallback<Record<string, Tool>>; }
91-92: Misleadingidfield usage in emailAccount object.The
idis set touserEmailwith a comment saying "id not used", but this creates a confusing object structure. If the id isn't used by Claude Code, consider creating a proper type or using a placeholder that makes this explicit.- return createClaudeCodeStreamText({ - emailAccount: { email: userEmail, id: userEmail }, // id not used, email used for sessions + return createClaudeCodeStreamText({ + emailAccount: { email: userEmail, id: "" }, // id not needed for Claude Code sessionsapps/web/utils/llms/claude-code-override.test.ts (1)
114-124: Usevi.clearAllMocks()instead ofvi.resetAllMocks()per coding guidelines.The coding guidelines specify using
vi.clearAllMocks()inbeforeEachto clean up mocks between tests.resetAllMocksalso resets mock implementations, which may cause unexpected behavior if mocks rely on specific implementations set at module level.beforeEach(() => { - vi.resetAllMocks(); + vi.clearAllMocks(); // Reset to default test configuration vi.mocked(env).DEFAULT_LLM_PROVIDER = "claudecode";apps/web/app/api/llm-tools/invoke/route.ts (2)
208-214: Inconsistent error handling pattern across tool cases.The
getLearnedPatternscase returns an object witherrorproperty on validation failure (line 211), while other cases likecreateRule(line 217) pass unvalidated input directly to the execute function. This creates inconsistent behavior - some tools return error objects, others throw.Consider validating all inputs consistently in the switch cases before calling execute functions:
case "createRule": { + const parseResult = createRuleSchema(ctx.provider).safeParse(input); + if (!parseResult.success) { + return { error: `Invalid input: ${parseResult.error.message}` }; + } - return executeCreateRule(input, ctx); + return executeCreateRule(parseResult.data, ctx); + }
411-416: Logging full error object may expose sensitive information.The
logger.errorcall with{ error }could serialize stack traces or internal details. Use error message only.} catch (error) { const message = error instanceof Error ? error.message : String(error); - logger.error("Failed to create rule", { error }); + logger.error("Failed to create rule", { error: message }); return { error: `Failed to create rule: ${message}` }; }apps/web/utils/llms/claude-code-llm.test.ts (2)
30-36: MockClaudeCodeErrormissingrawTextproperty from actual implementation.The mock class doesn't include the
rawTextproperty that exists in the actualClaudeCodeErrorclass (per relevant_code_snippets showing the actual class hasrawText?: string). This could cause tests to pass when code relies onrawTextbeing present.ClaudeCodeError: class ClaudeCodeError extends Error { code: string; - constructor(message: string, code: string) { + rawText?: string; + constructor(message: string, code: string, rawText?: string) { super(message); this.code = code; + this.rawText = rawText; } },
420-429: UsingsetTimeoutfor async waiting can cause flaky tests.The test uses
setTimeout(r, 10)to wait for async session save. This could be flaky under load. Consider usingvi.waitForor flushing promises more reliably.- // Wait for async session save - await new Promise((r) => setTimeout(r, 10)); + // Flush pending promises + await vi.waitFor(() => { + expect(mockSaveSession).toHaveBeenCalled(); + });apps/claude-code-wrapper/src/routes/stream.ts (1)
221-231: Empty catch block should use_to indicate intentional discard.The empty catch block at Line 221 silently discards the error variable. Use
catch (_)orcatch (_error)to signal intent.- } catch { + } catch (_) {apps/claude-code-wrapper/__tests__/routes/stream.test.ts (1)
66-68: Consider usingvi.advanceTimersByTimeAsyncfor setTimeout in tests.The tests use real
setTimeoutwith 50ms delays. While this works, using fake timers withvi.advanceTimersByTimeAsyncwould make tests faster and more deterministic. However, given the note about supertest limitations with fake timers (Lines 440-450), the current approach is reasonable.apps/web/utils/llms/claude-code-http.test.ts (1)
637-641: Consider extracting stream consumption to a helper.The pattern of consuming the stream reader appears multiple times (Lines 637-641, 670-673, 700-704, 735-739, 839-842, 942-947). A helper function would reduce duplication.
async function consumeStream(reader: ReadableStreamDefaultReader<string>): Promise<string[]> { const chunks: string[] = []; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); } return chunks; }apps/web/utils/redis/claude-code-session.ts (2)
69-74: Consider sanitizing userEmail in Redis key.While
userEmailis likely validated upstream, directly interpolating it into the Redis key could be problematic if it contains special characters like:. Consider URL-encoding or validating the format.function getSessionKey( userEmail: string, workflowGroup: WorkflowGroup, ): string { - return `claude-session:${userEmail}:${workflowGroup}`; + // Encode email to handle special characters safely in Redis key + const safeEmail = encodeURIComponent(userEmail); + return `claude-session:${safeEmail}:${workflowGroup}`; }
36-55: Workflow label mapping uses string literals prone to typos.The hardcoded label strings (e.g.,
"email-report-email-behavior") are scattered between this mapping and presumably other parts of the codebase. Consider extracting these as constants or using a more type-safe approach to prevent silent failures from typos.apps/web/utils/llms/model.ts (2)
247-259: Type assertionnull as unknown as LanguageModelV2is a code smell.While this works, setting
modeltonullwith a type assertion breaks the type contract ofSelectModel. Consider making themodelfield optional or using a discriminated union type based on the provider.export type SelectModel = { provider: string; modelName: string; - model: LanguageModelV2; + model: LanguageModelV2 | null; providerOptions?: Record<string, any>; - backupModel: LanguageModelV2 | null; + backupModel: LanguageModelV2 | null; /** Configuration for Claude Code provider (only set when provider is CLAUDE_CODE) */ claudeCodeConfig?: ClaudeCodeConfig; };Alternatively, use a discriminated union:
export type SelectModel = | { provider: typeof Provider.CLAUDE_CODE; modelName: string; model: null; claudeCodeConfig: ClaudeCodeConfig; backupModel: null; } | { provider: Exclude<string, typeof Provider.CLAUDE_CODE>; modelName: string; model: LanguageModelV2; backupModel: LanguageModelV2 | null; claudeCodeConfig?: never; };
442-446: Placeholder string "claude-code-wrapper" should be a constant.The placeholder value is hardcoded. Consider extracting it as a named constant for clarity and to avoid magic strings.
+const CLAUDE_CODE_PLACEHOLDER_KEY = "claude-code-wrapper"; + function getProviderApiKey(provider: string) { const providerApiKeys: Record<string, string | undefined> = { // ... other providers [Provider.CLAUDE_CODE]: env.CLAUDE_CODE_BASE_URL - ? "claude-code-wrapper" + ? CLAUDE_CODE_PLACEHOLDER_KEY : undefined, };apps/claude-code-wrapper/src/cli.ts (2)
85-105: Minor: SIGKILL fallback timeout is not cleaned up.If the process exits gracefully after SIGTERM but before the 1-second SIGKILL fallback fires, that inner timeout remains scheduled. While functionally benign (killing an already-dead process is a no-op), consider storing the timeout reference and clearing it in the
closehandler for cleaner resource management.if (timeoutMs > 0) { timeoutId = setTimeout(() => { if (!isSettled) { isSettled = true; logger.error("Claude CLI execution timed out", { timeoutMs, prompt: options.prompt.slice(0, 100), }); claude.kill("SIGTERM"); // Give it a moment to clean up, then force kill - setTimeout(() => claude.kill("SIGKILL"), 1000); + const killTimeoutId = setTimeout(() => claude.kill("SIGKILL"), 1000); + claude.once("close", () => clearTimeout(killTimeoutId)); reject( new ClaudeCliError(
227-235: Consider reducing log level for expected non-JSON lines.The comment states "non-JSON lines are expected in some outputs", but logging at
warnlevel will generate noise. Consider usinglogger.traceinstead, which aligns with the comment's expectation.} catch (error) { parseErrorCount++; // Log at debug level since non-JSON lines are expected in some outputs - logger.warn("Failed to parse CLI output line as JSON", { + logger.trace("Failed to parse CLI output line as JSON", { line: line.slice(0, 200), error: error instanceof Error ? error.message : "Unknown error", }); }apps/web/utils/llms/claude-code.ts (1)
413-432: Defensive flush handling with potential double resolve.The
flushhandler resolvestextPromiseat line 431 even if it was already resolved in the "done" event at line 372. While Promise semantics make subsequentresolve()calls no-ops, consider adding atextResolvedflag for consistency withsessionIdReceivedandusageReceivedto make the intent clearer.let accumulatedText = ""; let sessionIdReceived = false; let usageReceived = false; + let textResolved = false; // ... in "done" case: case "done": { // Stream complete, resolve the text promise + textResolved = true; resolveText(accumulatedText); // ... in flush: flush() { // ... - resolveText(accumulatedText); + if (!textResolved) { + resolveText(accumulatedText); + } },apps/web/utils/llms/claude-code-llm.ts (2)
29-32: Refactor type aliases to avoidany.The
anytypes are overly permissive. Based on the usage patterns in the code, these functions accept and return objects with specific shapes ({ system?, prompt, schema? }for input and{ text/object, usage, ... }for output). Consider defining explicit interfaces instead.Apply this pattern:
-// biome-ignore lint/suspicious/noExplicitAny: Complex AI SDK types require flexibility -type ClaudeCodeGenerateTextFn = (...args: any[]) => Promise<any>; -// biome-ignore lint/suspicious/noExplicitAny: Complex AI SDK types require flexibility -type ClaudeCodeGenerateObjectFn = (...args: any[]) => Promise<any>; +interface GenerateTextOptions { + system?: string; + prompt: string | ModelMessage[]; +} + +interface GenerateTextResult { + text: string; + usage: LanguageModelUsage; + finishReason: string; + // ... other fields +} + +type ClaudeCodeGenerateTextFn = (options: GenerateTextOptions) => Promise<GenerateTextResult>; + +interface GenerateObjectOptions<T> extends GenerateTextOptions { + schema: unknown; // or more specific Zod schema type +} + +interface GenerateObjectResult<T> { + object: T; + usage: LanguageModelUsage; + // ... other fields +} + +type ClaudeCodeGenerateObjectFn = <T>(options: GenerateObjectOptions<T>) => Promise<GenerateObjectResult<T>>;
146-254: LGTM with optional refactor opportunity.The implementation correctly handles session management, usage tracking, and error logging. The extensive stub values (lines 220-242) are well-documented and match the Vercel AI SDK interface requirements.
Consider extracting the common result structure into a helper function to reduce duplication with
createClaudeCodeGenerateObject(lines 220-242 vs 342-359):function createGenerateResultBase( modelName: string, sessionId: string, usage: LanguageModelUsage ) { return { finishReason: "stop" as const, usage, request: {}, response: { id: sessionId, timestamp: new Date(), modelId: modelName, headers: {}, body: undefined, }, warnings: [], providerMetadata: undefined, experimental_providerMetadata: undefined, }; }
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.
5 issues found across 48 files
Prompt for AI agents (all 5 issues)
Check if these issues are valid — if so, understand the root cause of each and fix them.
<file name="apps/claude-code-wrapper/__tests__/setup.ts">
<violation number="1" location="apps/claude-code-wrapper/__tests__/setup.ts:19">
P2: Console mocks set at module level will be undone by `vi.restoreAllMocks()` after the first test. Move the console spies inside `beforeEach` so they're re-established before each test.</violation>
</file>
<file name="apps/web/utils/llms/model.ts">
<violation number="1" location="apps/web/utils/llms/model.ts:443">
P2: Inconsistent availability check - `getProviderApiKey` only checks `CLAUDE_CODE_BASE_URL` while `isClaudeCodeAvailable()` and `buildClaudeCodeConfig` require both `CLAUDE_CODE_BASE_URL` and `CLAUDE_CODE_WRAPPER_API_KEY`. Consider checking both for consistency.</violation>
</file>
<file name="apps/claude-code-wrapper/Dockerfile">
<violation number="1" location="apps/claude-code-wrapper/Dockerfile:54">
P2: Pin the `@anthropic-ai/claude-code` package version for reproducible builds. Without version pinning, builds at different times may pull different CLI versions with potentially breaking changes.</violation>
</file>
<file name="apps/web/app/api/llm-tools/invoke/validation.ts">
<violation number="1" location="apps/web/app/api/llm-tools/invoke/validation.ts:123">
P2: Missing `ActionType.MOVE_FOLDER` from the action type enum. The Prisma schema defines `MOVE_FOLDER` as a valid action type, and the `fields` object already includes `folderName` which is used by this action type.</violation>
</file>
<file name="apps/web/utils/llms/claude-code.ts">
<violation number="1" location="apps/web/utils/llms/claude-code.ts:188">
P1: Creating a new `TextDecoder()` per chunk will incorrectly decode multi-byte UTF-8 characters (emoji, non-ASCII) that span chunk boundaries. The decoder should be instantiated once and reused with `{ stream: true }` option.</violation>
</file>
Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR
Replace greedy regex patterns with non-greedy global matches that iterate through candidates. This prevents incorrect extraction when responses contain multiple JSON fragments or trailing text.
- Use generic error message instead of including userEmail in 404 response - Remove userEmail from tool invocation logs (emailAccountId suffices for debugging)
All ruleName fields now require non-empty strings with min(1) validation, matching the existing pattern in getLearnedPatternsInputSchema.
Import and use the proper WorkflowGroup type throughout the session management functions, eliminating the need for unsafe type assertions.
Add isTextContentPart type guard to validate content part objects before accessing properties, replacing unsafe type assertion.
Creating a new TextDecoder per chunk incorrectly decodes multi-byte
UTF-8 characters (emojis, CJK, etc.) that span chunk boundaries.
Fix by instantiating decoder once and using { stream: true } option.
Both BASE_URL and WRAPPER_API_KEY are now required, matching the isClaudeCodeAvailable() check.
The action type enum was missing MOVE_FOLDER, which is a valid Prisma ActionType. The fields object already includes folderName.
Console mocks at module level are undone by vi.restoreAllMocks() in afterEach. Moving them into beforeEach ensures they are re-established before each test.
Replace the unsafe `!` assertion with proper fallback chain matching buildClaudeCodeConfig() logic: config.model || env var || "sonnet"
Test was expecting error message to contain the email address, but we removed that for security (commit 3e6de34).
✅ All Tests PassVerified locally after pulling all changes - all tests pass successfully. |
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 (1)
apps/claude-code-wrapper/src/routes/generate.ts (1)
77-84: Consider sanitizing validation error details.Same as the
/generate-textendpoint, the validation error response exposes the full Zod error structure. Consider returning a more generic error message.
🧹 Nitpick comments (3)
apps/web/utils/llms/claude-code.ts (1)
415-435: Potential double resolution of text promise.The
resolveText(accumulatedText)is called both in the "done" event handler (line 374) and unconditionally inflush()(line 433). While JavaScript promises ignore subsequent resolutions, this indicates unclear intent. Consider tracking whether the "done" event was received to avoid the redundant call.Apply this diff:
let accumulatedText = ""; let sessionIdReceived = false; let usageReceived = false; + let doneReceived = false; // ... in transform switch case "done": case "done": { // Stream complete, resolve the text promise + doneReceived = true; resolveText(accumulatedText); // ... } // ... in flush(): flush() { // Handle promises that were never resolved/rejected during stream if (!sessionIdReceived) { rejectSessionId(/* ... */); } if (!usageReceived) { rejectUsage(/* ... */); } - resolveText(accumulatedText); + if (!doneReceived) { + resolveText(accumulatedText); + } },apps/web/utils/llms/claude-code-llm.ts (1)
590-601: Consider calling onFinish even when usage tracking fails.The
onFinishcallback is only invoked ifusageis defined (line 591). If usage tracking fails, the callback is silently skipped. Consider whetheronFinishshould be called regardless, potentially with partial data, since callers may need to perform cleanup or logging.Consider this alternative that always calls onFinish:
// Call onFinish callback if provided - always attempt even if usage tracking failed - if (onFinish && usage) { + if (onFinish) { try { - await onFinish({ text, usage }); + await onFinish({ text, usage: usage ?? { inputTokens: 0, outputTokens: 0, totalTokens: 0 } }); } catch (onFinishError) {apps/claude-code-wrapper/src/routes/generate.ts (1)
24-31: Consider sanitizing validation error details.The validation error response exposes the full Zod error structure via
JSON.stringify(parseResult.error.issues), which could reveal internal schema details. While this is an internal service with API key auth, consider returning a more generic error message for production use.Consider applying this pattern:
if (!parseResult.success) { res.status(400).json({ - error: "Invalid request body", + error: "Invalid request body. Check required fields: prompt, model, etc.", code: "VALIDATION_ERROR", - rawText: JSON.stringify(parseResult.error.issues), }); return; }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (10)
.histfile(1 hunks)apps/claude-code-wrapper/__tests__/setup.ts(1 hunks)apps/claude-code-wrapper/src/routes/generate.ts(1 hunks)apps/web/app/api/llm-tools/invoke/route.test.ts(1 hunks)apps/web/app/api/llm-tools/invoke/route.ts(1 hunks)apps/web/app/api/llm-tools/invoke/validation.ts(1 hunks)apps/web/utils/llms/claude-code-llm.ts(1 hunks)apps/web/utils/llms/claude-code.ts(1 hunks)apps/web/utils/llms/index.ts(4 hunks)apps/web/utils/llms/model.ts(4 hunks)
✅ Files skipped from review due to trivial changes (1)
- .histfile
🚧 Files skipped from review as they are similar to previous changes (1)
- apps/claude-code-wrapper/tests/setup.ts
🧰 Additional context used
📓 Path-based instructions (22)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/claude-code-wrapper/src/routes/generate.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include atitleelement for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
AssigntabIndexto non-interactive HTML elements witharia-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid AR...
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use@/path aliases for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Follow consistent naming conventions using PascalCase for components
Centralize shared types in dedicated type filesImport specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
Files:
apps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
apps/web/app/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
Follow NextJS app router structure with (app) directory
Files:
apps/web/app/api/llm-tools/invoke/route.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.ts
apps/web/app/api/**/*.ts
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/app/api/**/*.ts: Wrap GET API routes withwithAuthorwithEmailAccountmiddleware for authentication
Export response types from GET API routes usingAwaited<ReturnType<>>pattern for type-safe client usage
Files:
apps/web/app/api/llm-tools/invoke/route.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.ts
apps/web/app/api/**/route.ts
📄 CodeRabbit inference engine (.cursor/rules/fullstack-workflow.mdc)
apps/web/app/api/**/route.ts: Create GET API routes usingwithAuthorwithEmailAccountmiddleware inapps/web/app/api/*/route.ts, export response types asGetExampleResponsetype alias for client-side type safety
Always export response types from GET routes asGet[Feature]Responseusing type inference from the data fetching function for type-safe client consumption
Do NOT use POST API routes for mutations - always use server actions withnext-safe-actioninstead
Files:
apps/web/app/api/llm-tools/invoke/route.ts
**/app/**/route.ts
📄 CodeRabbit inference engine (.cursor/rules/get-api-route.mdc)
**/app/**/route.ts: Always wrap GET API route handlers withwithAuthorwithEmailAccountmiddleware for consistent error handling and authentication in Next.js App Router
Infer and export response type for GET API routes usingAwaited<ReturnType<typeof functionName>>pattern in Next.js
Use Prisma for database queries in GET API routes
Return responses usingNextResponse.json()in GET API routes
Do not use try/catch blocks in GET API route handlers when usingwithAuthorwithEmailAccountmiddleware, as the middleware handles error handling
Files:
apps/web/app/api/llm-tools/invoke/route.ts
**/{server,api,actions,utils}/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/{server,api,actions,utils}/**/*.ts: UsecreateScopedLoggerfrom "@/utils/logger" for logging in backend code
Add thecreateScopedLoggerinstantiation at the top of the file with an appropriate scope name
Use.with()method to attach context variables only within specific functions, not on global loggers
For large functions with reused variables, usecreateScopedLogger().with()to attach context once and reuse the logger without passing variables repeatedly
Files:
apps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
apps/web/app/**/[!.]*/route.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/project-structure.mdc)
Use kebab-case for route directories in Next.js App Router (e.g.,
api/hello-world/route)
Files:
apps/web/app/api/llm-tools/invoke/route.ts
apps/web/app/api/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/security-audit.mdc)
apps/web/app/api/**/*.{ts,tsx}: API routes must usewithAuth,withEmailAccount, orwithErrormiddleware for authentication
All database queries must include user scoping withemailAccountIdoruserIdfiltering in WHERE clauses
Request parameters must be validated before use; avoid direct parameter usage without type checking
Use generic error messages instead of revealing internal details; throwSafeErrorinstead of exposing user IDs, resource IDs, or system information
API routes should only return necessary fields usingselectin database queries to prevent unintended information disclosure
Cron endpoints must usehasCronSecretorhasPostCronSecretto validate cron requests and prevent unauthorized access
Request bodies should use Zod schemas for validation to ensure type safety and prevent injection attacks
Files:
apps/web/app/api/llm-tools/invoke/route.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.ts
**/app/api/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/app/api/**/*.ts: ALL API routes that handle user data MUST use appropriate middleware: usewithEmailAccountfor email-scoped operations, usewithAuthfor user-scoped operations, or usewithErrorwith proper validation for public/custom auth endpoints
UsewithEmailAccountmiddleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation usingemailAccountId
UsewithAuthmiddleware for user-level operations such as user settings, API keys, and referrals that use onlyuserId
UsewithErrormiddleware only for public endpoints, custom authentication logic, or cron endpoints. For cron endpoints, MUST usehasCronSecret()orhasPostCronSecret()validation
Cron endpoints without proper authentication can be triggered by anyone. CRITICAL: All cron endpoints MUST validate cron secret usinghasCronSecret(request)orhasPostCronSecret(request)and capture unauthorized attempts withcaptureException()
Always validate request bodies using Zod schemas to ensure type safety and prevent invalid data from reaching database operations
Maintain consistent error response format across all API routes to avoid information disclosure while providing meaningful error feedback
Files:
apps/web/app/api/llm-tools/invoke/route.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/app/api/llm-tools/invoke/validation.ts
apps/web/{utils/ai,utils/llms,__tests__}/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)
LLM-related code must be organized in specific directories:
apps/web/utils/ai/for main implementations,apps/web/utils/llms/for core utilities and configurations, andapps/web/__tests__/for LLM-specific tests
Files:
apps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
apps/web/utils/llms/{index,model}.ts
📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)
Core LLM functionality must be defined in
utils/llms/index.ts, model definitions and configurations inutils/llms/model.ts, and usage tracking inutils/usage.ts
Files:
apps/web/utils/llms/index.tsapps/web/utils/llms/model.ts
**/*.test.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
**/*.test.{ts,tsx}: Usevitestfor testing the application
Tests should be colocated next to the tested file with.test.tsor.test.tsxextension (e.g.,dir/format.tsanddir/format.test.ts)
Mockserver-onlyusingvi.mock("server-only", () => ({}))
Mock Prisma usingvi.mock("@/utils/prisma")and import the mock from@/utils/__mocks__/prisma
Usevi.clearAllMocks()inbeforeEachto clean up mocks between tests
Each test should be independent
Use descriptive test names
Mock external dependencies in tests
Do not mock the Logger
Avoid testing implementation details
Use test helpersgetEmail,getEmailAccount, andgetRulefrom@/__tests__/helpersfor mocking emails, accounts, and rules
Files:
apps/web/app/api/llm-tools/invoke/route.test.ts
**/*.{test,spec}.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{test,spec}.{js,jsx,ts,tsx}: Don't nest describe() blocks too deeply in test files
Don't use callbacks in asynchronous tests and hooks
Don't have duplicate hooks in describe blocks
Don't use export or module.exports in test files
Don't use focused tests
Make sure the assertion function, like expect, is placed inside an it() function call
Don't use disabled tests
Files:
apps/web/app/api/llm-tools/invoke/route.test.ts
🧠 Learnings (57)
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Create GET API routes using `withAuth` or `withEmailAccount` middleware in `apps/web/app/api/*/route.ts`, export response types as `GetExampleResponse` type alias for client-side type safety
Applied to files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Maintain consistent error response format across all API routes to avoid information disclosure while providing meaningful error feedback
Applied to files:
apps/claude-code-wrapper/src/routes/generate.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to **/*.ts : Prevent Insecure Direct Object References (IDOR) by validating resource ownership in all queries - always include ownership filters (e.g., `emailAccount: { id: emailAccountId }`) when accessing user-specific resources
Applied to files:
apps/claude-code-wrapper/src/routes/generate.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must follow a standard structure: accept options with `inputData` and `emailAccount` parameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and use `createGenerateObject` to execute the LLM call
Applied to files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/app/api/**/route.ts : Do NOT use POST API routes for mutations - always use server actions with `next-safe-action` instead
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : Use Zod schemas for request body validation in API routes
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:38:56.992Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/project-structure.mdc:0-0
Timestamp: 2025-11-25T14:38:56.992Z
Learning: Applies to apps/web/app/**/[!.]*/route.{ts,tsx} : Use kebab-case for route directories in Next.js App Router (e.g., `api/hello-world/route`)
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : All API routes must use `withAuth`, `withEmailAccount`, or `withError` middleware for authentication
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : Request parameters must be validated before use; direct parameter usage without validation is prohibited
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : Cron endpoints must use `hasCronSecret` or `hasPostCronSecret` middleware to validate cron job authenticity
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`
Applied to files:
apps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : ALL API routes that handle user data MUST use appropriate middleware: use `withEmailAccount` for email-scoped operations, use `withAuth` for user-scoped operations, or use `withError` with proper validation for public/custom auth endpoints
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/*.ts : Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : Use generic error messages instead of revealing internal details; throw `SafeError` instead of exposing user IDs, resource IDs, or system information
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-07-08T13:14:07.449Z
Learnt from: elie222
Repo: elie222/inbox-zero PR: 537
File: apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx:30-34
Timestamp: 2025-07-08T13:14:07.449Z
Learning: The clean onboarding page in apps/web/app/(app)/[emailAccountId]/clean/onboarding/page.tsx is intentionally Gmail-specific and should show an error for non-Google email accounts rather than attempting to support multiple providers.
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : Error responses must use `SafeError` to prevent information disclosure; avoid revealing internal details like user IDs or database information in error messages
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : All database queries must include user/account filtering with `emailAccountId` or `userId` in WHERE clauses to prevent IDOR vulnerabilities
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account (reading/writing emails, rules, schedules, etc.) - provides `emailAccountId`, `userId`, and `email` in `request.auth`
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : All database queries must include user scoping with `emailAccountId` or `userId` filtering in WHERE clauses
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Use `withEmailAccount` middleware for operations scoped to a specific email account, including reading/writing emails, rules, schedules, or any operation using `emailAccountId`
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to **/*.ts : Always validate that resources belong to the authenticated user before any operation - use ownership checks in queries (e.g., `emailAccount: { id: emailAccountId }`) and throw `SafeError` if validation fails
Applied to files:
apps/web/app/api/llm-tools/invoke/route.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use descriptive scoped loggers for each LLM feature, log inputs and outputs with appropriate log levels, and include relevant context in log messages
Applied to files:
apps/web/app/api/llm-tools/invoke/route.tsapps/web/utils/llms/index.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use TypeScript types for all LLM function parameters and return values, and define clear interfaces for complex input/output structures
Applied to files:
apps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/{utils/ai,utils/llms,__tests__}/**/*.ts : LLM-related code must be organized in specific directories: `apps/web/utils/ai/` for main implementations, `apps/web/utils/llms/` for core utilities and configurations, and `apps/web/__tests__/` for LLM-specific tests
Applied to files:
apps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/app/api/llm-tools/invoke/route.test.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/llms/{index,model}.ts : Core LLM functionality must be defined in `utils/llms/index.ts`, model definitions and configurations in `utils/llms/model.ts`, and usage tracking in `utils/usage.ts`
Applied to files:
apps/web/utils/llms/index.tsapps/web/utils/llms/model.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`
Applied to files:
apps/web/utils/llms/index.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Applied to files:
apps/web/utils/llms/index.tsapps/web/utils/llms/claude-code.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Keep related AI functions in the same file or directory, extract common patterns into utility functions, and document complex AI logic with clear comments
Applied to files:
apps/web/utils/llms/index.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Use `console.debug()` for outputting generated LLM content in tests, e.g., `console.debug("Generated content:\n", result.content);`
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{ts,tsx} : Don't misuse the non-null assertion operator (!) in TypeScript files
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{ts,tsx} : Don't use non-null assertions with the `!` postfix operator
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Always define a Zod schema for LLM response validation and make schemas as specific as possible to guide the LLM output
Applied to files:
apps/web/utils/llms/index.tsapps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : System prompts must define the LLM's role and task specifications
Applied to files:
apps/web/utils/llms/index.tsapps/web/utils/llms/claude-code-llm.ts
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Applies to **/*.{ts,tsx} : Always define types for A/B test variant flags (e.g., `type PricingVariant = "control" | "variant-a" | "variant-b"`) and provide type safety through type casting
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Use vitest imports (`describe`, `expect`, `test`, `vi`, `beforeEach`) in LLM test files
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Mock 'server-only' module with empty object in LLM test files: `vi.mock("server-only", () => ({}))`
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Place all LLM-related tests in `apps/web/__tests__/` directory
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to **/*.test.ts : Include security tests in test suites to verify: authentication is required, IDOR protection works (other users cannot access resources), parameter validation rejects invalid inputs, and error messages don't leak information
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Mock Prisma using `vi.mock("@/utils/prisma")` and import the mock from `@/utils/__mocks__/prisma`
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Use test helpers `getEmail`, `getEmailAccount`, and `getRule` from `@/__tests__/helpers` for mocking emails, accounts, and rules
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Use `vitest` for testing the application
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/__tests__/**/*.{ts,tsx} : AI tests must be placed in the `__tests__` directory and are not run by default (they use a real LLM)
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Prefer using existing helpers from `@/__tests__/helpers.ts` (`getEmailAccount`, `getEmail`, `getRule`, `getMockMessage`, `getMockExecutedRule`) instead of creating custom test data helpers
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:40:00.833Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-11-25T14:40:00.833Z
Learning: Applies to **/*.test.{ts,tsx} : Mock `server-only` using `vi.mock("server-only", () => ({}))`
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Include standard test cases for LLM functionality: happy path, error handling, edge cases (empty input, null values), different user configurations, and various input formats
Applied to files:
apps/web/app/api/llm-tools/invoke/route.test.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Create separate validation files for server actions using the naming convention `apps/web/utils/actions/NAME.validation.ts` containing Zod schemas and inferred types
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.ts
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/utils/actions/**/*.validation.ts : Use Zod schemas for validation and export both schema and inferred types in validation files
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define input validation schemas using Zod in `.validation.ts` files and export both the schema and its inferred TypeScript type
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Define Zod validation schemas in separate `*.validation.ts` files and export both the schema and inferred type (e.g., `CreateExampleBody`)
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.validation.ts : Export types from Zod schemas using `z.infer<>` to maintain type safety between validation and client usage
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.ts
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*.validation.ts : Define validation schemas using Zod
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.tsapps/web/utils/llms/claude-code.ts
📚 Learning: 2025-11-25T14:39:49.448Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/server-actions.mdc:0-0
Timestamp: 2025-11-25T14:39:49.448Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `.schema()` method with Zod validation schemas from corresponding `.validation.ts` files in next-safe-action configuration
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.ts
📚 Learning: 2025-11-25T14:36:51.389Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:51.389Z
Learning: Applies to **/*.validation.ts : Use descriptive error messages in Zod validation schemas
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.ts
📚 Learning: 2025-11-25T14:37:09.306Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/fullstack-workflow.mdc:0-0
Timestamp: 2025-11-25T14:37:09.306Z
Learning: Applies to apps/web/utils/actions/*.ts : Use `next-safe-action` with Zod schemas for all server actions (create/update/delete mutations), storing validation schemas in `apps/web/utils/actions/*.validation.ts`
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.ts
📚 Learning: 2025-11-25T14:36:18.416Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: apps/web/CLAUDE.md:0-0
Timestamp: 2025-11-25T14:36:18.416Z
Learning: Applies to apps/web/utils/actions/**/*.ts : Use `next-safe-action` with `actionClient` for server actions with Zod schema validation
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.ts
📚 Learning: 2025-11-25T14:36:53.147Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/form-handling.mdc:0-0
Timestamp: 2025-11-25T14:36:53.147Z
Learning: Applies to **/*.validation.{ts,tsx} : Define validation schemas using Zod
Applied to files:
apps/web/app/api/llm-tools/invoke/validation.ts
🧬 Code graph analysis (3)
apps/web/utils/llms/index.ts (4)
apps/web/utils/llms/config.ts (1)
Provider(3-13)apps/web/env.ts (1)
env(18-266)apps/web/utils/llms/model.ts (2)
isClaudeCodeAvailable(70-72)buildClaudeCodeConfig(42-64)apps/web/utils/llms/claude-code-llm.ts (2)
createClaudeCodeGenerateText(147-255)createClaudeCodeGenerateObject(261-372)
apps/web/app/api/llm-tools/invoke/route.test.ts (2)
apps/claude-code-wrapper/__tests__/helpers.ts (1)
createMockRequest(226-234)apps/web/next.config.ts (1)
headers(187-264)
apps/web/app/api/llm-tools/invoke/validation.ts (1)
apps/web/utils/actions/rule.validation.ts (1)
delayInMinutesSchema(11-15)
⏰ 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). (1)
- GitHub Check: test
🔇 Additional comments (17)
apps/web/app/api/llm-tools/invoke/route.ts (1)
1-732: LGTM! Secure proxy implementation with comprehensive validation.The LLM tool proxy endpoint is well-implemented with:
- Timing-safe token comparison for authentication
- Comprehensive request validation with Zod schemas
- All database queries properly scoped to
emailAccountId- Generic error messages that don't leak sensitive information
- Structured logging without PII (previous comments addressed)
The use of a POST route without standard middleware is appropriate here since this is a special proxy endpoint designed to be called by Claude Code CLI (an external system) rather than the web app itself.
apps/web/app/api/llm-tools/invoke/route.test.ts (1)
1-635: LGTM! Comprehensive test coverage.The test suite is well-structured with:
- Proper mocking of dependencies (server-only, Prisma, env, logger)
vi.clearAllMocks()inbeforeEachfor test isolation- Comprehensive coverage of authorization, validation, account lookup, tool execution, and edge cases
- Clear test descriptions and assertions
apps/web/utils/llms/index.ts (1)
66-88: LGTM - Claude Code routing logic is well-structured.The routing logic correctly prioritizes explicit provider selection while allowing environment-based defaults. The fallback chain on line 85 properly handles the optional
modelfield.apps/web/utils/llms/model.ts (3)
247-259: Non-null assertion onclaudeCodeConfig.model!is safe but could be clearer.At line 252,
buildClaudeCodeConfig()guaranteesmodelis always set (line 56:modelOverride || env.CLAUDE_CODE_MODEL || "sonnet"), so the non-null assertion is technically safe. However, for consistency with the approach used inindex.tsline 85, consider using the model value directly frombuildClaudeCodeConfig()which always returns a defined model.The
model: null as unknown as LanguageModelV2pattern at line 255 is a reasonable workaround documented with a clear comment explaining why Claude Code doesn't use the standard SDK model interface.
42-64: Well-designed configuration builder with proper validation.The
buildClaudeCodeConfigfunction validates required environment variables and provides clear error messages. The default model fallback chain (modelOverride || env.CLAUDE_CODE_MODEL || "sonnet") is a good pattern.
66-72: Clean availability check implementation.
isClaudeCodeAvailable()correctly checks both required env vars without throwing, enabling safe conditional routing.apps/web/utils/llms/claude-code.ts (3)
180-238: SSE parser implementation is robust.The
createSSEParsercorrectly reuses a singleTextDecoderwith{ stream: true }to handle multi-byte UTF-8 characters spanning chunks. The flush handler properly processes any remaining buffer data.
446-534: Structured object generation with proper Zod validation.The
claudeCodeGenerateObjectfunction correctly converts Zod schemas to JSON Schema for the wrapper service and validates the response against the original Zod schema. The error handling provides clear context with the raw text included for debugging.
63-76: Good defensive JSON parsing with logging.The
safeJsonParsehelper gracefully handles non-JSON responses and logs a preview for debugging without exposing sensitive data.apps/web/utils/llms/claude-code-llm.ts (4)
47-63: Useful label-based model optimization pattern.The
LABEL_MODEL_OVERRIDESmap allows routing simple tasks to faster/cheaper models without modifying upstream code. ThegetModelForLabelfunction provides a clean fallback chain.
69-133: Session management with appropriate graceful degradation.The
retrieveSessionIdandpersistSessionIdfunctions properly handle errors without breaking the main flow. Usinglogger.errorensures Sentry captures these failures for monitoring while allowing the LLM call to proceed.
209-243: Well-documented stub values in generateText result.The comments clearly explain why fields like
toolCalls,reasoning, andstepsare stubbed. This helps future maintainers understand the limitations of the Claude Code integration.
414-425: Good type guard for content parts validation.The
isTextContentPartfunction properly validates the structure of content parts at runtime before accessing properties, addressing the concern about unsafe type assertions.apps/claude-code-wrapper/src/routes/generate.ts (4)
1-12: LGTM!Imports and router initialization follow proper TypeScript and Express patterns.
33-60: LGTM!Request processing follows best practices with proper validation, structured logging, async/await error handling, and typed responses.
95-116: LGTM!Enhanced prompt construction properly instructs Claude to output JSON matching the schema. The schema is validated input, and the template literal construction is safe.
210-210: LGTM!Standard Express router export pattern.
…Object Same fix as createGenerateText - use proper fallback chain instead of non-null assertion for claudeCodeConfig.model.
- Remove raw response text from JSON parse errors - Remove rawOutput from CLI error responses Both could potentially contain user data or secrets.
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: 0
🧹 Nitpick comments (1)
apps/web/utils/llms/index.ts (1)
177-201: Claude Code routing logic is correct, but consider extracting the duplicated logic.The routing logic for
createGenerateObjectmirrors the logic increateGenerateText(lines 66-88), resulting in ~23 lines of duplicated code. While both implementations are correct and type-safe, consolidating this logic would improve maintainability and reduce the risk of inconsistencies.Consider extracting the common routing logic into a helper function:
function getClaudeCodeConfigIfNeeded( modelOptions: ReturnType<typeof getModel> ): { shouldUse: boolean; config?: ClaudeCodeConfig; modelName?: string } { const shouldUse = (modelOptions.provider === Provider.CLAUDE_CODE && modelOptions.claudeCodeConfig) || (env.DEFAULT_LLM_PROVIDER === Provider.CLAUDE_CODE && isClaudeCodeAvailable()); if (!shouldUse) { return { shouldUse: false }; } const config = modelOptions.claudeCodeConfig || buildClaudeCodeConfig(); const modelName = config.model || env.CLAUDE_CODE_MODEL || "sonnet"; return { shouldUse: true, config, modelName }; }Then use it in both functions:
export function createGenerateText({ emailAccount, label, modelOptions, }: { emailAccount: Pick<EmailAccountWithAI, "email" | "id">; label: string; modelOptions: ReturnType<typeof getModel>; }): typeof generateText { const claudeCodeRoute = getClaudeCodeConfigIfNeeded(modelOptions); if (claudeCodeRoute.shouldUse) { return createClaudeCodeGenerateText({ emailAccount, label, config: claudeCodeRoute.config!, modelName: claudeCodeRoute.modelName!, provider: Provider.CLAUDE_CODE, }); } // ... rest of function }
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/claude-code-wrapper/src/routes/generate.ts(1 hunks)apps/web/utils/llms/index.ts(4 hunks)
🧰 Additional context used
📓 Path-based instructions (13)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/data-fetching.mdc)
**/*.{ts,tsx}: For API GET requests to server, use theswrpackage
Useresult?.serverErrorwithtoastErrorfrom@/components/Toastfor error handling in async operations
**/*.{ts,tsx}: Use wrapper functions for Gmail message operations (get, list, batch, etc.) from @/utils/gmail/message.ts instead of direct API calls
Use wrapper functions for Gmail thread operations from @/utils/gmail/thread.ts instead of direct API calls
Use wrapper functions for Gmail label operations from @/utils/gmail/label.ts instead of direct API calls
**/*.{ts,tsx}: For early access feature flags, create hooks using the naming conventionuse[FeatureName]Enabledthat return a boolean fromuseFeatureFlagEnabled("flag-key")
For A/B test variant flags, create hooks using the naming conventionuse[FeatureName]Variantthat define variant types, useuseFeatureFlagVariantKey()with type casting, and provide a default "control" fallback
Use kebab-case for PostHog feature flag keys (e.g.,inbox-cleaner,pricing-options-2)
Always define types for A/B test variant flags (e.g.,type PricingVariant = "control" | "variant-a" | "variant-b") and provide type safety through type casting
**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use this and super in static contexts
Don't use any or unknown as type constraints
Don't use the TypeScript directive @ts-ignore
Don't use TypeScript enums
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 TypeScript namespaces
Don't use non-null assertions with the!postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Useas constinstead of literal types and type annotations
Use eitherT[]orArray<T>consistently
Initialize each enum member value explicitly
Useexport typefor types
Use `impo...
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/utils/llms/index.ts
**/{pages,routes,components}/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/gmail-api.mdc)
Never call Gmail API directly from routes or components - always use wrapper functions from the utils folder
Files:
apps/claude-code-wrapper/src/routes/generate.ts
**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (.cursor/rules/prisma-enum-imports.mdc)
Always import Prisma enums from
@/generated/prisma/enumsinstead of@/generated/prisma/clientto avoid Next.js bundling errors in client componentsImport Prisma using the project's centralized utility:
import prisma from '@/utils/prisma'
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/utils/llms/index.ts
**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/security.mdc)
**/*.ts: ALL database queries MUST be scoped to the authenticated user/account by including user/account filtering in WHERE clauses to prevent unauthorized data access
Always validate that resources belong to the authenticated user before performing operations, using ownership checks in WHERE clauses or relationships
Always validate all input parameters for type, format, and length before using them in database queries
Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Only return necessary fields in API responses using Prisma'sselectoption. Never expose sensitive data such as password hashes, private keys, or system flags
Prevent Insecure Direct Object References (IDOR) by validating resource ownership before operations. AllfindUnique/findFirstcalls MUST include ownership filters
Prevent mass assignment vulnerabilities by explicitly whitelisting allowed fields in update operations instead of accepting all user-provided data
Prevent privilege escalation by never allowing users to modify system fields, ownership fields, or admin-only attributes through user input
AllfindManyqueries MUST be scoped to the user's data by including appropriate WHERE filters to prevent returning data from other users
Use Prisma relationships for access control by leveraging nested where clauses (e.g.,emailAccount: { id: emailAccountId }) to validate ownership
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/utils/llms/index.ts
**/*.{tsx,ts}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
**/*.{tsx,ts}: Use Shadcn UI and Tailwind for components and styling
Usenext/imagepackage for images
For API GET requests to server, use theswrpackage with hooks likeuseSWRto fetch data
For text inputs, use theInputcomponent withregisterPropsfor form integration and error handling
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/utils/llms/index.ts
**/*.{tsx,ts,css}
📄 CodeRabbit inference engine (.cursor/rules/ui-components.mdc)
Implement responsive design with Tailwind CSS using a mobile-first approach
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/utils/llms/index.ts
**/*.{js,jsx,ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
**/*.{js,jsx,ts,tsx}: Don't useaccessKeyattribute on any HTML element
Don't setaria-hidden="true"on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like<marquee>or<blink>
Only use thescopeprop on<th>elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assigntabIndexto non-interactive HTML elements
Don't use positive integers fortabIndexproperty
Don't include "image", "picture", or "photo" in img alt prop
Don't use explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Always include atitleelement for SVG elements
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
AssigntabIndexto non-interactive HTML elements witharia-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include atypeattribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden witharia-hidden)
Always include alangattribute on the html element
Always include atitleattribute for iframe elements
AccompanyonClickwith at least one of:onKeyUp,onKeyDown, oronKeyPress
AccompanyonMouseOver/onMouseOutwithonFocus/onBlur
Include caption tracks for audio and video elements
Use semantic elements instead of role attributes in JSX
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid AR...
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/utils/llms/index.ts
!(pages/_document).{jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/ultracite.mdc)
Don't use the next/head module in pages/_document.js on Next.js projects
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/utils/llms/index.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (.cursor/rules/utilities.mdc)
**/*.{js,ts,jsx,tsx}: Use lodash utilities for common operations (arrays, objects, strings)
Import specific lodash functions to minimize bundle size (e.g.,import groupBy from 'lodash/groupBy')
Files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/utils/llms/index.ts
apps/web/**/*.{ts,tsx}
📄 CodeRabbit inference engine (apps/web/CLAUDE.md)
apps/web/**/*.{ts,tsx}: Use TypeScript with strict null checks
Use@/path aliases for imports from project root
Use proper error handling with try/catch blocks
Format code with Prettier
Follow consistent naming conventions using PascalCase for components
Centralize shared types in dedicated type filesImport specific lodash functions rather than entire lodash library to minimize bundle size (e.g.,
import groupBy from 'lodash/groupBy')
Files:
apps/web/utils/llms/index.ts
apps/web/{utils/ai,utils/llms,__tests__}/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)
LLM-related code must be organized in specific directories:
apps/web/utils/ai/for main implementations,apps/web/utils/llms/for core utilities and configurations, andapps/web/__tests__/for LLM-specific tests
Files:
apps/web/utils/llms/index.ts
apps/web/utils/llms/{index,model}.ts
📄 CodeRabbit inference engine (.cursor/rules/llm.mdc)
Core LLM functionality must be defined in
utils/llms/index.ts, model definitions and configurations inutils/llms/model.ts, and usage tracking inutils/usage.ts
Files:
apps/web/utils/llms/index.ts
**/{server,api,actions,utils}/**/*.ts
📄 CodeRabbit inference engine (.cursor/rules/logging.mdc)
**/{server,api,actions,utils}/**/*.ts: UsecreateScopedLoggerfrom "@/utils/logger" for logging in backend code
Add thecreateScopedLoggerinstantiation at the top of the file with an appropriate scope name
Use.with()method to attach context variables only within specific functions, not on global loggers
For large functions with reused variables, usecreateScopedLogger().with()to attach context once and reuse the logger without passing variables repeatedly
Files:
apps/web/utils/llms/index.ts
🧠 Learnings (22)
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/app/api/**/*.ts : Maintain consistent error response format across all API routes to avoid information disclosure while providing meaningful error feedback
Applied to files:
apps/claude-code-wrapper/src/routes/generate.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to **/*.ts : Prevent Insecure Direct Object References (IDOR) by validating resource ownership in all queries - always include ownership filters (e.g., `emailAccount: { id: emailAccountId }`) when accessing user-specific resources
Applied to files:
apps/claude-code-wrapper/src/routes/generate.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must follow a standard structure: accept options with `inputData` and `emailAccount` parameters, implement input validation with early returns, define separate system and user prompts, create a Zod schema for response validation, and use `createGenerateObject` to execute the LLM call
Applied to files:
apps/claude-code-wrapper/src/routes/generate.tsapps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:39:23.326Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:23.326Z
Learning: Applies to app/api/**/*.ts : Use `SafeError` for error responses to prevent information disclosure - provide generic messages (e.g., 'Rule not found' not 'Rule {id} does not exist for user {userId}') without revealing internal IDs or ownership details
Applied to files:
apps/claude-code-wrapper/src/routes/generate.ts
📚 Learning: 2025-11-25T14:39:27.909Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security.mdc:0-0
Timestamp: 2025-11-25T14:39:27.909Z
Learning: Applies to **/*.ts : Use SafeError for error responses to prevent information disclosure. Generic error messages should not reveal internal IDs, logic, or resource ownership details
Applied to files:
apps/claude-code-wrapper/src/routes/generate.ts
📚 Learning: 2025-11-25T14:39:04.892Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:04.892Z
Learning: Applies to apps/web/app/api/**/route.ts : Error responses must use `SafeError` to prevent information disclosure; avoid revealing internal details like user IDs or database information in error messages
Applied to files:
apps/claude-code-wrapper/src/routes/generate.ts
📚 Learning: 2025-11-25T14:39:08.150Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/security-audit.mdc:0-0
Timestamp: 2025-11-25T14:39:08.150Z
Learning: Applies to apps/web/app/api/**/*.{ts,tsx} : Use generic error messages instead of revealing internal details; throw `SafeError` instead of exposing user IDs, resource IDs, or system information
Applied to files:
apps/claude-code-wrapper/src/routes/generate.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Don't hardcode sensitive data like API keys and tokens
Applied to files:
apps/claude-code-wrapper/src/routes/generate.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : LLM feature functions must import from `zod` for schema validation, use `createScopedLogger` from `@/utils/logger`, `chatCompletionObject` and `createGenerateObject` from `@/utils/llms`, and import `EmailAccountWithAI` type from `@/utils/llms/types`
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/llms/{index,model}.ts : Core LLM functionality must be defined in `utils/llms/index.ts`, model definitions and configurations in `utils/llms/model.ts`, and usage tracking in `utils/usage.ts`
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use TypeScript types for all LLM function parameters and return values, and define clear interfaces for complex input/output structures
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/{utils/ai,utils/llms,__tests__}/**/*.ts : LLM-related code must be organized in specific directories: `apps/web/utils/ai/` for main implementations, `apps/web/utils/llms/` for core utilities and configurations, and `apps/web/__tests__/` for LLM-specific tests
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use descriptive scoped loggers for each LLM feature, log inputs and outputs with appropriate log levels, and include relevant context in log messages
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Use XML-like tags to structure data in prompts, remove excessive whitespace and truncate long inputs, and format data consistently across similar LLM functions
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Implement early returns for invalid LLM inputs, use proper error types and logging, implement fallbacks for AI failures, and add retry logic for transient failures using `withRetry`
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:37:56.430Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm-test.mdc:0-0
Timestamp: 2025-11-25T14:37:56.430Z
Learning: Applies to apps/web/__tests__/**/*.test.ts : Use `console.debug()` for outputting generated LLM content in tests, e.g., `console.debug("Generated content:\n", result.content);`
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Keep related AI functions in the same file or directory, extract common patterns into utility functions, and document complex AI logic with clear comments
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{ts,tsx} : Don't misuse the non-null assertion operator (!) in TypeScript files
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:42:08.869Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-11-25T14:42:08.869Z
Learning: Applies to **/*.{ts,tsx} : Don't use non-null assertions with the `!` postfix operator
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : Always define a Zod schema for LLM response validation and make schemas as specific as possible to guide the LLM output
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:07.606Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/llm.mdc:0-0
Timestamp: 2025-11-25T14:38:07.606Z
Learning: Applies to apps/web/utils/ai/**/*.ts : System prompts must define the LLM's role and task specifications
Applied to files:
apps/web/utils/llms/index.ts
📚 Learning: 2025-11-25T14:38:32.328Z
Learnt from: CR
Repo: elie222/inbox-zero PR: 0
File: .cursor/rules/posthog-feature-flags.mdc:0-0
Timestamp: 2025-11-25T14:38:32.328Z
Learning: Applies to **/*.{ts,tsx} : Always define types for A/B test variant flags (e.g., `type PricingVariant = "control" | "variant-a" | "variant-b"`) and provide type safety through type casting
Applied to files:
apps/web/utils/llms/index.ts
🧬 Code graph analysis (1)
apps/web/utils/llms/index.ts (4)
apps/web/utils/llms/config.ts (1)
Provider(3-13)apps/web/env.ts (1)
env(18-266)apps/web/utils/llms/model.ts (2)
isClaudeCodeAvailable(70-72)buildClaudeCodeConfig(42-64)apps/web/utils/llms/claude-code-llm.ts (2)
createClaudeCodeGenerateText(147-255)createClaudeCodeGenerateObject(261-372)
⏰ 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). (1)
- GitHub Check: test
🔇 Additional comments (3)
apps/web/utils/llms/index.ts (2)
17-17: LGTM: Imports are well-organized and follow project conventions.The new imports for Claude Code integration are necessary and properly structured. They follow the project's path alias conventions and import specific functions to minimize bundle size.
Also applies to: 34-45
66-88: LGTM: Claude Code routing logic is correct and type-safe.The routing logic properly checks if Claude Code should be used based on provider configuration and availability. The fallback chain for
modelName(line 85) correctly handles optional values and matches the logic inbuildClaudeCodeConfig(). The comments clearly explain the wrapper layer override behavior.apps/claude-code-wrapper/src/routes/generate.ts (1)
1-211: Well-implemented route handlers with past security issues properly addressed.The code quality is solid:
- Proper input validation using Zod schemas
- Safe logging that only captures metadata (model, prompt length) without exposing PII
- JSON parsing with multiple fallback strategies using non-greedy regex patterns (past issue resolved)
- Error handling that returns generic messages without leaking sensitive data like
rawOutputor raw response text (past issues resolved)- Clean separation of concerns with dedicated helper functions
The implementation correctly follows the authenticated wrapper service pattern described in the PR objectives.
Warning
Heavy PR: This PR adds ~12,000 lines including a new service (
apps/claude-code-wrapper/), comprehensive tests, and lockfile updates. The wrapper service alone accounts for ~3,500 lines. We recommend reviewing in sections: wrapper service → provider integration → tests.Caution
Anthropic Terms of Use: This integration pattern uses Claude Code CLI programmatically. Anthropic's terms restrict such usage to self-hosting under limited circumstances only. Please review Anthropic's Terms of Use before deploying. This is NOT suitable for commercial SaaS offerings.
Summary
Adds Claude Code CLI as a new LLM provider option, enabling users with Claude Max subscriptions to route AI operations through Claude Code instead of direct API calls. This provides an alternative for users who want to leverage their existing Max subscription for Inbox Zero's AI features.
Architecture
What Works
generateObjectgenerateObjectgenerateObjectgenerateTextgenerateText/generateObjectWhat Doesn't Work (Yet)
The chat assistant uses
streamText()with tools, which requires a different integration pattern. The infrastructure is scaffolded (tool proxy endpoint, Claude skill file) but would require either:Workaround: Set
CHAT_LLM_PROVIDER=anthropicto use direct API for chat while using Claude Code for everything else.Extensibility
Claude Skills
The wrapper includes a skill system (
apps/claude-code-wrapper/.claude/skills/) that allows Claude Code to invoke Inbox Zero tools. This enables future capabilities like:Future CLI Integration
The provider-layer pattern established here can be extended to support other CLI-based AI tools, not just Claude Code.
New Environment Variables
Provider Selection Logic
Docker Usage
Open Source Intent
We intend to open source the wrapper service as a standalone project. It was developed specifically to integrate Claude Code with Inbox Zero, so in this PR it remains coupled with the web app. Future work may extract it to a separate repository.
Files Changed
New Service
apps/claude-code-wrapper/- Complete Bun/Hono service (Dockerfile, routes, CLI handling, tests)Provider Integration
apps/web/utils/llms/claude-code.ts- HTTP clientapps/web/utils/llms/claude-code-llm.ts- AI SDK-compatible wrappersapps/web/utils/llms/model.ts- Provider configurationapps/web/utils/llms/index.ts- Override logicTool Proxy (for Claude skills)
apps/web/app/api/llm-tools/invoke/- REST endpoint for tool invocationConfiguration
apps/web/env.ts- New environment variablesdocker-compose.yml- Wrapper service definitionturbo.json- Build configurationTest Plan
Summary by CodeRabbit
New Features
Documentation
Tests
Chores
✏️ Tip: You can customize this high-level summary in your review settings.