7 min read
The daemon said yes to an option that did not exist
Driving the tuios control socket like a machine found a command that lost its spaces, a dropped parameter, and 88 settings of which six applied live.
GGGaurav Gosain
tuios has a control socket: a JSON protocol a program can speak to the daemon to open windows, send input, read screens, change settings. It exists so that agents and scripts can drive a session without faking keystrokes at a TUI. Every verb had tests. The tests were green. Then I sat down to write the documentation that teaches an agent to use it, and did the one thing the tests had never done. I ran the documented examples over a real socket against a real daemon, and looked at what actually happened.
Four findings, in order of how much they changed my mind about what a passing test means.
The command that lost its spaces
send-keys takes a token string in the tmux style: key names and text,
comma-separated. So the obvious way to run a command is:
tuios send-keys "echo hello,Enter"The pane receives echohello, followed by Enter. The tokenizer aliases
commas to spaces and then splits on whitespace, so echo, hello and
Enter are three tokens, each sent as its content, with the separators
dropped. That is correct for a key-token language, where the separator is
punctuation rather than payload. It is also a trap for exactly the caller this
surface exists to serve: a machine that composed a shell command and reached
for the verb with "keys" in the name. Nothing warns you. The call exits zero,
and the pane runs a command that does not exist.
send-keys, commas become spaces, the string splits on whitespace, and each token is sent as its own content. The separators never reach the pane, so echo hello,Enter runs echohello. Switch to send-text and the same words arrive as typed.This one is not fixed in code, because the tokenizer behaves as specified.
The fix is in the surface. The skill documentation now shows the failure
inline, annotated with exactly what gets typed, and steers text, including
whole shell commands, toward send-text, which sends its argument verbatim.
The examples in that documentation are held by a test that parses every one
of them against the real command tree, so the docs cannot drift from the
binary.
The parameter that fell on the floor
new-window takes a session and a name. I asked it for a workspace too:
{"verb": "new-window", "params": {"session": "dev", "workspace": 2}}A window came back: created, named, success envelope, everything in order.
On workspace 1. Dropping an unknown field is what encoding/json does by
default, and it is the worst answer available to a machine caller. The call
did less than it was asked, reported success, and left no trace of the
difference. A human notices the window opened in the wrong place. An agent
reads "type": "window_created" and moves on, wrong about the world from
then on.
The fix has two halves. workspace became real, along with cwd and
focus. And the protocol layer now checks every incoming parameter against
the verb's published schema before the handler runs. Anything unrecognised is
refused, with the closest match and the full list of accepted parameters.
The refusal is useful beyond typos. A parameter the verb does not take yet is
exactly what a caller built against a newer tuios sends to an older one, and a
refusal is the only honest answer in that situation. Turning the check on
also caught two parameters that were real but missing from their verbs'
declared schemas, which would have become unreachable the moment enforcement
landed. The declared surface and the implemented surface had already drifted
apart. Nothing was comparing them.
This is the same kind of request against the daemon today, with the workspace parameter misspelt:
-> new-window, session blogdemo, workspce 3<- error.code invalid_paramsmessage verb new-window has no parameter workspcedid_you_mean workspaceaccepted session, name, workspace, cwd, focus, command, hostdetail An unknown parameter is refused rather thansilently ignored. Fix the name and retry.
The full list of error codes is in the protocol reference.
The option that did not exist
{"verb": "set-option", "params": {"key": "appearance.totally_made_up", "value": "whatever"}}This came back option_set. Not an error. The response did include
"applied": false, which sounds like a signal until you learn it was one bit
meaning two things: "no client is attached to apply this right now" and
"that key means nothing and never will". A caller cannot tell a setting that
will take effect on next attach from a typo. Both were reported as success.
Now the path is resolved against the option registry first, and a miss is an
error, option_not_found, carrying the closest match and the complete list
of valid paths. The value is validated too, by trying the assignment on a
throwaway default config before touching the session, so the check and the
apply cannot disagree about what is assignable. And when applied is false,
the response says why. Both requests from this section, sent to a headless
session today:
-> key appearance.totally_made_up, value whatever<- error.code option_not_foundmessage no such option appearance.totally_made_upavailable appearance.alt_drag, appearance.animations_enabled, ...-> key appearance.border_style, value rounded<- type option_setapplied falsereason no client is attached, so nothing is drawing it yet.The value is recorded for this session and is notsaved to the config file. It applies when a clientattaches
Six paths out of 88
The last one was the quietest and the largest. Applying a setting to a
running tuios went through a hand-written switch, and the switch knew six
paths, all of them appearance options: border style, dock position,
animations, three flavours of window button. The option registry at that
commit declared 88 (the full list today is one tuios list-options away). Everything else, most of the sidebar's settings and all
but one of the dock's, could be written into the config file, read at
startup, listed by the CLI, accepted by set-option, and would do nothing
whatsoever to the running program. Not rejected. Accepted, recorded,
inert.
So the whole sidebar could be configured over the socket, verb by verb, success by success, without a single visible consequence. The fix deleted the dead end rather than extending the switch. Every path now goes through the same route a config file load uses: one assignment function driven by the registry, then the same live-apply step. The registry itself is held to the config struct by a reflection test that walks every scalar field and fails in both directions: a field without a registry entry, or an entry without a field. The number 88 can never again be quietly larger than the number six, because there is no six.
Why every test was green
The tests were not thin. They were pointed at the wrong layer.
The end-to-end suite really did drive set-option over the socket, and here
is the assertion it made: set mouse to on, get mouse back, expect
on. mouse is not a tuios option. The test proved the daemon's key-value
store round-trips a string, which it does beautifully, and proved nothing
about any option existing or taking effect. It now sets a real registry path
and asserts the value came back from the session, not from defaults.
The unit tests called verb handlers as functions, with a fake client wired to answer every command with a hardcoded success. A handler tested like that cannot fail for any of the four reasons above. It cannot even fail for not being registered. The commit that replaced them says it plainly: a handler test would pass for a verb that was never registered, and registration is half of what these verbs are.
The replacement suite starts a real daemon, connects over the socket, sends the verb as bytes, and asserts on the response and then on the state the daemon holds afterwards. It is slower, and it is the only kind of test on this surface whose passing means what it appears to mean.
What I keep from this
An API for machines fails differently from a UI for people. A person who asks for a window on workspace 2 and watches it open on workspace 1 has already noticed: the interface reports itself through their eyes. A program has only the response envelope, so every gap between what was reported and what was done becomes a false belief in the caller, compounding silently. For this kind of surface, "accepted and ignored" is strictly worse than any error. The couple of hours I spent driving my own API the way its real callers would found more product defects than the handler-level suite had found in its whole lifetime. The features were not broken. They were never wired to anything, and no green is as untrustworthy as the green of a test that cannot reach the wire.
Note, September 2026: an earlier version of this post showed the option
example as tuios set-option .... There was no such CLI command: set-option
is the socket verb, and the CLI command at the time was tuios set-config.
It also said the skill steered whole commands toward run-command, which
runs tuios commands such as ToggleZoom, not shell commands. Both are
corrected above.