submit composer input via tmux server, not the detached client PTY
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 30s
test / unit + widget + golden + a11y (push) Failing after 28s
test / integration_test (xvfb) (push) Has been skipped
test / bundle smoke (xvfb 5s) (push) Has been skipped
test / daemon subprocess + web WASM smoke (push) Has been skipped
test / dart doc (lib API) (push) Failing after 30s
The composer sent input with pane.write, which writes to the PTY of the tmux client the app spawned. That client detaches (we no longer render or drain its PTY since T-137), leaving the session alive on the server with no client — so keystrokes written to the dead PTY vanished and Claude never saw the message. Submit now goes through the tmux server: load the text into a named paste buffer, paste it bracketed (multi-line and special chars arrive as one block, not a stream of submits), then send Enter. Verified against a live session — paste-buffer -p reaches Claude's input with no client attached. The no-tmux fallback still uses pane.write (claude runs directly in our PTY there). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -49,6 +49,7 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
String _statusLine = 'attaching…';
|
||||
|
||||
bool _spawned = false;
|
||||
bool _usingTmux = false;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
@@ -193,8 +194,10 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
setState(() => _error = resp.error?.message ?? 'spawn failed');
|
||||
return;
|
||||
}
|
||||
_usingTmux = false;
|
||||
setState(() => _statusLine = 'no-tmux · fresh every launch');
|
||||
} else {
|
||||
_usingTmux = true;
|
||||
setState(() => _statusLine = 'tmux · $_sessionName');
|
||||
}
|
||||
|
||||
@@ -213,10 +216,17 @@ class _ClaudePaneState extends State<ClaudePane> {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
// Send composed text to Claude's tmux session. pane.write delivers it
|
||||
// to the PTY (the attached tmux client), which forwards to claude — the
|
||||
// same input verb the terminal pane uses, so D-6 parity holds.
|
||||
// Send composed text to Claude. On the tmux path, submit via the tmux
|
||||
// server (paste-buffer + Enter) — it reaches Claude even with no client
|
||||
// attached, unlike pane.write to the (now-detached) spawned client PTY.
|
||||
// The no-tmux fallback runs claude directly in our PTY, where pane.write
|
||||
// does reach it.
|
||||
void _send(String text) {
|
||||
if (_usingTmux) {
|
||||
final session = _sessionName;
|
||||
if (session != null) unawaited(tmux.sendMessage(session, text));
|
||||
return;
|
||||
}
|
||||
final id = _paneId;
|
||||
final ipc = _ipc();
|
||||
if (id == null || ipc == null) return;
|
||||
|
||||
@@ -54,3 +54,21 @@ Future<void> killAllForRepo(String primaryName) async {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Named tmux paste buffer clide loads composed messages into.
|
||||
const _composeBuffer = 'clide-compose';
|
||||
|
||||
/// Submit [text] to [session] as a single message: load it into a named
|
||||
/// paste buffer, paste it in bracketed mode (so multi-line content and
|
||||
/// special characters arrive as one block rather than a stream of
|
||||
/// submits), then press Enter.
|
||||
///
|
||||
/// This goes through the tmux *server*, so it reaches Claude whether or
|
||||
/// not a client is attached. Writing to the app's spawned client PTY
|
||||
/// (`pane.write`) does not — once that client detaches, the PTY is dead
|
||||
/// and keystrokes vanish (the bug this replaces).
|
||||
Future<void> sendMessage(String session, String text) async {
|
||||
await tmuxRunner([..._socket, 'set-buffer', '-b', _composeBuffer, '--', text]);
|
||||
await tmuxRunner([..._socket, 'paste-buffer', '-p', '-d', '-b', _composeBuffer, '-t', session]);
|
||||
await tmuxRunner([..._socket, 'send-keys', '-t', session, 'Enter']);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user