-
Notifications
You must be signed in to change notification settings - Fork 127
Description
Summary
After CCB restarts, ensure_pane() in gaskd_session.py can route messages to the wrong tmux pane because it trusts cached pane_id values without verifying ownership.
Root Cause
ensure_pane() L85-93 has a fast path:
pane_id = self.pane_id
if pane_id and backend.is_alive(pane_id):
self._attach_pane_log(backend, pane_id)
return True, pane_id # ← returns immediately, no ownership checkThis skips pane_title_marker verification entirely. When tmux recycles a pane ID (e.g., %20) to a different process (like Claude itself), is_alive() returns True, and messages are delivered to the wrong target.
Three-layer failure chain
- Stale pane_id: CCB restart creates new panes with new IDs, but
.gemini-session/.codex-sessionstill cache oldpane_id - title_marker bypass: The fast path at L91 returns before reaching the
find_pane_by_title_markerfallback at L95-104 - tmux ID recycling: Old pane IDs get reassigned to other processes.
is_alive()returnsTruebecause the pane exists — just not for the intended provider
Observed Behavior
ask gemini "..."silently delivered messages to Claude's own pane- Gemini never received the message, no response came back
- User had to manually discover the mismatch via
tmux list-panesand update session files - This happened repeatedly during a full work session, severely disrupting productivity
Real-world pane state example
# Session file says pane_id=%20 for Gemini
# But after CCB restart:
%20 ◇ Ready (BitXiongServer) ← Claude's pane (recycled ID)
%56 ◇ Ready (BitXiongServer) ← actual Gemini pane (new ID)
%57 CCB-Codex ← Codex pane (title marker present)
Suggested Fix
Add title marker verification to the fast path. When pane_id is alive but find_pane_by_title_marker resolves to a different pane, update the cached pane_id:
pane_id = self.pane_id
marker = self.pane_title_marker
resolver = getattr(backend, "find_pane_by_title_marker", None)
if pane_id and backend.is_alive(pane_id):
if marker and callable(resolver):
resolved = resolver(marker)
if resolved and str(resolved) != str(pane_id):
# Stale: pane alive but belongs to another process
if backend.is_alive(str(resolved)):
self.data["pane_id"] = str(resolved)
self.data["updated_at"] = _now_str()
self._write_back()
self._attach_pane_log(backend, str(resolved))
return True, str(resolved)
else:
self._attach_pane_log(backend, pane_id)
return True, pane_id
else:
self._attach_pane_log(backend, pane_id)
return True, pane_idAdditional Note
Gemini's pane title often shows "Ready (ProjectName)" instead of "CCB-Gemini", which also causes find_pane_by_title_marker to fail as a fallback. This is a secondary issue — the Gemini CLI overrides the title set by CCB's set_pane_title().
Environment
- CCB version: v5.2.4 (ce20d5c)
- Platform: macOS (Darwin 25.3.0)
- Terminal: tmux 3.x
- Providers affected: Gemini (primarily), potentially Codex