Skip to content

feat: generate session titles locally, without sending an excerpt - #13

Merged
devGregA merged 1 commit into
mainfrom
harden/disable-session-title-egress
Sep 17, 2026
Merged

devGregA merged 1 commit into
mainfrom
harden/disable-session-title-egress

Conversation

@devGregA

Copy link
Copy Markdown
Contributor

Problem

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, 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_title experimental 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

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.

What changed

Sealed in two places:

  • fetchChatTitle is the chokepoint — the only function that sends chat_title — so refusing there also covers any caller a later upstream merge introduces. 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 the OAuth token provider for an access token.

Two test-structure changes worth calling out, because they preserve coverage rather than delete it:

  • composeTitleInput is 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.
  • The SDK's serializes a temporary title-generation close against a public resume test 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:

  • oauth: refuses without making a request, refuses for every caller
  • engine: never 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 event
  • server: never sends a chat excerpt through the public REST title path (asserts the tools endpoint was never hit and the session keeps its local title)
  • SDK: applies no generated title and sends no excerpt to the tools endpoint

🤖 Generated with Claude Code

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>
@devGregA
devGregA merged commit 2edc06f into main Sep 17, 2026
15 checks passed
@devGregA
devGregA deleted the harden/disable-session-title-egress branch September 17, 2026 19:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant