anthropics / anthropics/claude-code
Desktop app adopts the server placeholder "Untitled session" as the final auto-title and never retries
- Ngôn ngữ chính
- Python
- Star
- 145k
- Fork
- 23.1k
- Chỉ số merge pull request
- Chỉ số pull request đang chờ
Mô tả
## 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.
Hướng dẫn đóng góp
Chưa lập chỉ mục được hướng dẫn đóng góp cho kho mã nguồn này
Hướng nghiên cứu
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.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- javascript, macos
- Lĩnh vực
- api, desktop
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 48/100