> ## Documentation Index
> Fetch the complete documentation index at: https://docs.hiloop.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Commands and executions

> Run buffered commands inside a sandbox, and open an interactive shell over SSH.

`exec` runs one command inside a running sandbox and returns its buffered output, exit code, and
truncation status in a single response. The command runs in the workload container, so it sees
your image's filesystem and processes.

## Execute a command

```sh theme={null}
hiloop sandbox exec my-sandbox --timeout 300 -- python -m pytest -q
```

Or through the API:

```sh theme={null}
hiloop api "/v1/sandboxes/${SANDBOX_ID}/exec" \
  -X post \
  -H "Idempotency-Key: run-tests-attempt-1" \
  -d '{
    "cmd": ["python", "-m", "pytest", "-q"],
    "timeout_seconds": 300
  }'
```

The request:

| Field             | Meaning                                                                                                         |
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| `cmd`             | Executable and arguments; the first entry is the executable. At most 256 entries, each at most 4096 characters. |
| `timeout_seconds` | Whole seconds, 1 through 3600. Omitted, the server default applies.                                             |

The response reports buffered `stdout` and `stderr`, exactly one process outcome (an `exit` code,
including zero, or a `timeout` marker), and a `truncated` flag. A timeout may carry partial
output; it never carries an exit code.

## Output limits

Combined stdout and stderr are capped at a server-configured limit of no more than 1 MiB;
`truncated` reports when the cap was hit. Truncation is orthogonal to the process outcome: a
truncated response still carries the real exit code. Write large output to a file and snapshot,
rather than through stdout.

## Retries and errors

Exec requires a `running` sandbox and never wakes one implicitly; any other non-quarantined state
returns `409 sandbox_not_running`. An optional `Idempotency-Key` makes a retry replay-safe instead
of double-executing.

Transport failures use stable codes and discard partial output: `spawn_failed` (the container
runtime failed the command instead of running it, for example a missing executable) and
`sandbox_stopped` (the sandbox had no running instance when the command was dispatched). Both mean
the command did not run, so both are safe to retry.

### When the outcome is unknown

A command that was dispatched but whose result could not be read returns an **indeterminate**
outcome rather than an error, alongside any output collected before the connection was lost. The
CLI exits `76` and prints a warning; the API returns `200` with `indeterminate` in place of `exit`.

This is deliberately not reported as a failure, because it is not one: the command may have run to
completion. Do not blindly re-run it. Check whether it already took effect, and re-run only if it
did not — or make the command safe to run twice.

The same caution applies to a timeout. A timeout stops the API waiting for your command; it does
not stop the command, which keeps running inside the sandbox.

To make a retry safe in every one of these cases, send an `Idempotency-Key`. A repeated request
carrying the same key replays the recorded outcome instead of executing a second time. The CLI
generates one for every `hiloop sandbox exec` automatically and reuses it across its own retries,
so a response lost in transit never turns into a second execution.

## Interactive access

`hiloop sandbox ssh <sandbox>` connects to a running sandbox with stock OpenSSH. You need no SSH
key, no `ssh` configuration, and no separate credential: the organization-scoped API credential you
already use authorizes the connection, and hiloop supplies the key material for one session. How
the connection is authorized, and what never enters the sandbox, is covered in
[shell access](/concepts/architecture#shell-access).

<Note>
  This verb needs the sandbox SSH endpoint, which an operator enables per deployment and which is off
  by default. On the hosted service it is currently enabled only in pre-release environments. Where it
  is off, a connect is refused with `unsupported_capability`. See
  [what's live today](/sandboxes/overview#whats-live-today).
</Note>

Everything after `--` is passed to `ssh`: options first, then an optional remote command.

```sh theme={null}
# An interactive shell.
hiloop sandbox ssh my-sandbox

# One remote command. The command's exit code becomes the CLI's exit code.
hiloop sandbox ssh my-sandbox -- 'echo hello'

# Force a pseudo-terminal.
hiloop sandbox ssh my-sandbox -- -tt tty

# Forward a local port to a destination inside the sandbox, with no shell.
hiloop sandbox ssh my-sandbox -- -L 8080:127.0.0.1:80 -N
```

Because it is stock OpenSSH, stdin and stdout pipe as usual, and the sftp subsystem is enabled, so
`scp` and `sftp` work against any image.

Arguments follow stock `ssh` — any leading options, then the sandbox, then an optional remote
command — so the command also serves as the remote-shell program for tools that expect one:

```sh theme={null}
rsync -av -e "hiloop sandbox ssh" ./src/ my-sandbox:/workspace/src/
```

Because everything after the sandbox goes to `ssh`, pass `--api-url`, `--context`, and `--api-key`
before it:

```sh theme={null}
hiloop sandbox ssh --context prod my-sandbox
```

`rsync` runs itself on both ends, so it also needs the `rsync` binary inside your image. The
platform default image does not carry one: use `hiloop sandbox cp`, or install `rsync` in the
sandbox first.

`scp -S` and `sftp -S` take a program **path** and do not split it into words, so they need a
one-line wrapper on your `PATH` rather than the command written inline:

```sh theme={null}
#!/bin/sh
exec hiloop sandbox ssh "$@"
```

For ordinary file transfer, prefer `hiloop sandbox cp`: it needs no wrapper and nothing installed
inside the sandbox.

The sandbox must be running: any other state returns `409 sandbox_not_running`. Each organization
may hold a bounded number of sessions open at once (32 by default, set per deployment); a connect
beyond it is refused with `session_capacity_exhausted`. Close a session you are finished with and
retry.

### Where a session lands

The session runs **inside your workload container**, so it sees your image's filesystem and its
processes, as the image's own user. Files you create are owned by that user, and `exec` and `ssh`
see the same filesystem.

It starts in your image's `WORKDIR` when it declares one, so a shell opens where your project is.
Without a `WORKDIR`, it starts in the login user's home directory, and failing that at `/`.

Your container is otherwise untouched: its entrypoint runs unmodified, and hiloop adds only two
read-only mounts (the endpoint binary and the SSH material) plus one lifecycle hook that starts the
endpoint. No environment variables are injected, and nothing the client sends can add any — a
session's environment is your image's environment.

### What a session may not do

The sandbox's SSH server refuses the following, by design. Each is a deliberate limit rather than a
fault, and a client that asks for one gets an error from OpenSSH:

| Refused                       | Notes                                                                                                     |
| ----------------------------- | --------------------------------------------------------------------------------------------------------- |
| Remote port forwarding (`-R`) | OpenSSH reports `remote port forwarding failed`. Only local (`-L`) forwarding is offered in this version. |
| Agent forwarding (`-A`)       | Your local SSH agent is never exposed to the sandbox.                                                     |
| X11 forwarding                | Not available.                                                                                            |
| Tunnel devices                | Not available.                                                                                            |
| Unix-socket forwarding        | Refused in both directions.                                                                               |
| Root login                    | You log in as the unprivileged `hiloop` account.                                                          |
| Password authentication       | Public-key authentication only.                                                                           |

Host-key checking is always strict and cannot be turned off: `StrictHostKeyChecking no` is rejected
by design. You will never be asked to accept an unknown host key, because the key is delivered over
the authenticated API. If a sandbox is recreated it presents a different host key, so a stale
connection attempt fails closed instead of connecting to a different machine.
