Skip to content

Commit cf04937

Browse files
alari76claude
andauthored
fix: preserve user input for session naming (#378)
finalizeResult() cleared _lastUserInput before executeSessionNaming() ran, so the naming prompt always had an empty user message. Without the user's request for context, the model produced generic/repeated names. Add a dedicated _namingUserInput field that captures the first user message and is not cleared by API retry cleanup. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent dd5d4b4 commit cf04937

File tree

4 files changed

+29
-2
lines changed

4 files changed

+29
-2
lines changed

server/session-manager.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1125,6 +1125,7 @@ export class SessionManager {
11251125
const combined = context + '\n\n' + data
11261126
session._lastUserInput = combined
11271127
session._lastUserInputAt = Date.now()
1128+
if (!session._namingUserInput) session._namingUserInput = data
11281129
session._apiRetry.count = 0
11291130
if (!session.isProcessing) {
11301131
session.isProcessing = true
@@ -1144,6 +1145,7 @@ export class SessionManager {
11441145
if (session.claudeProcess && !session.claudeProcess.isReady()) {
11451146
session._lastUserInput = data
11461147
session._lastUserInputAt = Date.now()
1148+
if (!session._namingUserInput) session._namingUserInput = data
11471149
session._apiRetry.count = 0
11481150
if (!session.isProcessing) {
11491151
session.isProcessing = true
@@ -1164,6 +1166,7 @@ export class SessionManager {
11641166

11651167
session._lastUserInput = data
11661168
session._lastUserInputAt = Date.now()
1169+
if (!session._namingUserInput) session._namingUserInput = data
11671170
session._apiRetry.count = 0
11681171
if (!session.isProcessing) {
11691172
session.isProcessing = true

server/session-naming.test.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ function fakeSession(overrides: Record<string, any> = {}): any {
5050
_namingTimer: undefined,
5151
_namingAttempts: 0,
5252
_lastUserInput: 'fix the login bug',
53+
_namingUserInput: 'fix the login bug',
5354
outputHistory: [{ type: 'output', data: 'I will help fix the login bug.' }],
5455
...overrides,
5556
}
@@ -67,7 +68,7 @@ describe('SessionNaming', () => {
6768
// 1. No context yet — retry scheduled
6869
it('re-schedules when no user input and no output history', async () => {
6970
vi.useFakeTimers()
70-
const session = fakeSession({ _lastUserInput: '', outputHistory: [] })
71+
const session = fakeSession({ _lastUserInput: '', _namingUserInput: '', outputHistory: [] })
7172
const deps = makeDeps({ getSession: vi.fn(() => session) })
7273
const naming = new SessionNaming(deps)
7374

@@ -263,6 +264,27 @@ describe('SessionNaming', () => {
263264
vi.useRealTimers()
264265
})
265266

267+
// 12b. executeSessionNaming — uses _namingUserInput when _lastUserInput is cleared
268+
it('uses _namingUserInput when _lastUserInput has been cleared', async () => {
269+
// Simulates the bug scenario: finalizeResult clears _lastUserInput before naming runs
270+
const session = fakeSession({
271+
_lastUserInput: undefined,
272+
_namingUserInput: 'add dark mode support',
273+
outputHistory: [{ type: 'output', data: 'I will add dark mode.' }],
274+
})
275+
const deps = makeDeps({ getSession: vi.fn(() => session) })
276+
const naming = new SessionNaming(deps)
277+
mockSpawn.mockReturnValue(fakeProc('Add Dark Mode Support', 0))
278+
279+
await naming.executeSessionNaming('s1')
280+
281+
expect(deps.rename).toHaveBeenCalledWith('s1', 'Add Dark Mode Support')
282+
// Verify the prompt included the user's message from _namingUserInput
283+
const stdinWrite = mockSpawn.mock.results[0].value.stdin.write
284+
const promptText = stdinWrite.mock.calls[0][0]
285+
expect(promptText).toContain('add dark mode support')
286+
})
287+
266288
// 13. executeSessionNaming — skips if session disappears mid-flight
267289
it('does nothing when session is removed before CLI responds', async () => {
268290
const session = fakeSession()

server/session-naming.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export class SessionNaming {
111111
.join('')
112112
.slice(0, 2000)
113113

114-
const userMsg = session._lastUserInput || ''
114+
const userMsg = session._namingUserInput || session._lastUserInput || ''
115115
if (!userMsg && !latestContext) {
116116
// No context yet — schedule a retry
117117
this.scheduleSessionNaming(sessionId)

server/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ export interface Session {
9191
_lastReportedModel?: string
9292
/** Last user input sent, stored for API error retry. */
9393
_lastUserInput?: string
94+
/** First user input, preserved for session naming (not cleared by API retry). */
95+
_namingUserInput?: string
9496
/** Timestamp of last user input, used to detect stale retries. */
9597
_lastUserInputAt?: number
9698
/** Transient API error retry state for the current turn. */

0 commit comments

Comments
 (0)