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:
/resume lists chats by the server's auto-generated title (e.g.
"Fixing eca API key config"), ignoring any user-assigned name
- After
/resume opens 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
/resume to list available chats
- Observe: the chat appears as "Debugging API endpoint" — the custom name is
completely absent ❌
- Run
/resume N to 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 call
There 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.
Summary
When a user renames a chat with
eca-chat-rename(C-c C-S-r), the new name isstored 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
/resumeopens the chat in a new buffer, the custom name is gone entirelyGET /chats) also returns only the originalauto-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
(e.g. "Debugging API endpoint")
C-c C-S-r→ type "My important investigation" →RET/resumeto list available chatscompletely absent ❌
/resume Nto resume that chatlost ❌
Root Cause Analysis
The rename is purely client-side and ephemeral
eca-chat-renameonly writes to a buffer-local variable:There is no
chat/renameRPC method, nochat/setTitlenotification, 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
chatTitleprompt. After that it is never updated by anyuser-facing mechanism.
/resumereads:titlefrom the server DBThe
/resumecommand incommands.cljbuilds its chat listing using only(:title chat):It has no knowledge of
eca-chat--custom-title.The display priority on the client hides the problem
eca-chat-titlecorrectly prioritises the custom name over the server name: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-titledies with it.
Full disconnect diagram
Proposed Fix
Option A — Persist the custom title server-side (recommended)
Add a
chat/rename(orchat/setTitle) RPC method on the server that updates[:chats chat-id :title]and flushes the cache.Server (
handlers.clj):Server (
server.clj):Client (
eca-chat.el) —eca-chat-rename:Client — on
chat/contentReceived"metadata"frame:Since
lifecycle/send-content!already sends{:type :metadata :title ...}to theclient after the server writes the title, the client could also update
eca-chat--title(noteca-chat--custom-title) to keep the server and clientdisplay names in sync:
This is already done — it sets
eca-chat--title. With Option A the rename wouldupdate
:titleon the server, which would then push a"metadata"frame back,which would set
eca-chat--titleon the client. At that pointeca-chat--custom-titlecould be cleared (the server is now the source of truth):This is optional — keeping
eca-chat--custom-titleas a local display overrideis 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-titlefield that the rename operation writes:/resumeand 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
/resumelisting/resume, new buffereca-chat--custom-titleis nil (name lost)"metadata"frameGET /chats)db.transit.json):titlesaved:titleupdated by rename and savedFiles to Change
Server
src/eca/handlers.cljchat-renamehandlersrc/eca/server.clj"chat/rename"in the dispatch tablesrc/eca/db.clj:custom-titlefield to schema doc (Option B only)Client (eca-emacs)
eca-chat.eleca-chat-renameeca-api-notifycall after settingeca-chat--custom-titleThat is the minimal fix — two changed files, one new server handler, one added
eca-api-notifycall in the client.Classification
This is could be either a bug or ux improvement in the rename feature:
eca-chat-renamepresents itself aspersisting a name change (
C-c C-S-rwith 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.