A session keeps the title we generated for it across a restart - #44
Merged
Conversation
Every guard against re-naming a session lived in memory: SessionNamer's rank ordering and the attempt budget are both dropped when the PTY exits, and the persisted row recorded only manuallyRenamed, which a generated name deliberately leaves false. So a stop/start reached the naming path with nothing standing in its way and renamed the session twice — once from the native first-message read, then again from a fresh model spawn. Not back to the same title either: buildTitleContext returns the LAST few messages, so the new name described wherever the conversation had drifted to. Keying that on the agent's session id does not work, which is the part worth writing down. Claude Code's --resume copies the transcript into a NEW file and appends under a fresh id (measured: three ids in one lineage), so the same thread comes back wearing a different name and every restart would look like a new conversation. The winning signal is therefore persisted beside the name as autoTitleRank, and a launch records at the time it happens whether it continues the previous conversation — the first identity report spends that claim, and any rotation after it is a real /clear. applyAutoName enforces the same precedence SessionNamer does, so a first-message or OSC title can no longer displace a generated one after a reload. Legacy rows are not backfilled: a file written before the field cannot say which signal named it, and guessing would freeze those sessions against ever being named properly.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reported as "title getting regenerated every session start and stop". It was, and twice per start.
What was happening
Three gates guard the model spawn in
maybeGenerateTitle, and only one of them was durable:isAutoNameablemanuallyRenamednamer.hasFinalTitleSessionNamertitleAttempts.refusedTitleAttemptsapplyAutoNamedeliberately never setsmanuallyRenamed("a derived name is not a name the user chose"), so gate 1 stays open forever, and the persisted row recorded nothing else about the name. On the next start nothing could tellFix S3 retry backofffromSession 3.So the SessionStart post landed with all three gates open:
first-message, so generation fired — rename Run a worktree.setup block before an isolated session's agent starts #2.And #2 was not a re-derivation of the old title.
buildTitleContextreturns the lastMAX_MSGSmessages, so on a resumed session it named wherever the conversation had drifted to. That is why the name kept moving rather than settling.Why the obvious fix does not work
Keying the durable marker on
agentSessionIdlooks right and fails. Claude Code's--resumedoes not keep the session id — it copies the transcript into a new file and appends under a fresh one:Three ids, one conversation. Every restart rotates it, so a key on that id would never match and the bug would survive the fix. Same fact explains a second trigger:
TitleAttemptsis keyed byagentSessionId, so a resume gets a fresh budget even without a bridge restart.The fix
autoTitleRankon the session row records which signal produced the current name, written byapplyAutoNameat the same moment as the name so the two cannot drift.SessionNamerpasses its rank through to the sink;applyAutoNameenforces the same precedence durably, which is what kills rename #1.maybeGenerateTitlegates onhasFinalAutoTitle, which kills rename #2.noteConversationStartanswers the question the id cannot: the launch records whether it resumed anything. The first identity report spends that claim; every rotation after it is a real/clearand releases the title. A launch that resumes nothing releases it too — that genuinely is a new conversation.Legacy rows are not backfilled: a file written before the field cannot say whether its name was generated or read off the opening prompt, and guessing "generated" would freeze those sessions against ever being named properly. Absent means "behave as before".
Verification
session-manager-autonameandsession-namer, covering the precedence after a reload, the resume continuation, the/clearrelease, a resume that reports the same id (which must still spend the claim), and the legacy row.Nothing app-side changes —
autoTitleRankis persisted-only andtoWirelists its fields explicitly.One gap, stated plainly: the
agent-coregate is a one-line delegation inside a closure with no direct test, the same shape as thehasFinalTitleguard beside it. The predicate it calls is tested.