fix: strip ANSI escape codes from session persistence hooks (#642) (#684)

Windows terminals emit control sequences (cursor movement, screen
clearing) that leaked into session.tmp files and were injected
verbatim into Claude's context on the next session start.

Add a comprehensive stripAnsi() to utils.js that handles CSI, OSC,
charset selection, and bare ESC sequences. Apply it in session-end.js
(when extracting user messages from the transcript) and in
session-start.js (safety net before injecting session content).
This commit is contained in:
Affaan Mustafa
2026-03-20 01:38:11 -07:00
committed by GitHub
parent 9a478ad676
commit 28de7cc420
4 changed files with 87 additions and 3 deletions

View File

@@ -21,6 +21,7 @@ const {
readFile,
writeFile,
runCommand,
stripAnsi,
log
} = require('../lib/utils');
@@ -58,8 +59,9 @@ function extractSessionSummary(transcriptPath) {
: Array.isArray(rawContent)
? rawContent.map(c => (c && c.text) || '').join(' ')
: '';
if (text.trim()) {
userMessages.push(text.trim().slice(0, 200));
const cleaned = stripAnsi(text).trim();
if (cleaned) {
userMessages.push(cleaned.slice(0, 200));
}
}

View File

@@ -15,6 +15,7 @@ const {
findFiles,
ensureDir,
readFile,
stripAnsi,
log,
output
} = require('../lib/utils');
@@ -42,7 +43,8 @@ async function main() {
const content = readFile(latest.path);
if (content && !content.includes('[Session context goes here]')) {
// Only inject if the session has actual content (not the blank template)
output(`Previous session summary:\n${content}`);
// Strip ANSI escape codes that may have leaked from terminal output (#642)
output(`Previous session summary:\n${stripAnsi(content)}`);
}
}

View File

@@ -464,6 +464,24 @@ function countInFile(filePath, pattern) {
return matches ? matches.length : 0;
}
/**
* Strip all ANSI escape sequences from a string.
*
* Handles:
* - CSI sequences: \x1b[ … <letter> (colors, cursor movement, erase, etc.)
* - OSC sequences: \x1b] … BEL/ST (window titles, hyperlinks)
* - Charset selection: \x1b(B
* - Bare ESC + single letter: \x1b <letter> (e.g. \x1bM for reverse index)
*
* @param {string} str - Input string possibly containing ANSI codes
* @returns {string} Cleaned string with all escape sequences removed
*/
function stripAnsi(str) {
if (typeof str !== 'string') return '';
// eslint-disable-next-line no-control-regex
return str.replace(/\x1b(?:\[[0-9;?]*[A-Za-z]|\][^\x07\x1b]*(?:\x07|\x1b\\)|\([A-Z]|[A-Z])/g, '');
}
/**
* Search for pattern in file and return matching lines with line numbers
*/
@@ -530,6 +548,9 @@ module.exports = {
countInFile,
grepFile,
// String sanitisation
stripAnsi,
// Hook I/O
readStdinJson,
log,