From b678c2f1b0aa9a712eb908d0a1ccc290e56cbe8c Mon Sep 17 00:00:00 2001 From: Affaan Mustafa Date: Fri, 13 Feb 2026 04:28:50 -0800 Subject: [PATCH] fix: collapse newlines in user messages to prevent markdown list breaks in session-end User messages containing newline characters were being added as-is to markdown list items in buildSummarySection(), breaking the list format. Now newlines are replaced with spaces before backtick escaping. --- scripts/hooks/session-end.js | 4 ++-- tests/hooks/hooks.test.js | 43 ++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/scripts/hooks/session-end.js b/scripts/hooks/session-end.js index d6ce61ac..59bad8d2 100644 --- a/scripts/hooks/session-end.js +++ b/scripts/hooks/session-end.js @@ -206,10 +206,10 @@ ${summarySection} function buildSummarySection(summary) { let section = '## Session Summary\n\n'; - // Tasks (from user messages — escape backticks to prevent markdown breaks) + // Tasks (from user messages — collapse newlines and escape backticks to prevent markdown breaks) section += '### Tasks\n'; for (const msg of summary.userMessages) { - section += `- ${msg.replace(/`/g, '\\`')}\n`; + section += `- ${msg.replace(/\n/g, ' ').replace(/`/g, '\\`')}\n`; } section += '\n'; diff --git a/tests/hooks/hooks.test.js b/tests/hooks/hooks.test.js index d2ef0af6..2f1d2d61 100644 --- a/tests/hooks/hooks.test.js +++ b/tests/hooks/hooks.test.js @@ -2433,6 +2433,49 @@ async function runTests() { cleanupTestDir(testDir); })) passed++; else failed++; + // Round 40: session-end.js (newline collapse in markdown list items) + console.log('\nRound 40: session-end.js (newline collapse):'); + + if (await asyncTest('collapses newlines in user messages to single-line markdown items', async () => { + const testDir = createTestDir(); + const transcriptPath = path.join(testDir, 'transcript.jsonl'); + + // User message containing newlines that would break markdown list + const lines = [ + JSON.stringify({ type: 'user', content: 'Please help me with:\n1. Task one\n2. Task two\n3. Task three' }), + ]; + fs.writeFileSync(transcriptPath, lines.join('\n')); + + const stdinJson = JSON.stringify({ transcript_path: transcriptPath }); + const result = await runScript(path.join(scriptsDir, 'session-end.js'), stdinJson, { + HOME: testDir + }); + assert.strictEqual(result.code, 0); + + // Find the session file and verify newlines were collapsed + const claudeDir = path.join(testDir, '.claude', 'sessions'); + if (fs.existsSync(claudeDir)) { + const files = fs.readdirSync(claudeDir).filter(f => f.endsWith('.tmp')); + if (files.length > 0) { + const content = fs.readFileSync(path.join(claudeDir, files[0]), 'utf8'); + // Each task should be a single-line markdown list item + const taskLines = content.split('\n').filter(l => l.startsWith('- ')); + for (const line of taskLines) { + assert.ok( + !line.includes('\n'), + 'Task list items should be single-line' + ); + } + // Newlines should be replaced with spaces + assert.ok( + content.includes('Please help me with: 1. Task one 2. Task two'), + `Newlines should be collapsed to spaces, got: ${content.substring(0, 500)}` + ); + } + } + cleanupTestDir(testDir); + })) passed++; else failed++; + // Summary console.log('\n=== Test Results ==='); console.log(`Passed: ${passed}`);