-
-
Notifications
You must be signed in to change notification settings - Fork 52
# Bug/UX: Custom chat names are not persisted to the server and are lost on /resume #388
Description
Summary
When a user renames a chat with eca-chat-rename (C-c C-S-r), the new name is
stored only in the Emacs buffer as a client-side buffer-local variable. It is
never sent to the server. As a result:
/resumelists chats by the server's auto-generated title (e.g.
"Fixing eca API key config"), ignoring any user-assigned name- After
/resumeopens the chat in a new buffer, the custom name is gone entirely - The server's remote API (
GET /chats) also returns only the original
auto-generated title
Expected behavior: Renaming a chat should persist the new name so that
/resume (and any other chat listing) reflects it.
Steps to Reproduce
- Start an ECA session and send a prompt — the chat gets an auto-generated title
(e.g. "Debugging API endpoint") - Rename the chat:
C-c C-S-r→ type "My important investigation" →RET - Observe: the tab, header line, and workspaces tree all show the new name ✅
- Kill the ECA session or start a new one
- In the new session, type
/resumeto list available chats - Observe: the chat appears as "Debugging API endpoint" — the custom name is
completely absent ❌ - Run
/resume Nto resume that chat - Observe: the opened buffer has no custom title — the rename is permanently
lost ❌
Root Cause Analysis
The rename is purely client-side and ephemeral
eca-chat-rename only writes to a buffer-local variable:
;; eca-chat.el
(defun eca-chat-rename ()
"Rename last visited chat to a custom NEW-NAME."
(interactive)
(let ((new-name (read-string "Inform the new chat title: ")))
(eca-assert-session-running (eca-session))
(with-current-buffer (eca-chat--get-last-buffer (eca-session))
(setq eca-chat--custom-title new-name)))) ; ← buffer-local only, no RPC callThere is no chat/rename RPC method, no chat/setTitle notification, nothing.
The server is never informed of the new name.
The server has a single title field, set only once
The server DB schema (db.clj) has exactly one field for the chat name:
:chats {"<chat-id>" {:id :string
:title (or :string nil) ; the only title field
...}}This field is populated once — asynchronously on the first prompt, via a background
LLM call using the chatTitle prompt. After that it is never updated by any
user-facing mechanism.
/resume reads :title from the server DB
The /resume command in commands.clj builds its chat listing using only
(:title chat):
;; features/commands.clj — /resume listing
(format "%s - %s - %s\n"
(inc (.indexOf ^PersistentVector chats-ids chat-id))
(shared/ms->presentable-date (:created-at chat) "dd/MM/yyyy HH:mm")
(or (:title chat) ; ← server :title only
(format "No chat title (%s user messages)" msgs-count)))It has no knowledge of eca-chat--custom-title.
The display priority on the client hides the problem
eca-chat-title correctly prioritises the custom name over the server name:
;; eca-chat.el
(defun eca-chat-title ()
(cond
(eca-chat--custom-title ...) ; custom wins if set
(eca-chat--title ...) ; server title fallback
(t "Empty chat")))This makes the rename appear to work perfectly within the active buffer session.
The illusion breaks the moment the buffer is killed — because eca-chat--custom-title
dies with it.
Full disconnect diagram
User: C-c C-S-r → "My important investigation"
│
▼
eca-chat--custom-title = "My important investigation" (buffer-local)
[NO server call, NO RPC, NO persistence]
│
▼
Tab / header / tree show "My important investigation" ✅ (looks correct)
[Session ends / buffer killed]
│
▼
eca-chat--custom-title = nil (gone forever)
Server DB: :title = "Fixing eca API key config" (unchanged)
User: /resume
│
▼
Server sends: "3 - 30/03/2026 09:25 - Fixing eca API key config" ❌
Proposed Fix
Option A — Persist the custom title server-side (recommended)
Add a chat/rename (or chat/setTitle) RPC method on the server that updates
[:chats chat-id :title] and flushes the cache.
Server (handlers.clj):
(defn chat-rename [{:keys [db* metrics]} {:keys [chat-id title]}]
(when (get-in @db* [:chats chat-id])
(swap! db* assoc-in [:chats chat-id :title] title)
(db/update-workspaces-cache! @db* metrics)
{}))Server (server.clj):
"chat/rename" (handlers/chat-rename components params)Client (eca-chat.el) — eca-chat-rename:
(defun eca-chat-rename ()
"Rename the current chat, persisting the new name to the server."
(interactive)
(let ((new-name (read-string "New chat title: ")))
(eca-assert-session-running (eca-session))
(with-current-buffer (eca-chat--get-last-buffer (eca-session))
;; Update locally for immediate display
(setq eca-chat--custom-title new-name)
;; Persist to server so /resume and other sessions see it
(eca-api-notify (eca-session)
:method "chat/rename"
:params (list :chatId eca-chat--id
:title new-name)))))Client — on chat/contentReceived "metadata" frame:
Since lifecycle/send-content! already sends {:type :metadata :title ...} to the
client after the server writes the title, the client could also update
eca-chat--title (not eca-chat--custom-title) to keep the server and client
display names in sync:
("metadata"
(unless parent-tool-call-id
(setq-local eca-chat--title (plist-get content :title))))This is already done — it sets eca-chat--title. With Option A the rename would
update :title on the server, which would then push a "metadata" frame back,
which would set eca-chat--title on the client. At that point
eca-chat--custom-title could be cleared (the server is now the source of truth):
("metadata"
(unless parent-tool-call-id
(let ((title (plist-get content :title)))
(setq-local eca-chat--title title)
;; If server confirmed our rename, the custom title is now redundant
(when (string= title eca-chat--custom-title)
(setq-local eca-chat--custom-title nil)))))This is optional — keeping eca-chat--custom-title as a local display override
is also fine.
Option B — Two-field schema: auto-title + user-title (alternative)
Keep the LLM-generated title in :title (untouched) and add a separate
:custom-title field that the rename operation writes:
;; db.clj
:chats {"<chat-id>" {:title (or :string nil) ; LLM-generated
:custom-title (or :string nil) ; user-assigned
...}}/resume and the remote API would then display (or :custom-title :title).
Advantage: The original auto-generated title is never destroyed, which can be
useful for debugging or if the user wants to revert.
Disadvantage: More schema surface and slightly more logic everywhere a title is
read.
Impact
| Feature | Current behaviour | With fix |
|---|---|---|
/resume listing |
Shows LLM-generated title | Shows user-assigned name |
After /resume, new buffer |
eca-chat--custom-title is nil (name lost) |
Server sends correct title via "metadata" frame |
Remote HTTP API (GET /chats) |
Returns LLM-generated title | Returns user-assigned name |
| Tab-line / header in active buffer | Shows custom name correctly ✅ | No change |
Server cache (db.transit.json) |
Only :title saved |
:title updated by rename and saved |
| Name survives ECA server restart | ❌ No (custom title is gone) | ✅ Yes (written to disk cache) |
Files to Change
Server
| File | Change |
|---|---|
src/eca/handlers.clj |
Add chat-rename handler |
src/eca/server.clj |
Register "chat/rename" in the dispatch table |
src/eca/db.clj |
Optional: add :custom-title field to schema doc (Option B only) |
Client (eca-emacs)
| File | Change |
|---|---|
eca-chat.el eca-chat-rename |
Add eca-api-notify call after setting eca-chat--custom-title |
That is the minimal fix — two changed files, one new server handler, one added
eca-api-notify call in the client.
Classification
This is could be either a bug or ux improvement in the rename feature: eca-chat-rename presents itself as
persisting a name change (C-c C-S-r with an explicit "Inform the new chat title"
prompt) but silently discards the change the moment the buffer is killed. A user
who renames a chat has a reasonable expectation that the name will be remembered.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status