[Bug] Auto-generated session titles overwrite user-set names and CLI/remote names diverge
Bug Description
Auto-generated title overwrites a user-set session name; CLI and remote (mobile) names diverge
Summary
Two related defects in session naming, both reproduced today on macOS CLI v2.1.215:
- A name set with
/renameis later replaced by an auto-generated title. The guard that prevents this already exists in the codebase, but only on the background-job path — it is not applied to interactive sessions. - The CLI's local session record and the remote session title are two independent stores with no sync. A rename can land in one and not the other, so the terminal tab / statusline and the mobile app show different names for the same session indefinitely.
Prior reports of defect 1 were all closed without a fix: #23895 (closed as duplicate → chain ends at #6006 closed-as-duplicate, #16053 not-planned/stale, #15762 which is a feature request, not a bug), #65010 (not planned, stale), #63765 (not planned, stale). Defect 2 is the CLI↔mobile variant of #16053 (CLI↔VS Code).
Environment
- Claude Code CLI 2.1.215 (binary build
316ce99,BUILD_TIME 2026-07-19) - macOS (Darwin 25.5.0), Ghostty terminal
- Several interactive sessions running concurrently in the same project directory
- Companion surface: Claude mobile app
Reproduction
Defect 1 — auto-title overwrites a manual name
- Start an interactive CLI session.
- Run
/rename Reports Reviewer. The name applies and is visible in the terminal tab. - Keep working; let the conversation continue (auto-titling runs on conversation events / compaction).
- The tab now reads "Уточнение формата входных данных" — an auto-generated summary of the latest exchange. The manually set name is gone, with no user action.
Defect 2 — local and remote names diverge
- In an interactive CLI session, set a name (in my case the name
WTFended up on the remote side only). - Open the same session in the Claude mobile app → it shows
WTF. - Meanwhile the CLI terminal tab and statusline show
work-os-81— the startup fallback derived from the working directory. - Inspect
~/.claude/sessions/<pid>.json:
{"pid":46645,"sessionId":"[REDACTED]","cwd":"/Users/…/work-os","version":"2.1.215",
"kind":"interactive","name":"work-os-81","nameSource":"derived",
"bridgeSessionId":"[REDACTED]"}
The record still carries the startup fallback (nameSource: "derived"), while the remote session under the same bridgeSessionId carries WTF. The two never reconcile.
Running /rename WTF_1 in the CLI afterwards did update the local record correctly ("name":"WTF_1", nameSource dropped) — so local persistence works on that path; what is missing is any propagation between the two stores.
Evidence from the shipped binary
Session registration writes the derived fallback for interactive sessions:
... , kind: t, entrypoint: …,
...(r || t !== "interactive" ? { name: r }
: { name: derivedFromCwd(cwd()), nameSource: "derived" })
The rename path writes locally, then pushes the title to the remote session as a separate, independent step, and only marks the source as user-set when the caller is not "auto":
await hOt(r, e, n, t);
let i = Rc();
if (i?.kind === "ccr" && i.sessionId) updateSessionTitle(i.sessionId, e); // PUT /v1/code/sessions/{id} {title}
if (await Lhe(e), t !== "auto") await ONe("--name", ["-n"], e, { name: e, nameSource: "user" });
There is no reverse path: nothing reads the remote title back into ~/.claude/sessions/<pid>.json, which is what the tab title and statusline render.
The anti-overwrite guard already exists — on the background-job naming path, an "auto" source explicitly refuses to replace an existing name:
if (i.name === t || (r === "auto" && i.name)) return true; // auto never overwrites an existing name
and likewise in the job-name generator:
let a = await Ea(t);
if (a.name) { ve("job_name"); return } // already named → skip
Interactive sessions appear not to be covered by this check, which is consistent with the observed overwrite in defect 1.
(Caveat: the binary is minified; I read the write paths and the guards directly, but did not trace every caller. The claim "the guard is not applied to interactive sessions" is an inference from the observed behaviour plus the absence of such a check on that path.)
A useful diagnostic
nameSource cleanly distinguishes a name that stuck from one that did…
Note: Content was truncated.