stop showing opencode placeholder titles as if they were names

OpenCode titles a session from the conversation, but asynchronously — a finished turn of
ours ended up called "Single color in oc-red2.png", better than anything we would generate.
Until then the session is literally named "New session - 2026-08-10T15:44:17.178Z".

That window is exactly when a session is most visible: /chat/live shows turns that are
RUNNING, so the placeholder is what the panel catches, and a live row was being labelled
with a timestamp string.

Recognise it and treat it as untitled, so the good name arrives on its own. Passing --title
on the run was the other option and is worse: it fixes the transient case by permanently
replacing opencode own title with a truncated prompt, degrading it where it lasts longest.

A pattern match rather than startsWith, because a genuine title is allowed to begin with
those words. Two defects the tests caught while writing them: a whitespace-only title was
not treated as unnamed, and the mapping let undefined through where a string was required.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-10 16:56:07 +01:00
co-authored by Claude Opus 5
parent f0aa6dbf4b
commit 90e0ca8ab4
3 changed files with 50 additions and 4 deletions
+5 -3
View File
@@ -150,9 +150,11 @@ chatRouter.get('/live', async (ctx) => {
isGenerating: true,
pendingTasks: 0,
harness: 'opencode' as const,
// Still null for the first seconds of a turn: the id is only known once the subprocess prints it.
// That window is real and short, and showing nothing beats showing a key the user has never seen.
title: meta?.title ?? null,
// Null for the first seconds of a turn (the id is only known once the subprocess prints it), and
// null again while OpenCode still calls the session `New session - <ISO>` — `listOpenCodeSessions`
// normalises that placeholder to `(untitled)`, and a live row is better blank than named after a
// timestamp. The real title arrives on a later poll, because OpenCode writes it asynchronously.
title: meta?.title && meta.title !== '(untitled)' ? meta.title : null,
cwd: meta?.cwd || null,
};
});