Skip to content

Commit f1d6ca8

Browse files
committed
Fix patch application: improve diff validation, detect incomplete diffs, increase max_tokens
1 parent 1ffb5f0 commit f1d6ca8

1 file changed

Lines changed: 96 additions & 10 deletions

File tree

.github/agent/agent.mjs

Lines changed: 96 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,52 @@ function validateDiff(diff) {
176176
}
177177
}
178178

179+
// Check for incomplete file sections (critical validation)
180+
const fileSections = diff.split(/^--- a\//gm);
181+
for (let i = 1; i < fileSections.length; i++) {
182+
const section = fileSections[i];
183+
const lines = section.split('\n');
184+
185+
// First line should be the file path, second should be +++ b/path
186+
if (lines.length < 2) {
187+
errors.push(`Incomplete file section starting at line ${diff.substring(0, diff.indexOf(section)).split('\n').length + 1}: missing +++ b/ line`);
188+
continue;
189+
}
190+
191+
const oldPath = lines[0].trim();
192+
const newPathLine = lines[1];
193+
194+
if (!newPathLine.startsWith('+++ b/')) {
195+
errors.push(`Incomplete file section for ${oldPath}: missing or malformed +++ b/ line`);
196+
continue;
197+
}
198+
199+
const newPath = newPathLine.substring(6).trim();
200+
201+
// Check if the section has at least one hunk
202+
const hasHunk = section.includes('@@');
203+
if (!hasHunk && !section.includes('new file') && !section.includes('deleted file')) {
204+
errors.push(`File section for ${oldPath} has no hunks (might be incomplete)`);
205+
}
206+
207+
// Check if section ends properly (not mid-line)
208+
const lastLine = lines[lines.length - 1];
209+
if (lastLine && (lastLine.startsWith('+') || lastLine.startsWith('-') || lastLine.startsWith('\\'))) {
210+
// Section might be cut off
211+
const nextSectionStart = diff.indexOf('--- a/', diff.indexOf(section) + section.length);
212+
if (nextSectionStart === -1 && !lastLine.match(/^[\s+-\\]/)) {
213+
// Last line doesn't look like a proper diff line ending
214+
errors.push(`File section for ${oldPath} appears to be cut off (incomplete diff)`);
215+
}
216+
}
217+
}
218+
219+
// Check for incomplete +++ b/ lines (common issue)
220+
const incompletePlusPlus = diff.match(/\+\+\+ b\/[^\n]*$/m);
221+
if (incompletePlusPlus) {
222+
errors.push(`Incomplete +++ b/ line detected at end of diff (file name missing)`);
223+
}
224+
179225
return {
180226
isValid: errors.length === 0,
181227
errors,
@@ -903,22 +949,25 @@ CRITICAL: Generate ONLY the file changes in this EXACT format (no "diff --git" h
903949
existing line
904950
905951
Rules for the diff format:
906-
1. Start each file with "--- a/filepath" and "+++ b/filepath"
952+
1. Start each file with "--- a/filepath" and "+++ b/filepath" (BOTH lines must be complete with full file path)
907953
2. NO "diff --git" line, NO "index" line with hashes
908954
3. Include enough context lines (unchanged lines) around changes
909955
4. Use @@ -startLine,numLines +startLine,numLines @@ for hunks
910956
5. Prefix added lines with "+", removed lines with "-", context lines with " " (space)
911957
6. Include at least 3 lines of context before and after changes
912958
7. Ensure line numbers match the actual file contents exactly
959+
8. CRITICAL: The diff MUST be complete - every file section must have BOTH "--- a/path" AND "+++ b/path" lines with complete file paths
960+
9. CRITICAL: Do NOT truncate the diff - if you reach token limits, prioritize completing fewer files rather than truncating
961+
10. Each file section must end properly - do not cut off mid-line or mid-hunk
913962
914-
Output ONLY the diff content, no explanations, no markdown code blocks.`;
963+
Output ONLY the complete diff content, no explanations, no markdown code blocks. Ensure every file section is fully complete.`;
915964

916965
// Call AI provider with unified interface
917966
async function callAI(promptText) {
918967
if (provider === "claude") {
919968
const response = await aiClient.messages.create({
920969
model: aiModel,
921-
max_tokens: 8192,
970+
max_tokens: 16384, // Increased for larger diffs
922971
temperature: 0.2,
923972
messages: [{ role: "user", content: promptText }],
924973
});
@@ -957,20 +1006,57 @@ try {
9571006
// Extract diff from potential markdown code blocks
9581007
let diff = output.trim();
9591008
if (output.includes("```")) {
960-
// Extract content between code fences
961-
const match = output.match(/```(?:diff)?\n([\s\S]*?)```/);
962-
if (match) {
963-
diff = match[1].trim();
1009+
// Try to extract content between code fences
1010+
// Handle both single and multiple code blocks
1011+
const matches = output.matchAll(/```(?:diff)?\n([\s\S]*?)```/g);
1012+
const extractedDiffs = [];
1013+
for (const match of matches) {
1014+
extractedDiffs.push(match[1].trim());
1015+
}
1016+
// Use the longest extracted diff (likely the actual diff)
1017+
if (extractedDiffs.length > 0) {
1018+
diff = extractedDiffs.reduce((a, b) => a.length > b.length ? a : b);
1019+
}
1020+
1021+
// If no code blocks found but output contains diff markers, use the whole output
1022+
if (!diff.includes("--- a/") && output.includes("--- a/")) {
1023+
// Extract everything after the first "--- a/" line
1024+
const diffStart = output.indexOf("--- a/");
1025+
diff = output.substring(diffStart).trim();
1026+
// Remove any trailing markdown or explanations
1027+
const diffEnd = diff.indexOf("\n\n```") !== -1 ? diff.indexOf("\n\n```") :
1028+
diff.indexOf("\n\n##") !== -1 ? diff.indexOf("\n\n##") :
1029+
diff.indexOf("\n\n**") !== -1 ? diff.indexOf("\n\n**") :
1030+
diff.length;
1031+
diff = diff.substring(0, diffEnd).trim();
9641032
}
9651033
}
9661034

9671035
// Enhanced diff validation
9681036
console.log("[AGENT] Validating diff format...");
9691037
const validationResult = validateDiff(diff);
1038+
1039+
// Check if diff appears to be truncated
1040+
const diffLength = diff.length;
1041+
const lastLines = diff.split('\n').slice(-5).join('\n');
1042+
if (validationResult.errors.length > 0 || diff.match(/\+\+\+ b\/[^\n]*$/m)) {
1043+
console.warn(`[AGENT] WARNING: Diff validation found issues or appears incomplete`);
1044+
console.warn(`[AGENT] Diff length: ${diffLength} characters`);
1045+
console.warn(`[AGENT] Last 5 lines:\n${lastLines}`);
1046+
1047+
// Try to detect if this is a token limit issue
1048+
if (diffLength > 7000 && diff.match(/\+\+\+ b\/[^\n]*$/m)) {
1049+
const errorMsg = `Diff appears to be truncated (likely hit token limit). The AI model may have generated an incomplete diff.\n\nErrors:\n${validationResult.errors.join('\n')}\n\nDiff preview (last 500 chars):\n\`\`\`\n${diff.substring(Math.max(0, diffLength - 500))}\n\`\`\`\n\nSuggestion: Try breaking the task into smaller parts or increase max_tokens.`;
1050+
const errorDetails = formatErrorDetails(new Error(errorMsg), { diff: diff.substring(Math.max(0, diffLength - 1000)), files: validationResult.stats.filesChanged });
1051+
await handleError(new Error(errorMsg), "Incomplete Diff (Token Limit?)", { diff: diff.substring(Math.max(0, diffLength - 1000)), errorDetails });
1052+
// handleError calls process.exit(1), so we never reach here
1053+
}
1054+
}
1055+
9701056
if (!validationResult.isValid) {
971-
const errorMsg = `Invalid diff format:\n${validationResult.errors.join('\n')}\n\nDiff preview:\n\`\`\`\n${diff.substring(0, 500)}\n\`\`\``;
972-
const errorDetails = formatErrorDetails(new Error(errorMsg), { diff, files: validationResult.stats.filesChanged });
973-
await handleError(new Error(errorMsg), "Validation Error", { diff, errorDetails });
1057+
const errorMsg = `Invalid diff format:\n${validationResult.errors.join('\n')}\n\nDiff preview (first 1000 chars):\n\`\`\`\n${diff.substring(0, 1000)}\n\`\`\`\n\nDiff preview (last 500 chars):\n\`\`\`\n${diff.substring(Math.max(0, diffLength - 500))}\n\`\`\``;
1058+
const errorDetails = formatErrorDetails(new Error(errorMsg), { diff: diff.substring(0, 1000), files: validationResult.stats.filesChanged });
1059+
await handleError(new Error(errorMsg), "Validation Error", { diff: diff.substring(0, 1000), errorDetails });
9741060
// handleError calls process.exit(1), so we never reach here
9751061
}
9761062
console.log(`[AGENT] Diff format valid: ${validationResult.stats.filesChanged} files, ${validationResult.stats.hunks} hunks`);

0 commit comments

Comments
 (0)