From c803b49491e96e278ccf5d32312e5180ca4c6e58 Mon Sep 17 00:00:00 2001 From: Gabriel Malinosqui Date: Thu, 5 Feb 2026 17:20:28 -0300 Subject: [PATCH 1/2] feat: filter files exceeding API size limits before sending review Add client-side validation to skip files with diff > 500KB, content > 2MB, and cap at 100 files per request to prevent API 400 errors. Co-Authored-By: Claude Opus 4.6 --- src/services/review.service.ts | 41 +++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/src/services/review.service.ts b/src/services/review.service.ts index 0ca9d1c..6f269b9 100644 --- a/src/services/review.service.ts +++ b/src/services/review.service.ts @@ -5,7 +5,11 @@ import { getTrialIdentifier } from '../utils/rate-limit.js'; import { loadConfig } from '../utils/config.js'; import { CLI_VERSION } from '../constants.js'; import chalk from 'chalk'; -import type { ReviewConfig, ReviewResult, TrialReviewResult, PullRequestSuggestionsResponse, ReviewIssue, ApiFileSuggestion, ApiPrLevelSuggestion, ApiSuggestionsObject, Severity } from '../types/index.js'; +import type { ReviewConfig, ReviewResult, TrialReviewResult, PullRequestSuggestionsResponse, ReviewIssue, ApiFileSuggestion, ApiPrLevelSuggestion, ApiSuggestionsObject, Severity, FileContent } from '../types/index.js'; + +const MAX_FILES = 100; +const MAX_DIFF_SIZE = 500 * 1024; // 500KB +const MAX_CONTENT_SIZE = 2 * 1024 * 1024; // 2MB class ReviewService { private verbose: boolean = false; @@ -33,7 +37,7 @@ class ReviewService { }; if (!fast) { - reviewConfig.files = await gitService.getFullFileContents( + const allFiles = await gitService.getFullFileContents( options?.files, { staged: options?.staged, @@ -41,7 +45,9 @@ class ReviewService { branch: options?.branch, } ); - + + reviewConfig.files = this.filterFiles(allFiles); + if (this.verbose) { console.log(chalk.dim(`[verbose] Full file contents: ${reviewConfig.files?.length || 0} file(s)`)); if (reviewConfig.files && reviewConfig.files.length > 0) { @@ -197,6 +203,35 @@ class ReviewService { })); } + private filterFiles(files: FileContent[]): FileContent[] { + const skipped: string[] = []; + const filtered = files.filter(f => { + if (f.diff.length > MAX_DIFF_SIZE) { + const sizeKB = Math.round(f.diff.length / 1024); + skipped.push(` - ${f.path} (diff: ${sizeKB}KB, max: ${MAX_DIFF_SIZE / 1024}KB)`); + return false; + } + if (f.content.length > MAX_CONTENT_SIZE) { + const sizeMB = (f.content.length / (1024 * 1024)).toFixed(1); + skipped.push(` - ${f.path} (content: ${sizeMB}MB, max: ${MAX_CONTENT_SIZE / (1024 * 1024)}MB)`); + return false; + } + return true; + }); + + if (skipped.length > 0) { + console.log(chalk.yellow(`⚠ Skipped ${skipped.length} file(s) exceeding size limits:`)); + skipped.forEach(msg => console.log(chalk.yellow(msg))); + } + + if (filtered.length > MAX_FILES) { + console.log(chalk.yellow(`⚠ Too many files (${filtered.length}), sending first ${MAX_FILES}`)); + return filtered.slice(0, MAX_FILES); + } + + return filtered; + } + normalizeSeverity(severity?: string): Severity { if (!severity) return 'info'; const s = severity.toLowerCase(); From 56a79dd01d05ec707d926a2eb0d0bcb51f844f5a Mon Sep 17 00:00:00 2001 From: Gabriel Malinosqui Date: Thu, 5 Feb 2026 17:42:41 -0300 Subject: [PATCH 2/2] fix: use Buffer.byteLength for accurate size checks in file filter string.length counts UTF-16 code units, not bytes. Non-ASCII content could pass the check while exceeding API byte limits. Co-Authored-By: Claude Opus 4.6 --- src/services/review.service.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/services/review.service.ts b/src/services/review.service.ts index 6f269b9..79c3103 100644 --- a/src/services/review.service.ts +++ b/src/services/review.service.ts @@ -206,13 +206,15 @@ class ReviewService { private filterFiles(files: FileContent[]): FileContent[] { const skipped: string[] = []; const filtered = files.filter(f => { - if (f.diff.length > MAX_DIFF_SIZE) { - const sizeKB = Math.round(f.diff.length / 1024); + const diffBytes = Buffer.byteLength(f.diff, 'utf8'); + const contentBytes = Buffer.byteLength(f.content, 'utf8'); + if (diffBytes > MAX_DIFF_SIZE) { + const sizeKB = Math.round(diffBytes / 1024); skipped.push(` - ${f.path} (diff: ${sizeKB}KB, max: ${MAX_DIFF_SIZE / 1024}KB)`); return false; } - if (f.content.length > MAX_CONTENT_SIZE) { - const sizeMB = (f.content.length / (1024 * 1024)).toFixed(1); + if (contentBytes > MAX_CONTENT_SIZE) { + const sizeMB = (contentBytes / (1024 * 1024)).toFixed(1); skipped.push(` - ${f.path} (content: ${sizeMB}MB, max: ${MAX_CONTENT_SIZE / (1024 * 1024)}MB)`); return false; }