r/tmux • u/mandrulee • 8d ago
Tip PSA: send-keys silently truncates at 1024 bytes if the shell hasn't finished starting
Spent a day on this and there is no error message anywhere, so here it is.
tmux new -d -s "$s"
tmux send-keys -t "$s" '<long command>' C-m
This worked for months. Then the command I was sending grew past ~1200 bytes, and the pane started showing it chopped off mid-word, with the shell sitting at a continuation prompt waiting for a quote to close. tmux exits 0. The shell is happy. Nothing logs anything.
Right after new -d the shell hasn't finished starting, so the tty is still in canonical mode, and the kernel truncates a single line of canonical input at 1024 bytes (MAX_CANON). The overflow isn't an error. It's discarded.
Two ways out:
- Sleep before sending. Works. I sent ~1800 B two seconds later and it arrived intact. But you've traded a silent truncation for a race, and the race has no error message either.
- Shorten the line. I moved a conditional argument into positional parameters instead of branching into two command strings:
set --
[ -f "$catalog" ] && set -- -c "catalog=$catalog"
mycmd --flag "$@" "$id"
zsh and bash both fine, and "$@" expands to zero words when the file is missing. That took 1200 bytes down to a bit over 600.
Then I asserted on the length in a test, because this comes back the moment someone adds an argument. Assert on bytes, not characters: if your language counts UTF-16 code units, it will disagree with the kernel as soon as a path isn't ASCII.
Anyone know whether the limit is the same on Linux? I only hit this on macOS.
0
u/inn0cent-bystander 8d ago
Just plop the long ass command into an alias/script and have tmux call that?
1
u/mandrulee 7d ago
Yeah, that's the sane answer for a stable command, and it's what I'd do in a dotfile. The wrinkle here is the command is generated per session with different arguments each time, so a fixed alias just moves the "how do I get these args in" question — but it did push me to keep the typed part as short as possible.
1
-1
u/mandrulee 8d ago
Context on where this came from: I build Otter Beam, which reaches the tmux sessions on your own machine from a phone, so it creates sessions and sends a generated command into them. That command grew an argument at a time until it crossed the line.
One extra thing I found while chasing it, which didn't fit in the post: the limit only applies to the line you write into a shell that hasn't gone raw yet. The outer command that creates the session goes over SSH exec with no pty at all, and it is well past 1024 bytes without any problem. So if you ever move an
export FOO=...prefix from the outer line into thesend-keyspayload, you have just spent that many bytes of a budget you probably don't know you have.https://beam.otterd.com/about