Skip to content

Commit 1a8bbac

Browse files
committed
fix(clipboard): remove kitten subprocess clipboard read; it corrupted tty state and crashed paste
1 parent cfc00bd commit 1a8bbac

1 file changed

Lines changed: 15 additions & 88 deletions

File tree

src/io/clipboard.cpp

Lines changed: 15 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -192,83 +192,16 @@ try_clipboard_cmd_override(std::string* error_out, bool& clip_handled) {
192192
// Requires Kitty (TERM=xterm-kitty / KITTY_WINDOW_ID) and `kitten` on
193193
// PATH. Returns nullopt (no error_out) when not under Kitty so the
194194
// caller falls through to the native path.
195-
std::optional<ClipboardImage> try_kitty_clipboard(std::string* error_out) {
196-
#if !defined(_WIN32)
197-
// Don't gate on TERM / KITTY_WINDOW_ID: SSH strips KITTY_WINDOW_ID
198-
// and may rewrite TERM, so they're unreliable on a remote host.
199-
// Presence of the `kitten` binary plus "no native clipboard" (the
200-
// only context we're called in) is signal enough — if the terminal
201-
// on the other end of the TTY isn't actually Kitty, the kitten's
202-
// OSC handshake gets no reply and the `timeout` below bounds the
203-
// wait, after which we fall through.
204-
if (!tool_in_path("kitten")) return std::nullopt;
205-
206-
// Unique temp path. tmpnam is racy in general but fine here: we own
207-
// the name, write+read+unlink it immediately, no attacker window in
208-
// a single-user TTY session.
209-
std::string tmp = "/tmp/agentty-kclip-" + std::to_string(::getpid()) + ".bin";
210-
211-
// --get-clipboard --mime 'image/*' picks the first image MIME the
212-
// clipboard offers; kitten transcodes raster formats to what we ask.
213-
// We request a concrete png file so kitten emits PNG.
214-
//
215-
// `timeout` guards against a stuck terminal handshake freezing the
216-
// UI thread — the kitten waits on a terminal reply that may never
217-
// come (terminal not Kitty after all, OSC swallowed, perms denied).
218-
// 5s is generous for a clipboard round-trip incl. a permission tap.
219-
std::string have_timeout = tool_in_path("timeout") ? "timeout 5 " : "";
220-
std::string errlog = tmp + ".err";
221-
std::string cmd = have_timeout
222-
+ "kitten clipboard --get-clipboard --mime 'image/png' "
223-
+ tmp + " </dev/tty >/dev/tty 2>" + errlog;
224-
int rc = std::system(cmd.c_str());
225-
226-
CaptureResult r;
227-
if (FILE* f = std::fopen(tmp.c_str(), "rb")) {
228-
char buf[8192];
229-
std::size_t n;
230-
while (r.bytes.size() < kCap && (n = std::fread(buf, 1, sizeof(buf), f)) > 0)
231-
r.bytes.append(buf, n);
232-
std::fclose(f);
233-
r.status = 0;
234-
}
235-
236-
// Optional diagnostics: AGENTTY_DEBUG_FILE captures the kitten's
237-
// exit code, captured bytes, and its stderr so a failed paste can
238-
// be diagnosed without guessing.
239-
if (const char* dbg = util::env::get_or_null<util::env::Var::DebugFile>()) {
240-
std::string kerr;
241-
if (FILE* ef = std::fopen(errlog.c_str(), "rb")) {
242-
char b[1024]; std::size_t n;
243-
while ((n = std::fread(b, 1, sizeof(b), ef)) > 0) kerr.append(b, n);
244-
std::fclose(ef);
245-
}
246-
if (FILE* lf = std::fopen(dbg, "a")) {
247-
std::fprintf(lf,
248-
"[kitty-clip] cmd=%s rc=%d bytes=%zu stderr=%.500s\n",
249-
cmd.c_str(), rc, r.bytes.size(), kerr.c_str());
250-
std::fclose(lf);
251-
}
252-
}
253-
std::remove(errlog.c_str());
254-
std::remove(tmp.c_str());
255-
256-
if (r.bytes.empty()) {
257-
if (error_out)
258-
*error_out = "Kitty clipboard had no image "
259-
"(copy an image, then Ctrl+V; if a permission "
260-
"prompt appeared, accept it and retry)";
261-
return std::nullopt;
262-
}
263-
if (auto img = wrap(std::move(r))) return img;
264-
if (error_out)
265-
*error_out = "Kitty clipboard returned non-image data";
266-
return std::nullopt;
267-
#else
268-
(void)error_out;
269-
return std::nullopt;
270-
#endif
271-
}
195+
// NOTE: an earlier version shelled out to `kitten clipboard` here to
196+
// fetch the image over Kitty's terminal protocol in a plain SSH session.
197+
// That is fundamentally unsafe from inside a live TUI: the kitten reads
198+
// and writes the controlling /dev/tty that agentty already holds in raw
199+
// mode (bracketed paste, kitty keyboard, mouse, alt-screen). On exit the
200+
// kitten resets terminal modes to its own defaults, corrupting agentty's
201+
// terminal state and crashing the session on the next input. A correct
202+
// implementation must do the clipboard-read escape INSIDE maya's input
203+
// loop (which owns the tty), not via a subprocess. Removed until that
204+
// exists. AGENTTY_CLIPBOARD_CMD remains the supported escape hatch.
272205

273206
} // namespace
274207

@@ -309,23 +242,17 @@ std::optional<ClipboardImage> read_clipboard_image(std::string* error_out) {
309242

310243
if (!has_wl_paste && !has_xclip) {
311244
// No display server reachable usually means a headless / SSH /
312-
// airgap host. The image is on the user's laptop, not here. If
313-
// we're running under Kitty, the kitten can fetch it over the
314-
// terminal's own clipboard protocol — works in a plain SSH
315-
// session, no clipboard tool needed on this host.
316-
std::string kitty_err;
317-
if (auto img = try_kitty_clipboard(&kitty_err)) return img;
318-
245+
// airgap host. The image is on the user's laptop, not here —
246+
// nothing on this machine can read it. Point at the override.
319247
const char* disp = std::getenv("DISPLAY");
320248
const char* wl = std::getenv("WAYLAND_DISPLAY");
321249
const bool headless = (!disp || !*disp) && (!wl || !*wl);
322250
if (headless) {
323-
if (!kitty_err.empty()) return fail_owned(std::move(kitty_err));
324251
return fail(
325252
"no clipboard on this host (headless / SSH). The image is "
326-
"on your laptop, not the remote. Use a Kitty terminal (its "
327-
"`kitten` fetches the clipboard over SSH), set "
328-
"AGENTTY_CLIPBOARD_CMD, or attach the image by path");
253+
"on your laptop, not the remote — set AGENTTY_CLIPBOARD_CMD "
254+
"to a command that prints the laptop's clipboard image to "
255+
"stdout, or attach the image by path");
329256
}
330257
return fail(wayland
331258
? "no clipboard tool — install wl-clipboard "

0 commit comments

Comments
 (0)