Release v0.51.276 — Release IR (stage-p3e — preserve manually-named session titles #3542) (#3682)

* feat(sessions): skip adaptive auto-rename for manually-named sessions (#3542, #3230)

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>

* docs(changelog): v0.51.276 — Release IR (stage-p3e)

* fix(sessions): clear manual_title lock on /api/session/clear (#3542)

Codex regression-gate follow-up: the clear endpoint reset the title to
Untitled directly, stranding manual_title=True so the reused session never
auto-named again. Route the reset through apply_session_title_rename (which
clears the lock for auto-labels) + add a behavioral and a static-guard test.

---------

Co-authored-by: nesquena-hermes <[email protected]>
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-06-05 13:34:54 -07:00
1 parent 165454dd7b
commit 87084dfebf
6 files changed
+297 -8

No files matched your search

+14 -4
View File
@@ -51,6 +51,7 @@ from api.models import (
get_state_db_session_messages,
reconciled_state_db_messages_for_session,
)
from api.session_ops import mark_session_title_generated, session_has_manual_title
# Global lock for os.environ writes. Per-session locks (_agent_lock) prevent
# concurrent runs of the SAME session, but two DIFFERENT sessions can still
@@ -2361,6 +2362,9 @@ def _run_background_title_update(session_id: str, user_text: str, assistant_text
_put_title_status(put_event, session_id, 'skipped', 'already_generated', str(s.title or ''))
return
current = str(s.title or '').strip()
if session_has_manual_title(s):
_put_title_status(put_event, session_id, 'skipped', 'manual_title', current)
return
still_auto = (
current == placeholder_title
or current in ('Untitled', 'New Chat', '')
@@ -2405,6 +2409,7 @@ def _run_background_title_update(session_id: str, user_text: str, assistant_text
if cached_session is not None and getattr(cached_session, 'session_id', None) == session_id:
s = cached_session
effective_title = str(s.title or '').strip()
manual_title = session_has_manual_title(s)
invalid_existing_now = _looks_invalid_generated_title(s.title)
still_auto = (
effective_title == placeholder_title
@@ -2412,12 +2417,12 @@ def _run_background_title_update(session_id: str, user_text: str, assistant_text
or _is_provisional_title(effective_title, s.messages)
or invalid_existing_now
)
if not still_auto:
if manual_title or not still_auto:
_put_title_status(put_event, session_id, 'skipped', 'manual_title', effective_title)
return
if next_title != effective_title:
s.title = next_title
s.llm_title_generated = True
mark_session_title_generated(s)
# Keep chronological ordering stable in the sidebar.
s.save(touch_updated_at=False)
effective_title = s.title
@@ -2450,6 +2455,9 @@ def _run_background_title_refresh(session_id: str, user_text: str, assistant_tex
return
# Safety: skip if user manually renamed since the check
effective = str(s.title or '').strip()
if session_has_manual_title(s):
_put_title_status(put_event, session_id, 'skipped', 'manual_title', effective)
return
if effective != current_title:
_put_title_status(put_event, session_id, 'skipped', 'manual_title', effective)
return
@@ -2482,11 +2490,11 @@ def _run_background_title_refresh(session_id: str, user_text: str, assistant_tex
if cached_session is not None and getattr(cached_session, 'session_id', None) == session_id:
s = cached_session
# Re-check: user may have renamed while we were generating
if str(s.title or '').strip() != current_title:
if session_has_manual_title(s) or str(s.title or '').strip() != current_title:
_put_title_status(put_event, session_id, 'skipped', 'manual_title', str(s.title or '').strip())
return
s.title = next_title
s.llm_title_generated = True
mark_session_title_generated(s)
effective_title = s.title
# Session.save() calls _write_session_index(), which acquires LOCK.
# Keep the per-session agent lock for mutation serialization, but
@@ -2631,6 +2639,8 @@ def _maybe_schedule_title_refresh(session, put_event, agent):
current_title = str(session.title or '').strip()
if not current_title or current_title in ('Untitled', 'New Chat'):
return
if session_has_manual_title(session):
return
if not getattr(session, 'llm_title_generated', False):
return
exchange_count = _count_exchanges(session.messages)