Fixing the Invisible Monitor: Curses vs. SSH
My build swarm has a beautiful monitor. It’s written in Python using curses. It has scrolling logs, progress bars, and realtime updates. It looks like the Matrix.
But yesterday, I tried to show it off to a friend remotely.
ssh commander@gateway "build-swarm watch"
Result: Nothing. The terminal cleared, hung for a second, and exited.
No error. No traceback. Just silence.
The Root Cause: Curses Needs a TTY
The Python curses library (and the C library it wraps) is picky. It demands a fully functional TTY (TeleTYpewriter) to initialize.
When you run a command directly over SSH like ssh user@host "command", SSH doesn’t allocate a TTY by default. It just pipes stdout.
Curses tries to call initscr(), realizes “I have no idea how wide this screen is because it’s just a pipe,” and crashes. Or in my case, throws an exception that I was inadvertently swallowing.
The Solution: A “Dumb” Mode
I realized I broke a cardinal rule of CLI design: Always have a fallback.
I updated the build-swarm CLI to perform a simple check before trying to be fancy.
import sys
def is_tty_compatible():
# 1. Check if stdout is actually a terminal
if not sys.stdout.isatty():
return False
# 2. Check for the --tui flag (user forced it)
if '--tui' in sys.argv:
return True
# 3. Try a dry-run initialization
try:
import curses
curses.setupterm()
return True
except:
return False
If this check fails, instead of launching the full TUI application, I now launch SimpleMonitor.
The “Simple” Monitor (That’s Actually Better?)
The SimpleMonitor class doesn’t use curses. It just prints text, waits 5 seconds, prints an ANSI clear code (\033[2J), and prints again.
But because I was forced to simplify, I actually made the data clearer.
═══ BUILD SWARM MONITOR ═══
2026-01-14 23:17:42
DRONES & ACTIVE BUILDS:
drone-Izar [16 cores] - BUILDING: app-shells/bash
drone-Meridian [18 cores] - IDLE
drone-Tau-Beta [8 cores] - BUILDING: net-misc/curl
drone-Tarn [14 cores] - BUILDING: dev-libs/openssl
QUEUE: 10 waiting, 3 building.
It’s ugly. It flashes when it refreshes. But it works everywhere.
- Inside a CI pipeline? logic works.
- Piped to a file? Works.
- Over a shaky SSH connection? Works.
Lesson Learned
Don’t let your vanity (cool UI) get in the way of utility (seeing the data).
I also added a --tui flag so I can force the Matrix mode when I know I’m in a good terminal. But now, my default is safe.
Status: TUI is flashy, stdout is forever.