> Documentation index: https://docs.kiste.run/llms.txt, a list of every page in this documentation.

# Run commands

> kiste exec runs a program in a Kiste with its exact output and exit status; durable records, cancel and interrupt.

```bash
kiste exec -- git status --short
kiste exec review-42 --cwd /workspace/api -- npm test
kiste exec build -e CI=true -e NODE_ENV=test -- sh -c 'make test'
```

`exec` runs a program in the Kiste and streams its output back while it runs.
`kiste` exits with the program's exit status, so `exec` fits into scripts and
CI like a local command.

## Exactly what you asked for

- **No shell in between.** The words after `--` are the program and its
  arguments, passed exactly. Write `sh -c '…'` when you want shell syntax such
  as pipes or `&&`. A login shell (`sh -lc`) also runs the image's login
  profile; use `sh -c` when standard output must contain only the command's
  output.
- **Byte-exact output.** Standard output and standard error arrive separately
  and unchanged, binary included.
- **No standard input.** `exec` doesn't forward standard input; pipe input with
  `kiste ssh NAME -- CMD < file` instead.
- **No terminal.** Programs that insist on a terminal belong in `kiste ssh`.
- **No SSH.** `exec` uses a private channel into the Kiste, not SSH, and works
  without device keys.

`--cwd` sets the working directory, `-e KEY=VALUE` (repeatable) the
environment. `--timeout` limits how long the command may run.

## Machine-readable

```bash
kiste exec build --json -- make test
kiste exec build --output ndjson -- make test
```

With `--json`, one `exec.completed` envelope holds the `exit_code` and the
output as `stdout_base64` and `stderr_base64`. With `--output ndjson` every
chunk of output arrives as its own line while the command runs. `kiste` exits
with the command's status in both modes; a failure of the CLI itself is an
error envelope with its own [code](https://docs.kiste.run/cli/output.md#exit-codes). Exit code `127`
means the program doesn't exist in the Kiste.

## Durable records

Every command gets a record that survives your connection: who ran what, when
it started and ended, its exit code or signal, and how many bytes it wrote. The
output itself is not stored on the server.

```bash
kiste commands review-42          # the commands of one Kiste
kiste command 550e8400-e29b-41d4-a716-446655440000
```

When this computer started the command, `kiste command` also shows its local
transcript, so you can diagnose it without running it again. The console shows
commands under **Activity**.

## Cancel and interrupt

Give a command your own ID to address it from elsewhere, for example from
another agent:

```bash
kiste exec build --id 550e8400-e29b-41d4-a716-446655440000 -- npm test
kiste cancel 550e8400-e29b-41d4-a716-446655440000 --name build
kiste interrupt 550e8400-e29b-41d4-a716-446655440000 --reason "superseded"
```

`cancel` stops a running `exec` command on its Kiste. `interrupt` asks any
durable command, including a [coding agent run](https://docs.kiste.run/ssh/agents.md), to stop
cooperatively, and records the reason.

## exec or ssh?

|  | `kiste exec` | `kiste ssh NAME -- CMD` |
| --- | --- | --- |
| Shell | none, argv as given | the remote login shell |
| Standard input | not forwarded | forwarded |
| Terminal | never | when interactive |
| Output | byte-exact, separate streams, JSON modes | as the terminal shows it |
| Record | durable, cancellable | none |

## Related topics

- [Overview](https://docs.kiste.run/ssh.md)
- [Copy files](https://docs.kiste.run/ssh/files.md)
- [Port forwarding](https://docs.kiste.run/ssh/forward.md)
- [SSH configuration and editors](https://docs.kiste.run/ssh/ssh-config.md)
- [Device keys](https://docs.kiste.run/ssh/devices.md)
- [Coding agents and credentials](https://docs.kiste.run/ssh/agents.md)
- [Browser terminal](https://docs.kiste.run/ssh/browser-terminal.md)
- Previous: [Device keys](https://docs.kiste.run/ssh/devices.md)
- Next: [Coding agents and credentials](https://docs.kiste.run/ssh/agents.md)
