[BUG] Mid-session AI-title regeneration overwrites agent-name while custom-title persists; resume then registers the AI title as nameSource user, breaking cross-session addressing
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
Short version: a mid-session regeneration of the AI-generated title overwrote the transcript's agent-name record with the AI title, while the custom-title record set by /rename stayed intact. The next claude -r restored the stored agent-name faithfully, so the session came back in ~/.claude/sessions/<pid>.json under the AI title with nameSource: "user", and peers could no longer reach it by its /rename name. No compaction was involved, and a terminal resume did read the stored name back, so this is neither #29922 nor #95807.
Environment: Claude Code 2.1.281 CLI, macOS (Darwin 25.6.0), interactive terminal sessions, no daemon or bg-spare workers running. Both sessions below run with ANTHROPIC_BASE_URL pointed at a local reverse proxy; that is not causal (see the control session).
What the transcript shows (session A, /renamed to Custom Name on day 1):
- Day 1, after
/rename: every per-turn metadata block writescustom-title: Custom Name,ai-title: <title-1>,agent-name: Custom Name. Acompact_boundaryon day 1 did not disturb this;agent-namestayedCustom Nameafterwards. - Day 2, one block written after an ordinary plain-text user turn (no slash command, no compaction anywhere near it):
````
ai-title <title-2> <- newly generated, different from title-1
agent-name <title-2> <- overwritten to match the AI title
and custom-title: Custom Name is still written in every block from then on. The agent-name immediately before this block was Custom Name; every one after it is <title-2> (35 blocks over two days).
- Day 9,
claude --dangerously-skip-permissions -rfrom a terminal.~/.claude/sessions/<pid>.jsontwo seconds afterstartedAt:
````
"name": "<title-2>", "nameSource": "user", "nameSince": <startedAt + 2.4 s>
<session-id>/custom-title.json still says {"customTitle":"Custom Name"} (mtime day 1). The session's own ListAgents self-line prints <title-2> [xxxxxx].
Effect on peers: ListAgents in every other session lists session A as <title-2>; SendMessage to Custom Name fails with No agent named 'Custom Name' is reachable; sending to <title-2> is delivered. Two sessions coordinating by name spent a day believing a third session existed.
Control (session B, same machine, same version, same proxy env, same -r resume path, /renamed on day 1): its ai-title never regenerated (one value across 43 records), its agent-name tracks custom-title throughout, and its registry name after resume is the /rename name. So the discriminator is the AI-title regeneration, not the proxy, not the resume path.
Not verified against source, stated as inference: the writer of the per-turn block appears to set agent-name from the freshly generated AI title without checking whether a custom title exists; and resume reads agent-name (not custom-title) into the registry, tagging it user either way. What triggers a mid-session AI-title regeneration is unknown to me; the turn before it was plain text. One observed instance plus one consistent non-example.
What Should Happen?
- When
custom-titleis set, an AI-title regeneration must not touchagent-name; the user's name wins. - Resume should register from
custom-titlewhen present, and tagnameSource: "user"only when the name actually came from the user. - Ideally
ListAgentsshows the stablesessionIdbeside the per-process name and ref, so peers have an address that survives this class of drift (as asked in #89338 and #93459).
Error Messages/Logs
$ # peer session
$ SendMessage to "Custom Name"
No agent named 'Custom Name' is reachable.
Use ListAgents to see everyone you can message.
$ python3 -c 'import json;d=json.load(open("$HOME/.claude/sessions/<pid>.json"));print({k:d[k] for k in ("sessionId","name","nameSource","nameSince","startedAt","version")})'
{'sessionId': '<session-id>', 'name': '<title-2>', 'nameSource': 'user', 'nameSince': <startedAt+2443>, 'startedAt': <startedAt>, 'version': '2.1.281'}
$ cat ~/.claude/projects/<slug>/<session-id>/custom-title.json
{"customTitle":"Custom Name"}
$ # transcript: first block where agent-name stops matching custom-title
$ python3 - <<'EOF'
import json,sys
last=None
for i,l in enumerate(open(sys.argv[1]),1):
d=json.loads(l); t=d.get("type")
if t=="custom-title": last=d.get("customTitle")
if t=="agent-name" and last and d.get("agentName")!=last: print(i, d.get("agentName"), "!=", last); break
EOF
Steps to Reproduce
I cannot force the AI-title regeneration on demand, so this is the observation path rather than a deterministic repro:
- Start an interactive session, send a message so it gets an AI title, then
/rename Custom Name. - Keep working in that session over several turns and days until a per-turn block shows a new
ai-titlevalue. - In that same block, observe
agent-namenow equals the newai-titlewhilecustom-titleis unchanged. - Exit and
claude -rfrom a terminal. Read~/.claude/sessions/<pid>.json:nameis the AI title,nameSourceisuser. - From another session,
ListAgentsshows the AI title;SendMessagetoCustom Namefails.
Workaround: run /rename Custom Name again in the affected session, then re-read ListAgents from peers after every resume.
Claude Model
Fable 5.1 (reporting session); peer session model not confirmed
Is this a regression?
I don't know
Last Working Version
_No response_
Claude Code Version
2.1.281
Platform
macOS
Additional Information
Related but different mechanism: #29922 (name lost after compaction; here a compaction did not lose it), #95807 (resume mints a derived name; here resume read a stored name back, but the stored agent-name was already wrong), #80906 (titles bleed across lineages; here one session, no fork), #89338 / #93459 (stable addressing request, which would have made this survivable).
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗