Skip to content

Commit abc2378

Browse files
hannesrudolphclaude
andcommitted
fix: prevent C# LSP crashes from excessively long URIs
Add URI length validation in diff views and checkpoints to prevent UriFormatException crashes when working with large files. Content is truncated when encoded URIs would exceed safe length limits. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent 395f55b commit abc2378

File tree

3 files changed

+90
-11
lines changed

3 files changed

+90
-11
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"roo-cline": patch
3+
---
4+
5+
Fix C# LSP crashes caused by excessively long URIs in diff views and checkpoints. Added URI length validation to prevent crashes when working with large files by truncating content that would exceed safe URI length limits.

src/core/checkpoints/index.ts

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,46 @@ import { getApiMetrics } from "../../shared/getApiMetrics"
1212

1313
import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider"
1414

15+
// Maximum safe URI length to avoid crashes in language servers
16+
// Most systems have limits between 2KB-32KB, using conservative 8KB limit
17+
const MAX_SAFE_URI_LENGTH = 8192
18+
19+
/**
20+
* Safely creates a diff URI by validating the total URI length.
21+
* If the URI would be too long, truncates the content to avoid LSP crashes.
22+
*/
23+
function createSafeDiffUri(fileName: string, content: string): vscode.Uri {
24+
try {
25+
const base64Content = Buffer.from(content).toString("base64")
26+
const baseUri = `${DIFF_VIEW_URI_SCHEME}:${fileName}`
27+
const testUri = vscode.Uri.parse(baseUri).with({ query: base64Content }).toString()
28+
29+
if (testUri.length <= MAX_SAFE_URI_LENGTH) {
30+
return vscode.Uri.parse(baseUri).with({ query: base64Content })
31+
}
32+
33+
// Calculate available space for content after accounting for URI overhead
34+
const overhead = baseUri.length + 50 // Extra buffer for URI encoding
35+
const maxBase64Length = Math.max(0, MAX_SAFE_URI_LENGTH - overhead)
36+
37+
// Truncate content to fit within safe URI length
38+
const maxContentLength = Math.floor((maxBase64Length * 3) / 4) // Base64 is ~4/3 the size
39+
const truncatedContent =
40+
content.length > maxContentLength
41+
? content.substring(0, maxContentLength) + "\n... [Content truncated to prevent LSP crashes]"
42+
: content
43+
44+
const truncatedBase64 = Buffer.from(truncatedContent).toString("base64")
45+
return vscode.Uri.parse(baseUri).with({ query: truncatedBase64 })
46+
} catch (error) {
47+
console.error(`Failed to create diff URI for ${fileName}:`, error)
48+
// Fallback to empty content if all else fails
49+
return vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${fileName}`).with({
50+
query: Buffer.from("").toString("base64"),
51+
})
52+
}
53+
}
54+
1555
import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../../services/checkpoints"
1656

1757
export function getCheckpointService(cline: Task) {
@@ -277,12 +317,8 @@ export async function checkpointDiff(cline: Task, { ts, previousCommitHash, comm
277317
mode === "full" ? "Changes since task started" : "Changes since previous checkpoint",
278318
changes.map((change) => [
279319
vscode.Uri.file(change.paths.absolute),
280-
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${change.paths.relative}`).with({
281-
query: Buffer.from(change.content.before ?? "").toString("base64"),
282-
}),
283-
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${change.paths.relative}`).with({
284-
query: Buffer.from(change.content.after ?? "").toString("base64"),
285-
}),
320+
createSafeDiffUri(change.paths.relative, change.content.before ?? ""),
321+
createSafeDiffUri(change.paths.relative, change.content.after ?? ""),
286322
]),
287323
)
288324
} catch (err) {

src/integrations/editor/DiffViewProvider.ts

Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,46 @@ import { DecorationController } from "./DecorationController"
1616

1717
export const DIFF_VIEW_URI_SCHEME = "cline-diff"
1818

19+
// Maximum safe URI length to avoid crashes in language servers
20+
// Most systems have limits between 2KB-32KB, using conservative 8KB limit
21+
const MAX_SAFE_URI_LENGTH = 8192
22+
23+
/**
24+
* Safely creates a diff URI by validating the total URI length.
25+
* If the URI would be too long, truncates the content to avoid LSP crashes.
26+
*/
27+
function createSafeDiffUri(fileName: string, content: string): vscode.Uri {
28+
try {
29+
const base64Content = Buffer.from(content).toString("base64")
30+
const baseUri = `${DIFF_VIEW_URI_SCHEME}:${fileName}`
31+
const testUri = vscode.Uri.parse(baseUri).with({ query: base64Content }).toString()
32+
33+
if (testUri.length <= MAX_SAFE_URI_LENGTH) {
34+
return vscode.Uri.parse(baseUri).with({ query: base64Content })
35+
}
36+
37+
// Calculate available space for content after accounting for URI overhead
38+
const overhead = baseUri.length + 50 // Extra buffer for URI encoding
39+
const maxBase64Length = Math.max(0, MAX_SAFE_URI_LENGTH - overhead)
40+
41+
// Truncate content to fit within safe URI length
42+
const maxContentLength = Math.floor((maxBase64Length * 3) / 4) // Base64 is ~4/3 the size
43+
const truncatedContent =
44+
content.length > maxContentLength
45+
? content.substring(0, maxContentLength) + "\n... [Content truncated to prevent LSP crashes]"
46+
: content
47+
48+
const truncatedBase64 = Buffer.from(truncatedContent).toString("base64")
49+
return vscode.Uri.parse(baseUri).with({ query: truncatedBase64 })
50+
} catch (error) {
51+
console.error(`Failed to create diff URI for ${fileName}:`, error)
52+
// Fallback to empty content if all else fails
53+
return vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${fileName}`).with({
54+
query: Buffer.from("").toString("base64"),
55+
})
56+
}
57+
}
58+
1959
// TODO: https://github.com/cline/cline/pull/3354
2060
export class DiffViewProvider {
2161
// Properties to store the results of saveChanges
@@ -310,14 +350,14 @@ export class DiffViewProvider {
310350
indentBy: "",
311351
suppressEmptyNode: true,
312352
processEntities: false,
313-
tagValueProcessor: (name, value) => {
353+
tagValueProcessor: (_name, value) => {
314354
if (typeof value === "string") {
315355
// Only escape <, >, and & characters
316356
return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
317357
}
318358
return value
319359
},
320-
attributeValueProcessor: (name, value) => {
360+
attributeValueProcessor: (_name, value) => {
321361
if (typeof value === "string") {
322362
// Only escape <, >, and & characters
323363
return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
@@ -444,9 +484,7 @@ export class DiffViewProvider {
444484

445485
vscode.commands.executeCommand(
446486
"vscode.diff",
447-
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${fileName}`).with({
448-
query: Buffer.from(this.originalContent ?? "").toString("base64"),
449-
}),
487+
createSafeDiffUri(fileName, this.originalContent ?? ""),
450488
uri,
451489
`${fileName}: ${fileExists ? "Original ↔ Roo's Changes" : "New File"} (Editable)`,
452490
{ preserveFocus: true },

0 commit comments

Comments
 (0)