Skip to content

fix(gemini): sync TUI↔chat and restore session titles#194

Open
bbsngg wants to merge 1 commit intomainfrom
fix/gemini-session-sync-and-titles
Open

fix(gemini): sync TUI↔chat and restore session titles#194
bbsngg wants to merge 1 commit intomainfrom
fix/gemini-session-sync-and-titles

Conversation

@bbsngg
Copy link
Copy Markdown
Contributor

@bbsngg bbsngg commented Apr 17, 2026

Summary

Fixes two user-reported Gemini session bugs and adds a shell-side advisory banner for a Gemini CLI limitation.

Bug #1 — Gemini sessions always showed "Untitled Session" in the sidebar, even after the user sent their first message. Three-pronged fix:

  • server/gemini-cli.js: persistGeminiSessionMetadata now writes null instead of the hardcoded placeholder, so upsertSessionFromSource's incoming || existing pattern stops clobbering real titles.
  • server/projects.js (buildGeminiSessionsIndex): treat the legacy "Untitled Session" placeholder as empty so the existing firstMessageText fallback can run for rows seeded by older builds.
  • Same two-line fix applied to server/nano-claude-code.js for its "Nano Claude Code Session" placeholder.

Bug #2 — Shell↔chat desync on the same session. Root causes turned out to span three layers:

  1. gemini --resume accepts "latest" or a numeric index from --list-sessions, not the UUID. server/index.js now resolves UUID → index via execFile('gemini', ['--list-sessions']) before spawning the shell.
  2. useProjectsState.ts had a changedFileParts.length >= 2 gate that excluded Gemini's single-segment path ({uuid}.jsonl), blocking chat refetch on file changes. Relaxed to use the basename directly.
  3. Gemini TUI writes live conversation state to ~/.gemini/tmp/{projectHash}/chats/session-*.json on every turn but only flushes ~/.gemini/sessions/{uuid}.jsonl periodically. The chat page was only reading the committed jsonl, so TUI activity was invisible for minutes. Now:
    • New helpers in server/projects.js (readGeminiTmpChatSessionId, findGeminiTmpChatFile, convertGeminiTmpChatsToMessages) ingest the tmp/chats blob and map its {type: "user"|"gemini", content, toolCalls} shape into the downstream message format (including tool_use/tool_result entries).
    • getSessionMessages prefers tmp/chats when its mtime ≥ the jsonl's.
    • A new gemini-tmp chokidar watcher on ~/.gemini/tmp resolves change events back to the session UUID and synthesizes a {uuid}.jsonl broadcast so the frontend's existing refetch gate fires without any client-side changes. The identical-snapshot dedup shortcut is bypassed for these events since tmp/chats churn doesn't change the project index.

Advisory banner. The reverse direction (chat → TUI) is inherently one-way: the running Gemini TUI keeps conversation state in memory and never re-reads its session file. src/components/Shell.jsx now writes a yellow \x1b[33m banner when resuming a Gemini session: "Gemini TUI won't see messages sent from the chat page until you exit and re-resume this session".

Test plan

  • Start a fresh Gemini session via chat; send a first message. Verify the sidebar title is derived from the message, not "Untitled Session". Confirm sqlite3 ~/.dr-claw/auth.db "SELECT id, display_name FROM session_metadata WHERE provider='gemini';" shows NULL for new rows.
  • Open a Gemini session's shell tab; verify the yellow banner appears and that gemini --resume {index} actually attaches to the same session (no new session created).
  • Type a new turn in the TUI; within ~1s the chat page should show the new user+assistant messages, including any tool calls Gemini made.
  • Type a new turn in chat; exit the TUI (Ctrl-C) and re-resume from the shell — the chat-sent messages should now appear in the TUI.
  • Sessions with no tmp/chats file (older sessions, restored from jsonl only) still render correctly from the committed jsonl.
  • Nano Claude Code sessions: verify new sessions no longer show "Nano Claude Code Session" as the title.

🤖 Generated with Claude Code

- persistGeminiSessionMetadata now writes null instead of the "Untitled
  Session" placeholder; buildGeminiSessionsIndex treats legacy rows with
  that placeholder as empty so firstMessageText fallback can run.
- Same placeholder fix for nano-claude-code's "Nano Claude Code Session".
- Resolve Gemini session UUID → --list-sessions index before passing to
  `gemini --resume` (the CLI doesn't accept UUIDs).
- Ingest ~/.gemini/tmp/{hash}/chats/session-*.json (live TUI state);
  getSessionMessages prefers this blob when newer than the committed
  jsonl so chat mirrors what the TUI is displaying.
- New gemini-tmp chokidar watcher resolves tmp/chats changes back to a
  session UUID and bypasses the identical-snapshot shortcut so the chat
  page's existing {uuid}.jsonl refetch gate still fires.
- Relax the useProjectsState gate that required a 2-part changedFile
  path, unblocking Gemini refetch triggers.
- Shell renders a yellow advisory banner when resuming a Gemini session,
  since the TUI can't ingest chat-originated writes without re-resume.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread server/projects.js
}
if (Array.isArray(m.toolCalls)) {
for (const tc of m.toolCalls) {
let toolInput = '{}';
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant