Skip to content

Commit 016065e

Browse files
alari76claude
andcommitted
fix: show 'Session started' immediately instead of empty chat on startup
Previously claude_started was suppressed and the first visible message only appeared on system_init, which fires after the first user input — leaving the chat empty until then. Now claude_started renders a "Session started" system message immediately. Also resets _lastReportedModel on process start so the model info message always appears on system_init, even across process restarts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cc6d04b commit 016065e

File tree

4 files changed

+29
-20
lines changed

4 files changed

+29
-20
lines changed

server/session-lifecycle.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,9 @@ export class SessionLifecycle {
172172

173173
cp.start()
174174
session.claudeProcess = cp
175+
// Reset so the next system_init always broadcasts the model message,
176+
// even if the model hasn't changed since the previous process.
177+
session._lastReportedModel = undefined
175178
this.deps.globalBroadcast?.({ type: 'sessions_updated' })
176179

177180
const startMsg: WsServerMessage = { type: 'claude_started', sessionId }

src/hooks/useChatSocket.hook.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,12 +554,14 @@ describe('useChatSocket hook', () => {
554554
unmount()
555555
})
556556

557-
it('claude_started is suppressed (no message added)', () => {
557+
it('claude_started shows Session started message', () => {
558558
const { result, unmount } = setupConnected()
559559
act(() => MockWebSocket.latest().simulateMessage({
560560
type: 'claude_started', sessionId: 's1',
561561
} as WsServerMessage))
562-
expect(result.current.messages).toHaveLength(0)
562+
expect(result.current.messages).toHaveLength(1)
563+
expect(result.current.messages[0].type).toBe('system')
564+
expect((result.current.messages[0] as any).text).toBe('Session started')
563565
unmount()
564566
})
565567

src/hooks/useChatSocket.test.ts

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,11 @@ describe('processMessage', () => {
158158
expect((result[0] as any).model).toBe('claude-opus-4-6')
159159
})
160160

161-
it('claude_started is suppressed (init shown on system_init instead)', () => {
161+
it('claude_started shows Session started message', () => {
162162
const result = processMessage(empty(), { type: 'claude_started', sessionId: '123' } as WsServerMessage)
163-
expect(result).toHaveLength(0)
163+
expect(result).toHaveLength(1)
164+
expect(result[0].type).toBe('system')
165+
expect((result[0] as any).text).toBe('Session started')
164166
})
165167
})
166168

@@ -347,11 +349,13 @@ describe('rebuildFromHistory', () => {
347349
expect((result[0] as any).text).toBe('User input')
348350
})
349351

350-
it('claude_started is suppressed in history rebuild', () => {
352+
it('claude_started shows Session started in history rebuild', () => {
351353
const result = rebuildFromHistory([
352354
{ type: 'claude_started', sessionId: 's1' } as WsServerMessage,
353355
])
354-
expect(result).toHaveLength(0)
356+
expect(result).toHaveLength(1)
357+
expect(result[0].type).toBe('system')
358+
expect((result[0] as any).text).toBe('Session started')
355359
})
356360

357361
it('builds tool_group from tool_active', () => {
@@ -447,15 +451,17 @@ describe('rebuildFromHistory', () => {
447451
{ type: 'result' } as WsServerMessage,
448452
]
449453
const result = rebuildFromHistory(buffer)
450-
expect(result).toHaveLength(5)
451-
expect(result[0].type).toBe('system') // system_message (claude_started suppressed)
452-
expect(result[1].type).toBe('user') // user_echo
453-
expect(result[2].type).toBe('assistant') // "Sure, I can help." (incomplete — tool_active broke the chain)
454-
expect((result[2] as any).text).toBe('Sure, I can help.')
455-
expect((result[2] as any).complete).toBe(false)
456-
expect(result[3].type).toBe('tool_group') // Read tool
457-
expect(result[4].type).toBe('assistant') // "Here it is." — marked complete by result
458-
expect((result[4] as any).text).toBe('Here it is.')
459-
expect((result[4] as any).complete).toBe(true)
454+
expect(result).toHaveLength(6)
455+
expect(result[0].type).toBe('system') // claude_started → "Session started"
456+
expect((result[0] as any).text).toBe('Session started')
457+
expect(result[1].type).toBe('system') // system_message init
458+
expect(result[2].type).toBe('user') // user_echo
459+
expect(result[3].type).toBe('assistant') // "Sure, I can help." (incomplete — tool_active broke the chain)
460+
expect((result[3] as any).text).toBe('Sure, I can help.')
461+
expect((result[3] as any).complete).toBe(false)
462+
expect(result[4].type).toBe('tool_group') // Read tool
463+
expect(result[5].type).toBe('assistant') // "Here it is." — marked complete by result
464+
expect((result[5] as any).text).toBe('Here it is.')
465+
expect((result[5] as any).complete).toBe(true)
460466
})
461467
})

src/hooks/useChatSocket.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,8 @@ function applyMessageMut(messages: ChatMessage[], msg: WsServerMessage): boolean
6868
return true
6969

7070
case 'claude_started':
71-
// Process spawned — don't show a message yet. The actual "Session started"
72-
// is shown when system_init arrives (system_message subtype=init), proving
73-
// the process is fully initialized and ready for input.
74-
return false
71+
messages.push({ type: 'system', subtype: 'init', text: 'Session started', key: nextKey() })
72+
return true
7573

7674
case 'tool_active':
7775
if (last && last.type === 'tool_group') {

0 commit comments

Comments
 (0)