Skip to content

fix(webui): pin explicitly-set session titles — stop auto-retitle churn (#2165)#2171

Merged
kovtcharov merged 1 commit into
mainfrom
fix/2165-pin-session-titles
Jul 17, 2026
Merged

fix(webui): pin explicitly-set session titles — stop auto-retitle churn (#2165)#2171
kovtcharov merged 1 commit into
mainfrom
fix/2165-pin-session-titles

Conversation

@kovtcharov

Copy link
Copy Markdown
Collaborator

Why this matters

Naming a session — via the UI rename or POST /api/sessions {"title": ...} — didn't stick: the background auto-titler silently renamed it on essentially every assistant reply, with one session cycling through 3+ names in a single run (#2165). Explicit titles are now pinned via a persisted title_is_custom flag and are never touched by the auto-titler, while fresh "New Task"/"New Chat" sessions still get auto-titled on the first substantive message exactly as before. An additive migration backfills existing non-placeholder titles as pinned, so already-renamed sessions stop churning too.

Test plan

  • python -m pytest tests/unit/chat/ — 798 passed (includes new pin/placeholder/migration cases in test_chat_helpers.py, test_database.py, test_server.py)
  • webui vitest run — 182 passed (new ChatView.autotitle.test.tsx: rename pins, client auto-title doesn't) + tsc --noEmit clean
  • python util/lint.py --all — clean
  • Live server on a scratch DB: created a session with an explicit title, drove the retitler across 3 low-overlap turns (LLM stubbed) — pinned title untouched; a default "New Chat" session was auto-titled once and stayed unpinned

Sessions created with an explicit title (POST /api/sessions {"title": ...})
or renamed in the UI were being renamed by the background auto-titler on
essentially every assistant reply — the topic-shift pass replaced any title
with low word-overlap against the new message, with no notion of "the user
chose this name". One session cycled through 3+ names in a single run.

Sessions now persist a title_is_custom flag: set when a title is passed at
creation or via a PUT rename, never set by the auto-titler's own updates.
_should_retitle returns False for pinned sessions; placeholder titles
("New Task" / "New Chat" / empty) still auto-title as before, and
non-pinned auto-generated titles keep the topic-shift pass.

The webui's client-side first-message auto-title sends title_is_custom:
false so it doesn't pin, while the manual rename sends true. An additive
migration adds the column and backfills existing non-placeholder titles
as pinned — wrongly renaming a user's title is worse than never renaming.

Fixes #2165
@github-actions github-actions Bot added the tests Test changes label Jul 17, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Verdict: Approve

This fixes the annoying churn where naming a session (UI rename or API) didn't stick — the background auto-titler kept renaming it on nearly every reply (#2165). The fix adds a persisted title_is_custom pin: explicit titles are never touched, placeholder "New Task"/"New Chat" sessions still get auto-titled on the first message, and an additive migration backfills existing renamed sessions so they stop churning too. The whole path is covered end-to-end — the pure retitle rule, the DB layer, the migration, the REST endpoints, and the webui pin/unpin — and the design deliberately errs toward never clobbering a user's title, which is the right call. Nothing blocking.

🔍 Technical details

Correctness — verified, no issues found:

  • _maybe_update_session_title reads session.get("title_is_custom"), and get_session selects SELECT s.* (database.py:424/448), so the new column actually reaches the pin check — not silently dropped by an explicit column list.
  • All four SessionResponse return paths route through session_to_response (utils.py:132), which reads the flag with a safe .get("title_is_custom", 0) default — no KeyError on rows predating the column.
  • Migration is guarded by a PRAGMA table_info check (database.py:304) so it only ALTERs old DBs; new DBs get the column from CREATE TABLE and skip it. Backfill treats every non-placeholder title as pinned — the documented "wrongly renaming is worse than never renaming" tradeoff.
  • Every title writer is consistent with the pin contract: the backend auto-titler updates title without the flag (_chat_helpers.py:441, stays unpinned), the UI rename sends title_is_custom: true (ChatView.tsx:1327), and the client first-message auto-title sends false (ChatView.tsx:1121).
  • Router opt-out logic (sessions.py:418) correctly distinguishes "PUT with title but no flag → pin" from "explicit flag → honor it" from "no title → leave pin unchanged."

Minor (non-blocking):

  • 🟢 _AUTO_TITLE_DEFAULTS = PLACEHOLDER_TITLES (_chat_helpers.py:222) aliases the same set object rather than copying it. It's read-only today so this is harmless, and centralizing the placeholder list is the right move — just worth being aware that a future in-place mutation of one would silently affect the other. No change needed.

Strengths:

  • Test coverage is genuinely complete for a behavior fix: the pure _should_retitle rule (including the pin-wins-over-placeholder-lookalike case), the DB create/update/preserve paths, a real old-schema migration test with backfill assertions, the REST endpoints, and a webui test asserting rename pins while client auto-title doesn't.
  • Fail-loudly-respecting: no silent fallbacks added; the auto-titler simply returns early on a pinned session.
  • Clean layering — the placeholder set and is_placeholder_title live in database.py and are reused by both the helper and the router, avoiding a duplicated list.

@kovtcharov
kovtcharov enabled auto-merge July 17, 2026 22:44
@kovtcharov
kovtcharov added this pull request to the merge queue Jul 17, 2026
Merged via the queue into main with commit f79c417 Jul 17, 2026
46 checks passed
@kovtcharov
kovtcharov deleted the fix/2165-pin-session-titles branch July 17, 2026 22:49
@itomek itomek mentioned this pull request Jul 22, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants