fix(cli): strip bloated file contents from tool metadata to fix session loading perf (#7706)

Edit, apply_patch, and user message summary were storing full before/after
file contents in metadata — causing sessions to balloon to 90+ MB and crash
the VS Code extension on load.

Strip before/after from edit filediff, apply_patch files[], and
summary.diffs both at write time (new data) and read time (existing data).
This commit is contained in:
Marius
2026-03-26 15:47:04 +01:00
committed by GitHub
parent f65429df6c
commit f2dcafc2c5
3 changed files with 93 additions and 27 deletions
@@ -2007,8 +2007,8 @@ interface ApplyPatchFile {
relativePath: string
type: "add" | "update" | "delete" | "move"
diff: string
before: string
after: string
before?: string
after?: string
additions: number
deletions: number
movePath?: string
@@ -2158,7 +2158,7 @@ ToolRegistry.register({
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content>
<Show when={visible()}>
<Show when={visible() && file.before !== undefined}>
<div data-component="apply-patch-file-diff">
<Dynamic
component={fileComponent}
@@ -2206,14 +2206,16 @@ ToolRegistry.register({
</Switch>
}
>
<div data-component="apply-patch-file-diff">
<Dynamic
component={fileComponent}
mode="diff"
before={{ name: file().filePath, contents: file().before }}
after={{ name: file().movePath ?? file().filePath, contents: file().after }}
/>
</div>
<Show when={file().before !== undefined}>
<div data-component="apply-patch-file-diff">
<Dynamic
component={fileComponent}
mode="diff"
before={{ name: file().filePath, contents: file().before }}
after={{ name: file().movePath ?? file().filePath, contents: file().after }}
/>
</div>
</Show>
</ToolFileAccordion>
)}
</Show>
+67 -5
View File
@@ -503,6 +503,62 @@ export namespace MessageV2 {
})
export type WithParts = z.infer<typeof WithParts>
// kilocode_change start - strip bloated metadata fields from stored parts to prevent multi-MB payloads
// This handles both legacy data that was stored with full file contents and keeps the API response lean.
function stripPartMetadata(part: Part): Part {
if (part.type !== "tool") return part
const { state } = part
if (state.status !== "completed" && state.status !== "running") return part
const meta = state.metadata
if (!meta) return part
let changed = false
let next = meta
// Strip edit tool's filediff.before/after (full file contents)
if (meta.filediff && (meta.filediff.before || meta.filediff.after)) {
const { before, after, ...rest } = meta.filediff
next = { ...next, filediff: rest }
changed = true
}
// Strip apply_patch tool's files[].before/after (full file contents per file)
if (Array.isArray(meta.files) && meta.files.length > 0 && meta.files[0]?.before !== undefined) {
next = {
...next,
files: meta.files.map((f: Record<string, unknown>) => {
const { before, after, ...rest } = f
return rest
}),
}
changed = true
}
if (!changed) return part
return { ...part, state: { ...state, metadata: next } } as Part
}
function stripMessageMetadata(info: Info): Info {
// Strip summary.diffs before/after from user messages (can be 20+ MB)
if (info.role !== "user") return info
const user = info as User
if (!user.summary?.diffs?.length) return info
const has = user.summary.diffs.some((d: Snapshot.FileDiff) => d.before || d.after)
if (!has) return info
return {
...user,
summary: {
...user.summary,
diffs: user.summary.diffs.map(({ before, after, ...rest }: Snapshot.FileDiff) => ({
...rest,
before: "",
after: "",
})),
},
} as Info
}
// kilocode_change end
export function toModelMessages(
input: WithParts[],
model: Provider.Model,
@@ -766,12 +822,12 @@ export namespace MessageV2 {
.all(),
)
for (const row of partRows) {
const part = {
const part = stripPartMetadata({
...row.data,
id: row.id,
sessionID: row.session_id,
messageID: row.message_id,
} as MessageV2.Part
} as MessageV2.Part) // kilocode_change - strip bloated metadata on read
const list = partsByMessage.get(row.message_id)
if (list) list.push(part)
else partsByMessage.set(row.message_id, [part])
@@ -779,7 +835,7 @@ export namespace MessageV2 {
}
for (const row of rows) {
const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info
const info = stripMessageMetadata({ ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info) // kilocode_change
yield {
info,
parts: partsByMessage.get(row.id) ?? [],
@@ -796,7 +852,13 @@ export namespace MessageV2 {
db.select().from(PartTable).where(eq(PartTable.message_id, message_id)).orderBy(PartTable.id).all(),
)
return rows.map(
(row) => ({ ...row.data, id: row.id, sessionID: row.session_id, messageID: row.message_id }) as MessageV2.Part,
(row) =>
stripPartMetadata({
...row.data,
id: row.id,
sessionID: row.session_id,
messageID: row.message_id,
} as MessageV2.Part), // kilocode_change - strip bloated metadata on read
)
})
@@ -808,7 +870,7 @@ export namespace MessageV2 {
async (input): Promise<WithParts> => {
const row = Database.use((db) => db.select().from(MessageTable).where(eq(MessageTable.id, input.messageID)).get())
if (!row) throw new Error(`Message not found: ${input.messageID}`)
const info = { ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info
const info = stripMessageMetadata({ ...row.data, id: row.id, sessionID: row.session_id } as MessageV2.Info) // kilocode_change
return {
info,
parts: await parts(input.messageID),
+13 -11
View File
@@ -1840,8 +1840,8 @@ interface ApplyPatchFile {
relativePath: string
type: "add" | "update" | "delete" | "move"
diff: string
before: string
after: string
before?: string
after?: string
additions: number
deletions: number
movePath?: string
@@ -1956,7 +1956,7 @@ ToolRegistry.register({
</Accordion.Trigger>
</StickyAccordionHeader>
<Accordion.Content>
<Show when={visible()}>
<Show when={visible() && file.before !== undefined}>
<div data-component="apply-patch-file-diff">
<Dynamic
component={fileComponent}
@@ -2032,14 +2032,16 @@ ToolRegistry.register({
</Switch>
}
>
<div data-component="apply-patch-file-diff">
<Dynamic
component={fileComponent}
mode="diff"
before={{ name: single()!.filePath, contents: single()!.before }}
after={{ name: single()!.movePath ?? single()!.filePath, contents: single()!.after }}
/>
</div>
<Show when={single()!.before !== undefined}>
<div data-component="apply-patch-file-diff">
<Dynamic
component={fileComponent}
mode="diff"
before={{ name: single()!.filePath, contents: single()!.before }}
after={{ name: single()!.movePath ?? single()!.filePath, contents: single()!.after }}
/>
</div>
</Show>
</ToolFileAccordion>
</BasicTool>
</div>