Desktop app adopts the server placeholder "Untitled session" as the final auto-title and never retries
Summary
Since roughly 2026-09-08 14:03 UTC, every new Code-tab session in the Claude desktop app is auto-titled "Untitled session" and stays that way. Sessions opened at 13:36 UTC and earlier the same day were titled normally. Nothing changed locally in that window (same app build, same CLI build, same feature-flag load, no settings change).
Environment
- Claude desktop app 1.46388.4 (macOS, Darwin 25.4.0)
- Bundled Claude Code CLI 2.1.260
- Remote Control auto-enabled on first turn (
rcAutoEnable ... source=gb_default)
What happens
- Open a new Code-tab session and send a first message (a plain sentence or a slash command; both reproduce).
- About 3 s after
Starting local session ...,main.logshowsLocalSessions.updateSession: keys=title,titleSourcewithtitleSource: 'auto'. - The adopted title is the literal string
Untitled session. It is also written into the CLI transcript as{"type":"custom-title","customTitle":"Untitled session"}. - The session is never retitled afterwards.
Five consecutive new sessions reproduced it; the last good session, opened 27 minutes earlier, got a real title about 1 s after start via the same log line.
Where it seems to come from
Reading the app bundle, the only auto-title path is adoptRemoteTitle: after Remote Control publishes the session, the app calls getSessionTitle for the remote session and adopts the returned title whenever it is non-null and differs from the client-supplied refusalTitle, then sets titleAdopt.done = true.
That means a server response whose title is the placeholder Untitled session (rather than null) is adopted as final, and the app never asks again. So a server-side change in when or what getSessionTitle returns turns every new session into a permanent "Untitled session".
Expected
- The app should treat the placeholder title the same as null: do not adopt it, do not mark adoption done, keep retrying up to
TITLE_ADOPT_MAX_ATTEMPTS(or fall back to a local title from the first message). - Server side:
getSessionTitleshould not return the placeholder as if it were a generated title.
Workaround
Renaming by hand, or from a session via the set_session_title session-management tool, works and sticks.
Code path (desktop 1.46388.4, app.asar, minified names as shipped)
- The remote-control lease manager owns
adoptRemoteTitle(lease)withstatic TITLE_ADOPT_MAX_ATTEMPTS = 3. - It is called twice: once when the lease attaches (
phase = "running", right afternotifyRemoteStart), and once at every turn end whilephase === "running". - Body, de-minified:
adoptRemoteTitle(lease) {
const { localSessionId, ccrSessionId } = lease;
if (localSessionId === undefined) return;
const a = lease.titleAdopt ??= { done: false, attempts: 0, inFlight: false };
if (a.done || a.inFlight || a.attempts >= TITLE_ADOPT_MAX_ATTEMPTS) return;
a.inFlight = true; a.attempts += 1;
(async () => {
try {
const { title, refusalTitle } = await api.getSessionTitle(ccrSessionId); // GET /v1/code/sessions/<id>
if (lease.titleAdopt !== a) return;
if (title !== null && title !== refusalTitle && !this.leaseCancelled(lease)) {
await sessions.adoptSessionTitle(localSessionId, title); // updateSession({title, titleSource: "auto"})
a.done = true;
}
} catch (e) { log.info(`title adoption skipped (${e}) session=${ccrSessionId}`); }
finally { if (lease.titleAdopt === a) a.inFlight = false; }
})();
}
getSessionTitlereadssession.title(orresponse_shape.title) andclient_metadata.desktop_rc_refusal.title, both through a normalizer that only rejects empty or invalid strings. The stringUntitled sessionpasses it.- The app already has the constant
"Untitled session"as its own local placeholder (used for imported CLI sessions with no title).
So when GET /v1/code/sessions/<id> returns title: "Untitled session" on the first attempt (about 3 s after the session starts), the guard title !== null && title !== refusalTitle passes, the placeholder is written with titleSource: "auto", done is set, and the turn-end retries never run.
Proposed fix
const PLACEHOLDER = "Untitled session";
const usable = title !== null && title !== refusalTitle && title !== PLACEHOLDER;
if (usable && !this.leaseCancelled(lease)) { await sessions.adoptSessionTitle(localSessionId, title); a.done = true; }
else if (title === PLACEHOLDER) { a.attempts -= 1; } // not a real attempt; let the turn-end retry ask again
Optionally also fall back to a locally generated title after the last failed attempt, so a slow or absent server title never leaves a session unnamed.
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗