feat: generate session titles locally, without sending an excerpt - #13
Merged
Merged
Conversation
Session-title generation posted an excerpt of the conversation, the user's prompt and the assistant's reply, to the managed platform's /tools endpoint as `chat_title`. It did that purely to produce a display string for the session list. That is the only call in the product that sends conversation content anywhere other than the configured model provider. With a third-party model backend it is a second, unrelated destination for the same text, and the bundled web UI fires it unprompted on the first turn. Upstream gated it behind an `auto_session_title` experimental flag; that flag no longer exists anywhere in the tree, so there was nothing left to turn off. Nothing is lost. applyPromptMetadataUpdate already sets a `replaceable` title locally from the first prompt via titleFromPromptMetadataText, and that text passes through the secret redactor first. Sessions stay titled; the title is a truncated prompt rather than a generated phrase. Sealed in two places: - fetchChatTitle is the chokepoint, the only function that sends chat_title, so refusing there covers any caller a later upstream merge adds. Upstream's body is kept as fetchChatTitleRemote, unreachable, so its tests keep running and upstream changes still merge cleanly. - SessionTitleService.generateAndApply bails in front of it, so the disabled path does no provider lookup and never asks for an OAuth access token. composeTitleInput is exported so its budget and digest-elision behaviour stays under test as a pure function, instead of being asserted through a request body that is no longer sent. The SDK's title-generation serialization test keeps its real subject, resume queuing behind an in-flight close, by gating on the will-close hook rather than on an outbound request starting. Verified: typecheck, lint and build clean, and the suite is back to the same pre-existing kap-server search failures as main. All seven new tests were negative-controlled by restoring both call paths: every one fails without the seals, at the oauth, engine, server and SDK layers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Problem
Session-title generation posted an excerpt of the conversation — the user's prompt and the assistant's reply — to the managed platform's
/toolsendpoint aschat_title, purely to produce a display string for the session list.That is the only call in the product that sends conversation content anywhere other than the configured model provider. With a third-party model backend it is a second, unrelated destination for the same text, and the bundled web UI fires it unprompted on the first turn.
Upstream gated this behind an
auto_session_titleexperimental flag. That flag no longer exists anywhere in the 2.0.0 tree — only the changelog mentions it — so there was nothing left to turn off.Nothing is lost
applyPromptMetadataUpdatealready sets areplaceabletitle locally from the first prompt viatitleFromPromptMetadataText, and that text passes through the secret redactor first. Sessions stay titled. The title is a truncated prompt rather than a generated phrase.What changed
Sealed in two places:
fetchChatTitleis the chokepoint — the only function that sendschat_title— so refusing there also covers any caller a later upstream merge introduces. Upstream's body is kept asfetchChatTitleRemote, unreachable, so its tests keep running and upstream changes still merge cleanly.SessionTitleService.generateAndApplybails in front of it, so the disabled path does no provider lookup and never asks the OAuth token provider for an access token.Two test-structure changes worth calling out, because they preserve coverage rather than delete it:
composeTitleInputis now exported, so its per-prompt budget, first-turn truncation and digest-elision behaviour stay under test as a pure function rather than being asserted through a request body that is no longer sent.serializes a temporary title-generation close against a public resumetest keeps its real subject — a public resume queuing behind an in-flight close — by gating on the will-close hook instead of on an outbound request starting.Verification
Typecheck, lint and build clean. The suite is back to the same pre-existing kap-server search failures as
main.All seven new tests were negative-controlled by restoring both call paths. Every one fails without the seals, at each layer:
refuses without making a request,refuses for every callernever calls the backend, whatever the source,does not reach for an OAuth token either,refuses even when forced,leaves the locally derived title in place,publishes no metadata eventnever sends a chat excerpt through the public REST title path(asserts the tools endpoint was never hit and the session keeps its local title)applies no generated title and sends no excerpt to the tools endpoint🤖 Generated with Claude Code