From 154b1bb33375d00df0ac0da53c43f7dde4e4832f Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Sun, 6 Sep 2026 21:02:41 -0700 Subject: [PATCH] fix(native-chat): degrade instead of throwing on a null tab title MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A stacked branch can publish title: null when a conversation name is cleared. The wire type says string, so this consumer trusted it and threw inside the store patch that applies the snapshot. Fall back to the placeholder — the producer bug is fixed separately, but a consumer of wire data should not crash on a contract violation. --- .../mirrored-agent-tab-label.test.ts | 8 ++++++++ .../runtime/web-session-tabs-sync/terminal-surfaces.ts | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/runtime/web-session-tabs-sync/mirrored-agent-tab-label.test.ts b/src/renderer/src/runtime/web-session-tabs-sync/mirrored-agent-tab-label.test.ts index e61af7da414..7d5ef3c25f3 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/mirrored-agent-tab-label.test.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/mirrored-agent-tab-label.test.ts @@ -67,6 +67,14 @@ describe('buildMirroredAgentTabs', () => { expect(tab.customLabel).toBeNull() }) + it('degrades to the placeholder when the host violates the string contract', () => { + const snapshot = snapshotWith('claude', 'Named') + // The wire type says `string`, but a host clearing a name can send null. + ;(snapshot.tabs[0] as { title: unknown }).title = null + expect(() => build(snapshot)).not.toThrow() + expect(build(snapshot).label).toBe('Claude Chat') + }) + it('names an agent this build does not know after itself, not Codex', () => { const snapshot = snapshotWith('codex', '') // Cast: the wire union is claude|codex today, but Tab.agentSessionAgent is diff --git a/src/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.ts b/src/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.ts index 0534b4f6ec0..1eb9078ca1d 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.ts @@ -74,7 +74,9 @@ export function buildMirroredAgentTabs( worktreeId: snapshot.worktree, contentType: 'agent-session', agentSessionAgent: tab.agent, - label: tab.title.trim() || defaultAgentChatLabel(tab.agent), + // Why: `title` is wire data typed `string`; a host that violates that must + // degrade to the placeholder, not throw inside the snapshot patch. + label: tab.title?.trim() || defaultAgentChatLabel(tab.agent), // Why: a manual rename lives only on the client; re-nulling it here made // every host snapshot silently discard the user's title. customLabel: existing?.customLabel ?? null,