Guides
Run commands reliably
How pane commands run, how Glazier waits for them and how to set a timeout.
Every tool in this space types your commands into a pane with send-keys, and it works right up until it does not. A ! in a commit message triggers history expansion. A long line hits the limit of the line editor. A command that ends in & eats the ; tmux wait-for that got appended behind it. I collected those failures for a while and then changed how Glazier delivers a command. This page explains the result: what runs, in what order, what Glazier waits for and what happens when a pane dies.
The attribute reference is on Commands.
Order and waiting
pane {
commands = ["npm install", "npm run build", "npm run dev"]
}- Glazier runs the
commandsof a pane in file order. - Glazier waits for each command except the last. Then it configures the next pane.
- Glazier does not wait for the final command. Thus a long-running or interactive command, for example
nvimor a dev server, does not stop the creation of the session. - The commands run in the shell of the pane, so
cdandexportstay in effect for the commands after them. - Glazier creates every pane of a window before it runs the commands of the first pane.
- Session
commandsrun in the active pane after Glazier creates all windows and panes.
Put the command that must finish first, and the program that stays open last. In the example above npm install and npm run build complete before npm run dev starts, and up returns while the server runs.
How a command reaches the pane
Glazier does not type the commands into the pane. It loads them into a tmux paste buffer. Then it types one line that tells the shell of the pane to run the buffer:
eval "$('/usr/bin/tmux' -S '/tmp/tmux-1000/default' show-buffer -b glaze-3f9c0e1a7b2d4c68)"The shell runs each command exactly as you wrote it. The line editor of the shell does not see the commands, so these do not change a command:
!, which bash and zsh expand from the history.- A tab, which the shell would complete.
- A leading
-, whichsend-keyswould read as a flag. - A key name such as
EnterorC-c, whichsend-keyswould read as a key. - A trailing
&,;or#, which would swallow a command that Glazier appends. - A long line past the limit of the line editor.
The buffer holds one eval per command. Thus a command with a syntax error fails alone, and the commands after it still run. The line starts with a space, so a shell that ignores such lines does not keep it in the history. The first line of the buffer deletes the buffer, and each buffer has a random name.
note
The eval runs only the commands from your profile. Earlier versions of Glazier typed the same commands into the pane. Thus the trust model does not change: a person who can change your profile can run commands in your panes.
Glazier sends the buffer to tmux on stdin, so the commands do not appear in the process list. Only a client with access to your tmux socket can read or change a buffer, and such a client can already type into your panes.
Why a paste buffer and not a temporary file? A file would land on disk with your commands in it. A buffer lives in the tmux server, deletes itself on the first line and is visible only to a client that already owns your panes. Same trust boundary, no new footprint.
Shells
- Glazier finds the shell of the pane from the tmux options
default-commandanddefault-shell. - Glazier uses the POSIX form above for every shell except fish. fish gets
eval (... | string collect). - For a shell that Glazier does not recognise, it uses the POSIX form and shows a warning.
- Glazier tests the delivery on bash, zsh, dash, busybox ash and fish.
A command is still a command for your shell. export X=1 is fine in bash and an error in fish. Write the commands for the shell that the pane runs.
Timeouts
By default, Glazier waits with no time limit, because a setup command such as npm install can be slow. Set a limit with --command-timeout on glaze up:
$ glaze up --command-timeout 5m
| Flag | Description |
|---|---|
--command-timeout | Stop the wait for the commands of a pane after this duration, for example 5m. The default value 0 waits with no limit. |
The limit applies to each pane, not to the whole run. When the limit passes, Glazier shows a warning and continues with the next pane:
2026-10-04 22:10:15 WRN glaze stopped waiting for the pane commands name=default reason=the pane's commands did not finish in time
When the shell of the pane exits
A command such as exit, or a program that ends the shell, leaves nothing to run the next command. Glazier checks the pane while it waits. If the shell of the pane exits, Glazier stops the wait, shows a warning and continues with the next pane:
2026-10-04 22:10:13 WRN glaze stopped waiting for the pane commands name=default reason=the pane's shell exited before its commands finished
The exit code of up stays 0 in both cases. A timeout and a dead pane are warnings, not failures, because the rest of the session is still useful. Every other error while Glazier runs a command stops up, and Glazier then removes the session that this run created. See Scripting with glaze for the roll-back rules.
tip
Set remain-on-exit = "on" in the options of a pane that may die, so the pane stays open and you can read the output. See Hooks and options.
See what Glazier sends
glaze up --debug prints each command that Glazier sends to the tmux socket and the text of each pane and session command. A command can contain a secret from a variable, so check the output before you share it. At the default log level, up shows only how many commands it runs in each pane.