Skip to content

Support configurable worktree mode — "isolated" vs. "merged" #372

@ovistoica

Description

@ovistoica

Summary

Since 0.110.0, ECA detects Git worktrees and merges them under a single session — a great resource-saving feature for users who want all worktrees sharing one AI context. However, this breaks the workflow of users who work with multiple worktrees in parallel and prefer each worktree to have its own independent ECA session and chat history.

Additionally, the current implementation has a resume reliability problem that affects both modes: because the workspace cache key is computed from the full sorted set of workspace folders at write time (which grows as worktrees are dynamically added), resuming a session from a different workspace combination (e.g., just /main the next day, when yesterday's session had /main + /wt1 + /wt2) fails silently — ECA loads from a different cache file and the previous chats don't appear.


Background

Previously (before 0.110.0), every worktree was treated as a fully independent workspace root, giving each its own session, chat history, and cache file. This worked perfectly for parallel development workflows like:

  • main branch open in one Emacs frame — working on ongoing refactors
  • feature/auth worktree in another frame — building a new feature with its own dedicated chat context

Current Behavior (0.110.0+)

Merged mode (new default):

  1. When you open a file in any worktree of a repo, ECA detects the shared git common-dir (git rev-parse --git-common-dir) and associates it with the existing session created for the main worktree
  2. The new worktree path is added to workspace-folders via workspace/didChangeWorkspaceFolders
  3. All worktrees share a single ECA process and a single chat history

This is efficient and sensible for users who want a unified context, but:

  • eca-chat-toggle-window shows the same "last visited" chat buffer regardless of which worktree you toggle from, breaking the mental model of per-worktree context
  • Resume breaks when the workspace set changes between sessions (see below)

The Resume Problem (affects both modes)

The workspace cache key is a SHA-256 hash of the full sorted set of workspace folder paths at the time the cache is written (~/.cache/eca/<project>_<hash>/db.transit.json).

In merged mode, the workspace set grows dynamically mid-session:

Event workspace-folders Cache key used
Start ECA from /repo/main [main] hash("main") = abc12345
Open file in /repo/wt1 [main, wt1] — (only read at startup)
End of session (cache written) [main, wt1] hash("main:wt1") = xyz99999
Next day: start ECA from /repo/main only [main] hash("main") = abc12345

The cache was written under xyz99999, but ECA reads from abc12345 on the next startup — the previous day's chats are invisible on resume. To recover them you'd need to re-open wt1 first, which is not obvious or reliable.

In isolated mode (the desired behavior for parallel-worktree users), this wouldn't be a problem because each worktree would always have its own single-element workspace set with a stable hash.


Expected / Desired Behavior

Mode 1: Merged Worktree (keep current default)

All worktrees of the same git repo share one ECA session and one chat history. Good for users who want a single unified context. No change needed here beyond the resume fix below.

Mode 2: Isolated Worktree (new option)

Each worktree is treated as a fully independent workspace with:

  • Its own ECA session (separate server process or same process, different session)
  • Its own chat history
  • Its own stable, deterministic cache key (single workspace path → stable hash)
  • eca-chat-toggle-window from a worktree buffer shows that worktree's last chat, not the globally last one

Configurable via something like:

(setq eca-worktree-mode 'isolated)  ;; or 'merged (default)

Or a simpler boolean:

(setq eca-merge-worktrees nil)  ;; default: t

or can be an option config.json since this is more related to the server (I think)

Resume Fix (applies to both modes)

The cache key should be computed from the initial workspace folders sent in the initialize request, not the dynamically grown set at write time. The dynamic folders added mid-session via workspace/didChangeWorkspaceFolders should either:

  • Option A: Be written to a separate cache keyed on the full dynamic set (so next time you open the same combo, it resumes correctly), while also maintaining per-worktree caches
  • Option B: The initial workspace hash always wins for the primary cache file; dynamically added worktrees are considered ephemeral and not counted in the cache key

Either way, opening ECA from a single worktree the next day should always reliably resume that worktree's last session.


Technical Notes (for implementers)

All the worktree merging logic lives in the Emacs client, isolated in two functions in eca-util.el:

  • eca--session-for-worktree — detects a shared git repo and returns an existing session
  • eca--session-add-workspace-folder — merges the new worktree path into the session and notifies the server

Both are called only from eca-session (in the fallback branch). Implementing isolated mode is straightforward:

  • When eca-worktree-mode is 'isolated, eca--session-for-worktree returns nil, forcing eca-session to create a new session per worktree (the first branch of eca-session only matches exact workspace-folders equality)
  • Alternatively, a custom eca-find-root-for-buffer-function that returns the actual worktree directory would bypass detection entirely

The server-side cache key is in cache.cljworkspaces-hash. The fix for resume stability would involve snapshotting the initial workspace set at initialize time and using that as the canonical read/write key, while optionally also writing to the dynamic-set key when new worktrees are added.


User Impact

  • Parallel worktree workflows (e.g. hotfix branch while feature branch is in progress): completely broken UX in merged mode — toggle-window shows wrong chat, chats bleed across concerns
  • Daily resume: fragile even in merged mode — if you don't open the exact same worktree combination that was active when you last saved, your chat history appears missing

Proposed Config

;; In user's init.el:
(setq eca-worktree-mode 'isolated)
;; Possible values:
;;   'merged   — all worktrees of the same repo share one session (default, current behavior)
;;   'isolated — each worktree gets its own independent session and chat history

Steps to Reproduce the Resume Problem

  1. Open Emacs with three worktrees: /repo/main, /repo/wt1, /repo/wt2
  2. Start ECA from /repo/main (session starts with workspace-folders = [main])
  3. Open a file in /repo/wt1 — ECA merges it: workspace-folders = [main, wt1]
  4. Open a file in /repo/wt2 — ECA merges it: workspace-folders = [main, wt1, wt2]
  5. Have a chat session, end the day
  6. Next morning: open Emacs with only /repo/main
  7. Start ECA — workspace-folders = [main]
  8. Open ECA chat, click "Resume" — previous chats are not listed
  9. Open a file in /repo/wt1 and /repo/wt2 to force the same merge
  10. The chats reappear — confirming the cache key mismatch

Happy to elaborate further or test any proposed implementation!

To upvote this issue, give it a thumbs up. See this list for the most upvoted issues.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions