# My multiplexer lied about what it could do

URL: https://tuios.dev/blog/a-terminal-that-lied-about-what-it-could-do

> kitty icat drew nothing when tuios ran in a browser client. The graphics passthrough said yes to file transmission without asking the host.

Images displayed fine in my terminal. Fine in the multiplexer running inside
it. Fine in the browser client. Stack all three (the browser client hosting a
shell that runs the multiplexer) and `icat` produced nothing at all. No error,
no placeholder, no partial image. Just the prompt, sitting where the image
should have been.

## What icat is actually doing

The kitty graphics protocol has a capability query. Before sending anything
real, `icat` asks the terminal whether it supports a given transmission medium,
and it asks about three of them:

```
_Ga=q,f=24,s=1,v=1,S=3,i=1        can you take pixels inline
_Ga=q,f=24,t=t,s=1,v=1,S=47,i=2   can you read a temp file
_Ga=q,f=24,t=s,s=1,v=1,S=18,i=3   can you read shared memory
```

It uses the first medium the terminal answers `OK` to, which is sensible. A
file is much cheaper than inline base64, so if the terminal can read files,
everyone wins.

## The lie

The multiplexer's passthrough answered `OK` to all three. Unconditionally.

```go
response := vt.BuildKittyResponse(true, cmd.ImageID, "")
ptyInput(response)
```

It never asked its own host anything. It just said yes.

So `icat` picked a file medium, wrote the image to `/tmp`, and sent a path. The
multiplexer forwarded that path out to the browser. The browser cannot read
`/tmp` on the server, so it dropped the transmission and drew nothing.

Captured at the browser, the failure looks like this:

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/blog/a-terminal-that-lied-about-what-it-could-do)*

## Why each layer alone was fine

In a real terminal, the host genuinely can read the path, so the lie is true by
accident.

In the browser client alone, nothing lies. The client answers `ENOTSUPPORTED`
to the file media, `icat` falls back to streaming the bytes inline, and
everything works.

To hit the bug you need a dishonest middle layer in front of a host that cannot
do the thing, and that only happens in the three-layer stack. Each component
was correct in the environment its author tested it in. That is why staring at
any one of them got me nowhere.

Try the combinations yourself:

*[An interactive figure goes here. Open the page to use it.](https://tuios.dev/blog/a-terminal-that-lied-about-what-it-could-do)*

## The fix

Probe the host for file transmission alongside the direct probe, record the
answer, and pass it on:

```go
if isFileMedium(cmd.Medium) && !kp.hostReadsFiles() {
    ok = false
    errMsg = "ENOTSUPPORTED:host terminal cannot read files from this machine"
}
```

A guest that never asks is covered too. If a file transmission arrives and the
host cannot read files, the multiplexer re-encodes it as direct data rather
than forwarding a path that will be silently discarded. A file-capable host
still gets the plain path with no extra copy, so the fast case stays fast.

There was a second bug hiding in the same function. Quiet mode has two levels,
`q=1` to suppress success responses and `q=2` to suppress everything, and the
code treated both as "say nothing". A guest that asked for `q=1` and hit an
error got silence, waited out its timeout, and never tried the next medium. An
error has to reach a guest that only suppressed successes.

## Ask, or say no

A proxy answering capability questions on behalf of something else has two
honest options: ask the far end, or say no and let the guest fall back to the
medium that always works. Saying yes because yes usually works gets you a
failure that only appears when the layers are combined, only for people running
unusual stacks, with nothing logged anywhere and every component pointing at
its own passing tests.

What annoys me is that the multiplexer already had the machinery to handle
this. A function that re-encodes file transmissions as inline data was sitting
in the codebase, gated behind a build-specific flag instead of behind "can the
host actually read files". I did not so much write the fix as move it.

It is the same protocol as the query bugs in
[an earlier post](https://tuios.dev/blog/nobody-was-listening), with a new failure: there the
answer went missing, and here it was made up. What tuios now tells programs
about graphics, in a native terminal and in the browser, is on the
[architecture page](https://tuios.dev/docs/architecture#graphics) and the
[web terminal page](https://tuios.dev/docs/web#graphics-in-the-browser).
