fix: eliminate memory leak in promptCacheBreakDetection by replacing closures with pre-computed strings

The buildDiffableContent and buildPrevDiffableContent fields were closures
capturing full system prompt and tool schema arrays (~300KB each). With 10
map entries × 2 closures, this held ~6MB of GC-unreachable memory.

Since recordPromptState already serializes the same data for hashing,
pre-computing the diffable content string has negligible marginal cost.
This commit is contained in:
claude-code-best
2026-05-05 08:54:04 +08:00
parent 6ff839d625
commit e7220c530f

View File

@@ -65,7 +65,7 @@ type PreviousState = {
/** Set when cached microcompact sends cache_edits deletions. Cache reads /** Set when cached microcompact sends cache_edits deletions. Cache reads
* will legitimately drop — this is expected, not a break. */ * will legitimately drop — this is expected, not a break. */
cacheDeletionsPending: boolean cacheDeletionsPending: boolean
buildDiffableContent: () => string buildDiffableContent: string
} }
type PendingChanges = { type PendingChanges = {
@@ -95,7 +95,7 @@ type PendingChanges = {
removedBetas: string[] removedBetas: string[]
prevEffortValue: string prevEffortValue: string
newEffortValue: string newEffortValue: string
buildPrevDiffableContent: () => string prevDiffableContent: string
} }
const previousStateBySource = new Map<string, PreviousState>() const previousStateBySource = new Map<string, PreviousState>()
@@ -285,8 +285,6 @@ export function recordPromptState(snapshot: PromptStateSnapshot): void {
const computeToolHashes = () => const computeToolHashes = () =>
computePerToolHashes(strippedTools, toolNames) computePerToolHashes(strippedTools, toolNames)
const systemCharCount = getSystemCharCount(system) const systemCharCount = getSystemCharCount(system)
const lazyDiffableContent = () =>
buildDiffableContent(system, toolSchemas, model)
const isFastMode = fastMode ?? false const isFastMode = fastMode ?? false
const sortedBetas = [...betas].sort() const sortedBetas = [...betas].sort()
const effortStr = effortValue === undefined ? '' : String(effortValue) const effortStr = effortValue === undefined ? '' : String(effortValue)
@@ -321,7 +319,7 @@ export function recordPromptState(snapshot: PromptStateSnapshot): void {
pendingChanges: null, pendingChanges: null,
prevCacheReadTokens: null, prevCacheReadTokens: null,
cacheDeletionsPending: false, cacheDeletionsPending: false,
buildDiffableContent: lazyDiffableContent, buildDiffableContent: buildDiffableContent(system, toolSchemas, model),
perToolHashes: computeToolHashes(), perToolHashes: computeToolHashes(),
}) })
return return
@@ -403,7 +401,7 @@ export function recordPromptState(snapshot: PromptStateSnapshot): void {
removedBetas: prev.betas.filter(b => !newBetaSet.has(b)), removedBetas: prev.betas.filter(b => !newBetaSet.has(b)),
prevEffortValue: prev.effortValue, prevEffortValue: prev.effortValue,
newEffortValue: effortStr, newEffortValue: effortStr,
buildPrevDiffableContent: prev.buildDiffableContent, prevDiffableContent: prev.buildDiffableContent,
} }
} else { } else {
prev.pendingChanges = null prev.pendingChanges = null
@@ -423,7 +421,7 @@ export function recordPromptState(snapshot: PromptStateSnapshot): void {
prev.cachedMCEnabled = cachedMCEnabled prev.cachedMCEnabled = cachedMCEnabled
prev.effortValue = effortStr prev.effortValue = effortStr
prev.extraBodyHash = extraBodyHash prev.extraBodyHash = extraBodyHash
prev.buildDiffableContent = lazyDiffableContent prev.buildDiffableContent = buildDiffableContent(system, toolSchemas, model)
} catch (e: unknown) { } catch (e: unknown) {
logError(e) logError(e)
} }
@@ -648,10 +646,10 @@ export async function checkResponseForCacheBreak(
// the summary log so ants can find it (DevBar UI removed — event data // the summary log so ants can find it (DevBar UI removed — event data
// flows reliably to BQ for analytics). // flows reliably to BQ for analytics).
let diffPath: string | undefined let diffPath: string | undefined
if (changes?.buildPrevDiffableContent) { if (changes?.prevDiffableContent) {
diffPath = await writeCacheBreakDiff( diffPath = await writeCacheBreakDiff(
changes.buildPrevDiffableContent(), changes.prevDiffableContent,
state.buildDiffableContent(), state.buildDiffableContent,
) )
} }