Skip to content

fix: thread caller pane ID for cross-instance session isolation#151

Merged
bfly123 merged 3 commits intobfly123:mainfrom
patrickrho-patty:fix/caller-pane-routing-isolation
Mar 30, 2026
Merged

fix: thread caller pane ID for cross-instance session isolation#151
bfly123 merged 3 commits intobfly123:mainfrom
patrickrho-patty:fix/caller-pane-routing-isolation

Conversation

@patrickrho-patty
Copy link
Copy Markdown
Contributor

Problem

When multiple Claude Code instances run in the same project directory and both
use ask codex (or any provider), completion hook responses are routed to the
wrong instance. This happens because all instances share a single session
file (e.g., .claude-session), which contains one pane_id. Whichever instance
writes the session file last "wins" — all responses go to that pane, regardless
of which instance originated the request.

Additionally, the send-key Enter approach from #149 does not work on older
WezTerm versions (e.g., 20240203) that lack the send-key subcommand, causing
responses to paste into the input buffer but never auto-submit.

Environment:

  • macOS (Darwin 25.3.0)
  • WezTerm 20240203-110809-5046fc22
  • Two Claude Code instances open in the same project directory
  • Both using ask codex concurrently

Root Cause

  1. Session file collision: The completion hook resolves the caller's pane by
    reading .claude-sessionpane_id. With multiple instances, this file is
    a shared mutable resource with last-writer-wins semantics.

  2. Missing send-key: WezTerm versions before ~2024.06 don't have
    wezterm cli send-key. The hook tries 4 variants, all fail silently, and the
    \r byte fallback doesn't trigger submission in raw-mode TUIs.

Fix

Pane ID threading (cross-instance isolation)

Capture the caller's terminal pane ID (WEZTERM_PANE or TMUX_PANE) at
ask-time and thread it through the entire request chain:

ask (capture) → daemon RPC → ProviderRequest → adapter →
notify_completion → completion hook env → direct terminal send

When CCB_CALLER_PANE_ID is set, the completion hook sends directly to that
pane, bypassing the session file lookup entirely. When unset (older ask
clients), the existing session-file-based routing is preserved as fallback.

Auto-submit on older WezTerm

Replace the send-key Enter approach with a trailing \n appended to the
message payload inside the bracketed paste. After the paste bracket closes, the
\n triggers submission in raw-mode TUIs. This works across all WezTerm
versions since it only uses send-text.

Changes (13 files, +161 / -109)

Layer Files What
Entry point bin/ask _caller_pane_info() helper; threads pane ID via daemon RPC and background script env
Core lib/askd/adapters/base.py caller_pane_id, caller_terminal fields on ProviderRequest
Daemon lib/askd/daemon.py Reads caller_pane_id/caller_terminal from RPC message
Hook lib lib/completion_hook.py Passes CCB_CALLER_PANE_ID/CCB_CALLER_TERMINAL env vars to hook script
Hook script bin/ccb-completion-hook Direct pane routing fast path; session file search moved to else branch; simplified send_via_wezterm
Adapters All 7 (codex, gemini, opencode, droid, claude, copilot, codebuddy, qwen) Forward caller_pane_id/caller_terminal to notify_completion

Test plan

  • 268/268 existing tests pass, 0 regressions
  • Manual: ask codex "reply PING" from instance A → response arrives in instance A (not B)
  • Manual: ask codex "reply PING" from instance B → response arrives in instance B (not A)
  • Manual: Response auto-submits without pressing Enter (WezTerm 20240203)
  • Backward compatible: when CCB_CALLER_PANE_ID is not set, falls back to session file lookup

Reduces cases in #142. Improves on #149 for older WezTerm versions.

M-Marbouh and others added 3 commits March 21, 2026 06:42
… response

When the completion hook delivers a Codex/Gemini response back to a Claude
Code pane, the multi-line message was sent with --no-paste, causing each \n
character to arrive as a raw LF keystroke. Claude Code runs as a raw-mode TUI
that does not submit on LF (0x0A) — it requires a real Enter key event. As a
result, the response was pasted into the input buffer but never auto-submitted,
requiring the user to press Enter manually.

The send path (terminal.py WeztermBackend.send_text) already handles this
correctly: it uses bracketed paste mode for multi-line content, then calls
_send_enter() which uses `wezterm cli send-key --key Enter`. The completion
hook's send_via_wezterm() was inconsistent with this pattern.

Fix:
- Send the response body using bracketed paste mode (omit --no-paste) so
  intermediate \n chars are treated as literal newlines in a paste block
- After a brief pause (0.1s) to let the TUI process the paste, send a proper
  Enter key event via `wezterm cli send-key`, trying both --key Enter and
  positional Enter across WezTerm CLI versions, with a \r byte fallback

Tmux is not affected: send_via_tmux() already uses paste-buffer + send-keys Enter.

May also reduce cases reported in bfly123#142 where fragmented \n keystrokes caused
incomplete delivery of the response to Claude Code's input.
…tion

When multiple Claude Code instances run in the same project directory,
completion hook responses were routed to the wrong instance because all
instances share a single session file (.claude-session). The session file
contains one pane_id, which gets overwritten by whichever instance wrote
last — so responses always went to that instance, not the one that
originated the request.

Fix: capture the caller's terminal pane ID (WEZTERM_PANE or TMUX_PANE)
at ask-time and thread it through the entire request chain:

  ask (capture) → daemon RPC → ProviderRequest → adapter →
  notify_completion → completion hook env → direct terminal send

When CCB_CALLER_PANE_ID is set, the completion hook sends directly to
that pane, bypassing the session file lookup entirely. When unset (older
ask clients), the existing session-file-based routing is preserved as
fallback.

Also fixes auto-submit on older WezTerm versions (pre-send-key):
replaces the send-key Enter approach with a trailing \n in the bracketed
paste payload, which triggers submission in raw-mode TUIs like Claude
Code across all WezTerm versions.

Changes (13 files):
- bin/ask: _caller_pane_info() helper, threads pane ID through daemon
  request and background script env vars
- bin/ccb-completion-hook: direct pane routing fast path; session file
  search moved to else branch (skipped when direct pane available);
  simplified send_via_wezterm using trailing \n instead of send-key
- lib/askd/adapters/base.py: caller_pane_id/caller_terminal fields on
  ProviderRequest
- lib/askd/daemon.py: reads caller_pane_id/caller_terminal from RPC msg
- lib/completion_hook.py: passes CCB_CALLER_PANE_ID/CCB_CALLER_TERMINAL
  env vars to hook subprocess
- All 7 adapter files: forward caller_pane_id/caller_terminal to
  notify_completion

Related: bfly123#142, bfly123#149
268/268 tests pass.
@bfly123 bfly123 merged commit 06789da into bfly123:main Mar 30, 2026
M-Marbouh added a commit to M-Marbouh/claude_code_bridge that referenced this pull request Mar 30, 2026
WezTerm builds pre-dating send-key (e.g. 20240203) fall through to the
CR byte fallback immediately after send-text completes. Without a short
delay, the \r arrives before the TUI has finished processing the
bracketed paste sequence, causing Claude Code to ignore it and require
a manual Enter.

Add time.sleep(0.1) before any Enter-sending attempt so the bracketed
paste is fully consumed first. Reproduces the timing that was present
in bfly123#149 and was inadvertently dropped in bfly123#151.

Fixes: bfly123#149 regression introduced by bfly123#151 on older WezTerm versions.
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.

3 participants