anthropics / anthropics/claude-code

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

Offen
#92,861 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
area:desktop bug has repro platform:macos
Vorherrschende Sprache
Python
Sterne
145k
Forks
23.1k
PR-Merge-Kennzahlen
PR-Kennzahlen ausstehend

Beschreibung

## 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:

```js
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/
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/` 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

```js
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.

Beitragsleitfaden

Für dieses Repository ist kein Beitragsleitfaden indexiert

Rechercherichtung

Start by finding the source equivalent of the desktop remote-control lease manager's adoptRemoteTitle(lease), TITLE_ADOPT_MAX_ATTEMPTS, api.getSessionTitle, and sessions.adoptSessionTitle. Reproduce with a new Code-tab session and main.log. Done means a returned title of "Untitled session" is not adopted as final and later retries or fallback can still produce a real title.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
javascript, macos
Bereich
api, desktop
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Aktiv
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
48/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.