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 <noreply@anthropic.com>
This commit is contained in:
hannesrudolph 2025-06-11 19:56:02 -06:00
parent 395f55b31f
commit abc237870d
3 changed files with 90 additions and 11 deletions

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
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.

View file

@ -12,6 +12,46 @@ import { getApiMetrics } from "../../shared/getApiMetrics"
import { DIFF_VIEW_URI_SCHEME } from "../../integrations/editor/DiffViewProvider"
// Maximum safe URI length to avoid crashes in language servers
// Most systems have limits between 2KB-32KB, using conservative 8KB limit
const MAX_SAFE_URI_LENGTH = 8192
/**
* Safely creates a diff URI by validating the total URI length.
* If the URI would be too long, truncates the content to avoid LSP crashes.
*/
function createSafeDiffUri(fileName: string, content: string): vscode.Uri {
try {
const base64Content = Buffer.from(content).toString("base64")
const baseUri = `${DIFF_VIEW_URI_SCHEME}:${fileName}`
const testUri = vscode.Uri.parse(baseUri).with({ query: base64Content }).toString()
if (testUri.length <= MAX_SAFE_URI_LENGTH) {
return vscode.Uri.parse(baseUri).with({ query: base64Content })
}
// Calculate available space for content after accounting for URI overhead
const overhead = baseUri.length + 50 // Extra buffer for URI encoding
const maxBase64Length = Math.max(0, MAX_SAFE_URI_LENGTH - overhead)
// Truncate content to fit within safe URI length
const maxContentLength = Math.floor((maxBase64Length * 3) / 4) // Base64 is ~4/3 the size
const truncatedContent =
content.length > maxContentLength
? content.substring(0, maxContentLength) + "\n... [Content truncated to prevent LSP crashes]"
: content
const truncatedBase64 = Buffer.from(truncatedContent).toString("base64")
return vscode.Uri.parse(baseUri).with({ query: truncatedBase64 })
} catch (error) {
console.error(`Failed to create diff URI for ${fileName}:`, error)
// Fallback to empty content if all else fails
return vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${fileName}`).with({
query: Buffer.from("").toString("base64"),
})
}
}
import { CheckpointServiceOptions, RepoPerTaskCheckpointService } from "../../services/checkpoints"
export function getCheckpointService(cline: Task) {
@ -277,12 +317,8 @@ export async function checkpointDiff(cline: Task, { ts, previousCommitHash, comm
mode === "full" ? "Changes since task started" : "Changes since previous checkpoint",
changes.map((change) => [
vscode.Uri.file(change.paths.absolute),
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${change.paths.relative}`).with({
query: Buffer.from(change.content.before ?? "").toString("base64"),
}),
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${change.paths.relative}`).with({
query: Buffer.from(change.content.after ?? "").toString("base64"),
}),
createSafeDiffUri(change.paths.relative, change.content.before ?? ""),
createSafeDiffUri(change.paths.relative, change.content.after ?? ""),
]),
)
} catch (err) {

View file

@ -16,6 +16,46 @@ import { DecorationController } from "./DecorationController"
export const DIFF_VIEW_URI_SCHEME = "cline-diff"
// Maximum safe URI length to avoid crashes in language servers
// Most systems have limits between 2KB-32KB, using conservative 8KB limit
const MAX_SAFE_URI_LENGTH = 8192
/**
* Safely creates a diff URI by validating the total URI length.
* If the URI would be too long, truncates the content to avoid LSP crashes.
*/
function createSafeDiffUri(fileName: string, content: string): vscode.Uri {
try {
const base64Content = Buffer.from(content).toString("base64")
const baseUri = `${DIFF_VIEW_URI_SCHEME}:${fileName}`
const testUri = vscode.Uri.parse(baseUri).with({ query: base64Content }).toString()
if (testUri.length <= MAX_SAFE_URI_LENGTH) {
return vscode.Uri.parse(baseUri).with({ query: base64Content })
}
// Calculate available space for content after accounting for URI overhead
const overhead = baseUri.length + 50 // Extra buffer for URI encoding
const maxBase64Length = Math.max(0, MAX_SAFE_URI_LENGTH - overhead)
// Truncate content to fit within safe URI length
const maxContentLength = Math.floor((maxBase64Length * 3) / 4) // Base64 is ~4/3 the size
const truncatedContent =
content.length > maxContentLength
? content.substring(0, maxContentLength) + "\n... [Content truncated to prevent LSP crashes]"
: content
const truncatedBase64 = Buffer.from(truncatedContent).toString("base64")
return vscode.Uri.parse(baseUri).with({ query: truncatedBase64 })
} catch (error) {
console.error(`Failed to create diff URI for ${fileName}:`, error)
// Fallback to empty content if all else fails
return vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${fileName}`).with({
query: Buffer.from("").toString("base64"),
})
}
}
// TODO: https://github.com/cline/cline/pull/3354
export class DiffViewProvider {
// Properties to store the results of saveChanges
@ -310,14 +350,14 @@ export class DiffViewProvider {
indentBy: "",
suppressEmptyNode: true,
processEntities: false,
tagValueProcessor: (name, value) => {
tagValueProcessor: (_name, value) => {
if (typeof value === "string") {
// Only escape <, >, and & characters
return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
}
return value
},
attributeValueProcessor: (name, value) => {
attributeValueProcessor: (_name, value) => {
if (typeof value === "string") {
// Only escape <, >, and & characters
return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
@ -444,9 +484,7 @@ export class DiffViewProvider {
vscode.commands.executeCommand(
"vscode.diff",
vscode.Uri.parse(`${DIFF_VIEW_URI_SCHEME}:${fileName}`).with({
query: Buffer.from(this.originalContent ?? "").toString("base64"),
}),
createSafeDiffUri(fileName, this.originalContent ?? ""),
uri,
`${fileName}: ${fileExists ? "Original ↔ Roo's Changes" : "New File"} (Editable)`,
{ preserveFocus: true },