fix(session-end): preserve $-sequences in user messages when rewriting summary (#2180)

The regenerated summary block embeds raw user-message text and was passed
as the *replacement* argument to String.prototype.replace, where $-sequences
($&, $$, $`, $') are special. A user message containing $& re-injected the
entire matched block (duplicating the summary markers) and $$ collapsed to $,
silently corrupting the persisted session summary. buildSummarySection only
escapes newlines and backticks, not $.

Fix: use function replacers (() => summaryBlock) at both rewrite sites so the
replacement text is treated literally. Adds an end-to-end regression test.

Co-authored-by: bymle <229636660+bymle@users.noreply.github.com>
This commit is contained in:
bymle
2026-06-07 13:25:36 +08:00
committed by GitHub
parent 9c35aef60f
commit e7e38cd508
2 changed files with 108 additions and 2 deletions

View File

@@ -255,16 +255,20 @@ async function main() {
if (summary && updatedContent) {
const summaryBlock = buildSummaryBlock(summary);
// Use function replacers: summaryBlock embeds raw user-message text, and a
// string replacement argument interprets $-sequences ($&, $$, $`, $', $n).
// A $& in a user message would otherwise re-inject the entire matched block
// and corrupt the persisted summary. A function replacer is treated literally.
if (updatedContent.includes(SUMMARY_START_MARKER) && updatedContent.includes(SUMMARY_END_MARKER)) {
updatedContent = updatedContent.replace(
new RegExp(`${escapeRegExp(SUMMARY_START_MARKER)}[\\s\\S]*?${escapeRegExp(SUMMARY_END_MARKER)}`),
summaryBlock
() => summaryBlock
);
} else {
// Migration path for files created before summary markers existed.
updatedContent = updatedContent.replace(
/## (?:Session Summary|Current State)[\s\S]*?$/,
`${summaryBlock}\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\`\n`
() => `${summaryBlock}\n\n### Notes for Next Session\n-\n\n### Context to Load\n\`\`\`\n[relevant files]\n\`\`\`\n`
);
}
}