fix(desktop): persist /title set before the first message instead of queuing - #47987
Merged
Merged
Conversation
…queuing A /title typed before any message in a fresh desktop chat could be silently lost: the session DB row is deferred to the first prompt, so session.title found no row, only stashed pending_title, and returned pending:true. It then relied on a post-turn apply block to write the title. When that turn never landed under the same session_key (or the apply path didn't fire), the title was dropped and the sidebar fell back to the first-message preview — e.g. "/title my-custom-name" then "hello" left the session titled "hello". Mirror the messaging gateway's _handle_title_command: an explicit /title is clear user intent, not an abandoned draft, so create the row up front (_ensure_session_db_row) and set the title immediately via the profile-aware _session_db handle, returning pending:false. This also fixes the frontend symptom for free — the desktop handler's immediate refreshSessions() now pulls the correct persisted title instead of clobbering the optimistic value with a still-NULL row. If row creation can't take (DB unavailable / racing writer), fall back to the existing pending_title queue so the post-turn apply block remains a recovery path. The sidebar's min-messages filter keeps a titled 0-message row hidden, so a /title'd-but-never-used draft still doesn't clutter the list. Updates the test that asserted the old queue-on-missing-row behavior and adds a fallback-to-queue regression test.
Contributor
🔎 Lint report:
|
| Rule | Count |
|---|---|
invalid-assignment |
2 |
First entries
tests/test_tui_gateway_server.py:1797: [invalid-assignment] invalid-assignment: Invalid subscript assignment with key of type `Literal["row"]` and value of type `dict[str, str | None]` on object of type `dict[str, None | bool]`
tests/run_agent/test_credits_notices_toggle.py:76: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to attribute `_credits_session_start_micros` of type `int`
✅ Fixed issues (2):
| Rule | Count |
|---|---|
unresolved-attribute |
2 |
First entries
tests/run_agent/test_credits_notices_toggle.py:76: [unresolved-attribute] unresolved-attribute: Unresolved attribute `_credits_session_start_micros` on type `AIAgent`
run_agent.py:2941: [unresolved-attribute] unresolved-attribute: Object of type `Self@get_credits_spent_micros` has no attribute `_credits_session_start_micros`
Unchanged: 5790 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
OutThisLife
approved these changes
Jun 17, 2026
OutThisLife
left a comment
Collaborator
There was a problem hiding this comment.
Approving. Verified against current main:
- The desktop
/title-before-first-message path previously only stashedpending_titleand bet on the post-turn apply block — exactly the race the writeup describes (the handler returnspending:trueon rowcount 0 when no row exists yet). - The fix correctly mirrors the messaging gateway's
_handle_title_command:_ensure_session_db_row(session)(INSERT OR IGNORE) + setting the title through the profile-aware_session_dbhandle. Routing through_session_dbinstead of the bare_get_db()is also strictly more correct for remote/profile sessions, whose row lives in that profile's ownstate.dbrather than the launch profile's. - The fallback to
pending_titleon a failed persist preserves the post-turn apply recovery path. - The
min_messages >= 1sidebar filter keeps a titled 0-message draft hidden, so this doesn't reintroduce empty-draft clutter. - Tests swap the buggy queue assertion for behavior assertions (row created, title set,
pending:false,pending_titlecleared) and add the fallback-to-queue case. CI green.
LGTM.
waefrebeorn
pushed a commit
to waefrebeorn/slermes
that referenced
this pull request
Jul 2, 2026
…queuing (NousResearch#47987) A /title typed before any message in a fresh desktop chat could be silently lost: the session DB row is deferred to the first prompt, so session.title found no row, only stashed pending_title, and returned pending:true. It then relied on a post-turn apply block to write the title. When that turn never landed under the same session_key (or the apply path didn't fire), the title was dropped and the sidebar fell back to the first-message preview — e.g. "/title my-custom-name" then "hello" left the session titled "hello". Mirror the messaging gateway's _handle_title_command: an explicit /title is clear user intent, not an abandoned draft, so create the row up front (_ensure_session_db_row) and set the title immediately via the profile-aware _session_db handle, returning pending:false. This also fixes the frontend symptom for free — the desktop handler's immediate refreshSessions() now pulls the correct persisted title instead of clobbering the optimistic value with a still-NULL row. If row creation can't take (DB unavailable / racing writer), fall back to the existing pending_title queue so the post-turn apply block remains a recovery path. The sidebar's min-messages filter keeps a titled 0-message row hidden, so a /title'd-but-never-used draft still doesn't clutter the list. Updates the test that asserted the old queue-on-missing-row behavior and adds a fallback-to-queue regression test. Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
habarmc1223-sudo
pushed a commit
to habarmc1223-sudo/hermes-agent-fluxmem
that referenced
this pull request
Jul 8, 2026
…queuing (NousResearch#47987) A /title typed before any message in a fresh desktop chat could be silently lost: the session DB row is deferred to the first prompt, so session.title found no row, only stashed pending_title, and returned pending:true. It then relied on a post-turn apply block to write the title. When that turn never landed under the same session_key (or the apply path didn't fire), the title was dropped and the sidebar fell back to the first-message preview — e.g. "/title my-custom-name" then "hello" left the session titled "hello". Mirror the messaging gateway's _handle_title_command: an explicit /title is clear user intent, not an abandoned draft, so create the row up front (_ensure_session_db_row) and set the title immediately via the profile-aware _session_db handle, returning pending:false. This also fixes the frontend symptom for free — the desktop handler's immediate refreshSessions() now pulls the correct persisted title instead of clobbering the optimistic value with a still-NULL row. If row creation can't take (DB unavailable / racing writer), fall back to the existing pending_title queue so the post-turn apply block remains a recovery path. The sidebar's min-messages filter keeps a titled 0-message row hidden, so a /title'd-but-never-used draft still doesn't clutter the list. Updates the test that asserted the old queue-on-missing-row behavior and adds a fallback-to-queue regression test. Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
santhreal
pushed a commit
to santhreal/hermes-agent
that referenced
this pull request
Jul 13, 2026
…queuing (NousResearch#47987) A /title typed before any message in a fresh desktop chat could be silently lost: the session DB row is deferred to the first prompt, so session.title found no row, only stashed pending_title, and returned pending:true. It then relied on a post-turn apply block to write the title. When that turn never landed under the same session_key (or the apply path didn't fire), the title was dropped and the sidebar fell back to the first-message preview — e.g. "/title my-custom-name" then "hello" left the session titled "hello". Mirror the messaging gateway's _handle_title_command: an explicit /title is clear user intent, not an abandoned draft, so create the row up front (_ensure_session_db_row) and set the title immediately via the profile-aware _session_db handle, returning pending:false. This also fixes the frontend symptom for free — the desktop handler's immediate refreshSessions() now pulls the correct persisted title instead of clobbering the optimistic value with a still-NULL row. If row creation can't take (DB unavailable / racing writer), fall back to the existing pending_title queue so the post-turn apply block remains a recovery path. The sidebar's min-messages filter keeps a titled 0-message row hidden, so a /title'd-but-never-used draft still doesn't clutter the list. Updates the test that asserted the old queue-on-missing-row behavior and adds a fallback-to-queue regression test. Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
Gravezzz
pushed a commit
to Gravezzz/hermes-agent
that referenced
this pull request
Jul 21, 2026
…queuing (NousResearch#47987) A /title typed before any message in a fresh desktop chat could be silently lost: the session DB row is deferred to the first prompt, so session.title found no row, only stashed pending_title, and returned pending:true. It then relied on a post-turn apply block to write the title. When that turn never landed under the same session_key (or the apply path didn't fire), the title was dropped and the sidebar fell back to the first-message preview — e.g. "/title my-custom-name" then "hello" left the session titled "hello". Mirror the messaging gateway's _handle_title_command: an explicit /title is clear user intent, not an abandoned draft, so create the row up front (_ensure_session_db_row) and set the title immediately via the profile-aware _session_db handle, returning pending:false. This also fixes the frontend symptom for free — the desktop handler's immediate refreshSessions() now pulls the correct persisted title instead of clobbering the optimistic value with a still-NULL row. If row creation can't take (DB unavailable / racing writer), fall back to the existing pending_title queue so the post-turn apply block remains a recovery path. The sidebar's min-messages filter keeps a titled 0-message row hidden, so a /title'd-but-never-used draft still doesn't clutter the list. Updates the test that asserted the old queue-on-missing-row behavior and adds a fallback-to-queue regression test. Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.com>
Merged
13 tasks
leewenjie
pushed a commit
to leewenjie/hermes-agent
that referenced
this pull request
Aug 7, 2026
…queuing (NousResearch#47987) A /title typed before any message in a fresh desktop chat could be silently lost: the session DB row is deferred to the first prompt, so session.title found no row, only stashed pending_title, and returned pending:true. It then relied on a post-turn apply block to write the title. When that turn never landed under the same session_key (or the apply path didn't fire), the title was dropped and the sidebar fell back to the first-message preview — e.g. "/title my-custom-name" then "hello" left the session titled "hello". Mirror the messaging gateway's _handle_title_command: an explicit /title is clear user intent, not an abandoned draft, so create the row up front (_ensure_session_db_row) and set the title immediately via the profile-aware _session_db handle, returning pending:false. This also fixes the frontend symptom for free — the desktop handler's immediate refreshSessions() now pulls the correct persisted title instead of clobbering the optimistic value with a still-NULL row. If row creation can't take (DB unavailable / racing writer), fall back to the existing pending_title queue so the post-turn apply block remains a recovery path. The sidebar's min-messages filter keeps a titled 0-message row hidden, so a /title'd-but-never-used draft still doesn't clutter the list. Updates the test that asserted the old queue-on-missing-row behavior and adds a fallback-to-queue regression test. Co-authored-by: Teknium <127238744+teknium1@users.noreply.github.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.
Symptom
In a fresh desktop chat, typing
/title my-custom-nameas the first action, then sending a message (e.g.hello), leaves the session titledhello(the first-message preview) — the manual title is silently lost. The status line even confirms it queued: "Session title set: my-custom-name (queued while session initializes)".Root cause
The desktop/TUI defers the session's SQLite row to the first prompt (so abandoned empty drafts don't litter the sidebar). When
/titleruns before any message,session.title(tui_gateway/server.py) finds no row, soset_session_titlereturns rowcount 0. The handler then only stashedpending_titleand returnedpending: true, betting that the post-turn apply block would write the title later.That deferral is fragile: when the first turn doesn't land/persist under the same
session_key(or the apply path doesn't fire), the queued title is dropped and the row'stitlestays NULL. The sidebar'ssessionTitle()then falls back totitle || preview || 'Untitled'→ the message previewhello.The messaging gateway's
_handle_title_commandnever had this bug — it creates the row immediately and sets the title directly.Fix
Make the desktop path match the messaging gateway: an explicit
/titleis clear user intent, not an abandoned draft, so persist the row up front (_ensure_session_db_row) and set the title immediately through the profile-aware_session_dbhandle, returningpending: false.This also fixes the frontend symptom for free. The desktop
/titlehandler does an optimisticsetSessions(...)then an immediaterefreshSessions(); whilepending:truethat pull hit the still-NULL row and clobbered the optimistic title back to the preview. With the row now persisted synchronously, that same refresh pulls the correct title.If row creation can't take (DB unavailable / racing writer), it falls back to the existing
pending_titlequeue, so the post-turn apply block remains a recovery path. The sidebar'smin_messages >= 1filter keeps a titled 0-message row hidden, so a/title'd-but-never-used draft still doesn't clutter the list.Testing
test_session_title_queues_when_db_row_not_ready(asserted the buggy queue behavior) withtest_session_title_creates_row_and_sets_immediately_when_not_ready, which asserts the row is created up front, the title is set,pending:false, andpending_titleis cleared.test_session_title_falls_back_to_queue_when_row_create_failsto lock in the recovery path.HERMES_HOME+ realSessionDB:/titlebefore any row now creates the row and sets the title (pending:false).pytest tests/test_tui_gateway_server.py -k "title or pending or auto_title or prompt_submit"→ all pass.