anthropics / anthropics/claude-code
Desktop app adopts the server placeholder "Untitled session" as the final auto-title and never retries
- Lenguaje dominante
- Python
- Estrellas
- 145k
- Forks
- 23.1k
- Métricas de merge de PR
- Métricas de PR pendientes
Descripción
## 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.
Guía de contribución
No hay ninguna guía de contribución indexada para este repositorio
Línea de trabajo
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.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- javascript, macos
- Área
- api, desktop
- Tipo de issue
- Error
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Estado de actividad
- Activo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 48/100