[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

Status Open
Reported on v2.1.281
Maintainer reply None cached
Activity 1 comment · opened Sep 24, 2026

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 writes custom-title: Custom Name, ai-title: <title-1>, agent-name: Custom Name. A compact_boundary on day 1 did not disturb this; agent-name stayed Custom Name afterwards.
  • 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 -r from a terminal. ~/.claude/sessions/<pid>.json two seconds after startedAt:

``
"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-title is set, an AI-title regeneration must not touch agent-name; the user's name wins.
  • Resume should register from custom-title when present, and tag nameSource: "user" only when the name actually came from the user.
  • Ideally ListAgents shows the stable sessionId beside 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:

  1. Start an interactive session, send a message so it gets an AI title, then /rename Custom Name.
  2. Keep working in that session over several turns and days until a per-turn block shows a new ai-title value.
  3. In that same block, observe agent-name now equals the new ai-title while custom-title is unchanged.
  4. Exit and claude -r from a terminal. Read ~/.claude/sessions/<pid>.json: name is the AI title, nameSource is user.
  5. From another session, ListAgents shows the AI title; SendMessage to Custom Name fails.

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).

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗