Desktop app adopts the server placeholder "Untitled session" as the final auto-title and never retries

Status Open
Reported on v2.1.260
Maintainer reply None cached
Activity 1 comment · opened Sep 8, 2026

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

  1. Open a new Code-tab session and send a first message (a plain sentence or a slash command; both reproduce).
  2. About 3 s after Starting local session ..., main.log shows LocalSessions.updateSession: keys=title,titleSource with titleSource: 'auto'.
  3. The adopted title is the literal string Untitled session. It is also written into the CLI transcript as {"type":"custom-title","customTitle":"Untitled session"}.
  4. 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: getSessionTitle should 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) with static TITLE_ADOPT_MAX_ATTEMPTS = 3.
  • It is called twice: once when the lease attaches (phase = "running", right after notifyRemoteStart), and once at every turn end while phase === "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; }
  })();
}
  • getSessionTitle reads session.title (or response_shape.title) and client_metadata.desktop_rc_refusal.title, both through a normalizer that only rejects empty or invalid strings. The string Untitled session passes 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.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗