anthropics / anthropics/claude-code

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

Đang mở
#92,861 1 bình luận 0 reaction 0 người được giao Xem trên GitHub
area:desktop bug has repro platform:macos
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

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.