Run local commands and coding-agent phases on a device connected through the Desktop App Companion.
| Node | ID | Use |
|---|---|---|
| Run command | rollout.client.shell |
Run local shell commands, tests, and repository operations |
Status: Alpha. These nodes provide bounded local execution. Production validation of the complete autonomous SDLC pilot is pending.
Before running
Install the provider's CLI on the device and sign in through that CLI. A subscription session uses the installed provider's account and limits; Rollout does not convert it into a managed API account. Keep provider credentials in the provider's own login mechanism. An Online device or a detected CLI does not prove that sign-in, model access, or capacity is ready.
Enable the required capability in the desktop app and under Settings → Devices. A workflow uses a device that lives in the workflow's space or is exposed to it, the same rule the editor checks when you pick one. A workflow in your Private space therefore reaches the devices you keep there; a Team workflow reaches the Team's devices and any device exposed to that Team or to the workspace, never a member's Private device that is not exposed. The person who starts a run lends it no devices.
| Target Device | Uses |
|---|---|
| Default | A connected device in the workflow's own Private space. Exposure plays no part, so a Team workflow has no Default device |
| Any available | Any connected device with the capability that lives in the workflow's space or is exposed to it |
| Specific | The named device, when it lives in the workflow's space or is exposed to it |
A specific device is stored by the device_id its Rollout Client reports
(the device_id field of query(source:"Device")), not by its Rollout id;
a Tool's presets and delegations use the id.
Set Working Directory to the assigned repository or worktree. Use an
isolated worktree for implementation and keep one writer for that worktree.
Local client nodes operate on the device's files; the git.* nodes operate on
the workflow runtime's workspace.
Literal command arguments
Use Literal Arguments (argv) to edit a JSON array whose first entry is the program and remaining entries are its arguments. For example:
["git", "status", "--short"]
A non-empty array takes precedence over Shell, Command, and a wired
command input. JSON preserves commas, whitespace, empty arguments and escaped
newlines inside an argument. Use [] to return to the shell command path.
Each caller placeholder must occupy a complete argument, such as "{{env}}".
Editing existing saved argv preserves its execution behavior.
Standard input
Standard Input pipes text to the command and then closes the pipe, so a
reader such as python3 -, bash -s, patch or cat sees the whole value and
a normal end of input. It is sent verbatim: never trimmed, never template-
resolved, and never parsed by a shell. It is also the only way to send content
larger than a single argument may carry — one argument is capped at 128,000
bytes, standard input at 1 MiB.
That is what lets a program travel beside its own arguments. Set Literal Arguments (argv) to the interpreter and the arguments it needs, and put the source in Standard Input:
["/usr/bin/python3", "-", "verify", "case-42", "sha256:ab12…"]
Both command shapes accept it: with Shell and Command, the script still runs through the shell and the value is only the data on its standard input.
Standard Input is program source, not a template. Unlike Command, it is
delivered exactly as typed: a {{ name }}, {{ .Values.image }} or \{\{ … \}\}
inside it stays those characters, and an unresolvable reference in it does not
fail the step. That is deliberate — a script that writes a Jinja or Helm file
must not have its own file rewritten on the way to the device. Use Command
or a wired input when you do want run data substituted.
Leave the field empty and nothing changes: no pipe is opened and the command keeps the standard input it has always had. A command that exits before reading everything fails the step, keeping its real exit code and output, rather than reporting success on a program that arrived half-written.
stdin is also an input, so an upstream node can supply the content — and a
wired value wins over the typed source. Those bytes are dynamic run data, so
wiring an empty value is a deliberate choice: the command gets a pipe that
is immediately closed, which is what a reader waiting on end of input needs.
Only leaving both the field and the input unset means "no pipe at all".
A value the device would refuse is refused here, before the command is sent: anything that is not text, and anything above 1 MiB. The step fails naming the field and its size. Nothing is truncated — a half-delivered program is worse than a step that did not run.
Like every input, stdin stays operator-fixed in a tool or widget grant until
someone exposes it on purpose; a widget grant cannot expose it at all.
One source, many commands
When several commands run the same program, copying it into each Standard
Input means editing and reviewing it several times. Wire the stdin_source
input instead: it names one uploaded workspace file, and its exact bytes become
that command's standard input. Eight operations can share one body; changing
the body means uploading a new file, and every command that still points at the
old one keeps the bytes it was authored against.
Upload the file in the workspace first, then name it in one of two places:
- Shared Standard Input in the command's own settings, which takes the
reference as JSON:
{"id": "<attachment id>", "content_digest": "sha256:<64 hex>"}. - the
stdin_sourceinput, wired on the canvas from an upstream node — a Code node returningrollout.attachmentRef({ id: '<attachment id>', content_digest: 'sha256:…' }), or any node whose output is already an attachment reference.
A wired source wins over the authored one. There is no file picker yet: no browse dialog, and no display of the file's name, size or version beside the setting.
Supply content_digest on the reference — sha256: followed by 64 hex
characters — to pin the exact bytes. A pinned command refuses to run if the file
it reads back is not the file it was written against.
The source is checked before anything is sent to the device:
- It must be a file in this workspace that the run is allowed to read. A file from another workspace, a deleted one, or one the run has lost access to fails the step. A reference is a name, never a permission.
- Its bytes must be text and within the same 1 MiB standard-input limit.
- Its digest must verify. A file stored by its content — anything that arrived
through the API, an integration or a workspace transfer — already carries one.
A file uploaded through the browser does not: Rollout has no surface that
shows its SHA-256 yet, so pin
content_digestyourself (shasum -a 256 <file>). Without an anchor the step refuses rather than run bytes nothing can vouch for. - A file someone attached to a chat message is data, not program source, and is never resolved here.
Wire either stdin or stdin_source, not both — two standard inputs on one
command fails the step instead of quietly picking one. The same goes for the two
settings: Standard Input and Shared Standard Input together fail the step
rather than one silently winning.
The source is operator-fixed. The node declares it that way, so a grant never offers it to an agent or an App as a value the caller may fill in, and a grant that tried to would be refused when it was saved. An operator picks the file, never the model asking for the command.
Documents are not a substitute. Rich text trims and reflows what it stores, so a program read back from a Document is not guaranteed to be the program that was written. Upload the source as a file.
Secrets
Secrets lists workspace secrets by name (Settings › Secrets). Each one reaches the command as an environment variable of the same name, on the executing device only:
sentry-cli releases info "$RELEASE" --auth-token "$SENTRY_AUTH_TOKEN"
The values never travel in the command itself. The approval card on the device lists the secret names the command will receive; the device fetches the values once the command is approved, for that one command, and refuses any variable the approval did not name. A queued command, the approval request and the run's history hold only the names and a reference. Anything the command prints that matches a value, or its Base64 form, is redacted on the device (its live output and task history) and on the server (the run view, step output and stored results).
- Secrets are not inherited from the workflow's Environment settings. List every one the command needs on the step.
- A name that doesn't exist, or can't be read, fails the step before anything is sent, and the error names the secret.
- A secret needs a workflow run to redact against. A call from MCP or an App with no run is refused when the step lists secrets.
- A device accepts at most 64 secrets per command, names up to 128 bytes and values from 8 bytes up to 64 KiB with no NUL byte. A shorter value would be too short to redact. The 8 bytes count in every form the device can print a value in: a value ending in a carriage return needs 8 bytes without it, because the device's output drops the CR before a newline. A secret outside those limits fails the step before it is sent.
- A secret can't be named after a variable the shell or the system loader
reads to run code:
BASH_ENV,ENV,ZDOTDIR,PATH,IFS,SHELLOPTS,BASHOPTS,PS4,PROMPT_COMMAND, or anything startingLD_,DYLD_orBASH_FUNC_. Rename the secret; the step fails before dispatch otherwise. - The values stay available for the step's Timeout plus its Approval Timeout, up to 2 hours together, which covers a 90-minute long command. Each time the device fetches them, Rollout checks again that the run may still read every secret: one archived or unshared while the command waited is not delivered, and the command fails.
- Live output of a step with secrets can lag. The device shows a line only once enough output follows it to rule out the start of a secret (up to the length of the longest secret, or its Base64 form), or when the command ends. A short status line followed by a long silence appears late; the final output is complete.
- A command also sees
ROLLOUT_EXEC_FILES, a private temporary directory the client removes when the command ends.
Device admission requires the current approval-receipt, secret-material, material-before-start and durable-children protocols. A command fetches its named secrets after approval and reports its start only after that fetch succeeds. A failed fetch reports no start and fails the step.
When a device refuses a command
A device checks each command against its own limits before it starts anything —
argument size, standard-input size, working-directory length, and the rest. A
command that fails those checks fails the step immediately with
Command rejected (invalid_command_payload): … and the name of the field that
was refused. The refused content is never echoed back.
Use that message to fix the step. An oversized argument means the value belongs in Standard Input; an oversized standard input means the content has to shrink or be fetched by the command itself.
Device, approval, and time limits
| Setting | Behavior |
|---|---|
| Target Device | Default (a device in the workflow's Private space), any available device that reaches the workflow, or a specific device |
| If No Device Available | Fail immediately, or queue within the command's bounded wait |
| Approval Required | Device approval is required by default; configure the policy for the intended automation |
| Approval Message | Operator-authored description shown with the requested action |
| Timeout | 10–5,400 seconds; default 300 seconds |
| Approval Timeout | Optional separate approval wait, up to 600 seconds; zero shares the command timeout |
Busy, offline and waiting for approval
Three different things stop a command from starting, and they are not the same failure:
-
The device is busy. It is connected and allowed to run your command, but it already has the most local commands it will run at once. The command is not failed. It waits and is re-sent every few seconds until a slot frees up or the command's own Timeout runs out, and the step never spends a retry on the wait. This applies to both If No Device Available settings and to every client node, Run browser script included: a busy device is not an unavailable one, so switching to Fail does not turn a full machine into an immediate failure. If the timeout does run out, the step says the device deferred the command for local capacity and it never started — no exit code and no session to reconcile, because nothing was launched.
Queue Until Available also moves the command: it goes back to the workspace queue while it waits, and the next attempt prefers a device that has not just deferred it, so with several enrolled machines it moves to an idle one instead of queueing behind a full one. A Fail dispatch has no queue to return to and keeps waiting on the device it picked, as does a Specific device target on either setting.
The wait comes out of the command's own Timeout. Once a device has deferred it, the clock that was running at that moment is a ceiling: waiting for a slot, waiting on the device's approval prompt and the run that eventually starts all end there. Set Timeout to cover the work and the wait you are willing to spend on a busy machine. Approving a deferred command does not prompt you again on every re-send; the device keeps your decision until the command runs, the connection drops, or the app restarts.
-
No device is available. Nothing eligible is connected. Fail ends the step at once; Queue Until Available holds the command in the workspace queue until an eligible device connects or the command's Timeout expires. The queued command records the workflow's space, and a device must still reach it when it is delivered.
-
The command is waiting for approval. It reached a device and is sitting on its prompt. That wait runs on the separate Approval Timeout where one is configured, and it holds no execution slot on the device.
When selection does fail, the step error names which case it was, so a step
that continues on failure can branch on nodes.<step>.error.code instead of
matching the sentence. Only devices the run can see count: those that reach
the workflow and, for a private workflow, the devices its owner can read. A
device the run cannot see behaves as if it were not connected.
| Code | Meaning | Fix |
|---|---|---|
no_device_available |
No connected device this run can see has the capability the step needs. For Default, none in the workflow's Private space has it | Connect the Rollout Client on a device with the capability enabled |
device_not_available |
Capable devices are connected, but none reaches this workflow, or the named one does not. A Task run also refuses any device but the one its execution holds | Expose the device to the workflow's space in Settings → Devices, or pick another |
device_not_found |
The named device is not connected, or this run cannot see it | Start the Rollout Client on it, or queue |
device_not_capable |
The named device does not have the capability | Enable it on the device |
default_device_requires_private |
Default on a workflow in a Team | Pick Any available or a specific device |
device_authority_invalid |
The run's identity cannot use devices: its owner or the person who started it left the workspace or was suspended, its Team was archived, or its Tool's device delegation was revoked. A command queued before its targeting recorded the workflow's space also fails with it | Restore that identity; no device change helps. Run a stopped queued command again |
device_authority_lost |
The device stopped reaching the run between selection and the send | Run the step again |
The node's execution timeout and the workflow timeout also apply. Set them to cover the intended phase and any separate approval wait. Run command supports commands up to 90 minutes; workflow and parent deadlines still apply.
Foreground commands of any length
Use the Command tab for the command and working directory, Execution for the device, approval and timeout settings, and Output for result extraction. Enable Command on the device and allow it in the workspace device policy.
The command remains in the foreground with live output and a finite timeout. Literal argv, current device authorization and approval rules are the same as Run command. The default outer node budget is 6,060,000 ms: 90 minutes for the command, ten minutes for a separate approval wait and one minute of headroom. The command itself still defaults to five minutes; choose the required bound.
The server operator must also provide sufficient supervision time. The default execution-unit ceiling is 90 minutes and remains enforced. A full 90-minute command with approval and cleanup needs an explicitly larger ceiling, such as 120 minutes. Parent Subflow waits and the whole-workflow timeout must cover the same work; increasing only the command timeout cannot override those clocks. This node does not retry a command automatically.
After the device reports the start
Once the device reports that the command started, a Run command step parks: the run waits for the device without holding a server, so a server deploy or restart no longer fails the step or stops the command. The run panel shows the step as running on the device, with the time it fails if no result arrives. That deadline is the command timeout the step had left when it parked. The device's result resumes the run on whichever server receives it; the deadline, a disconnect or the server's orphan sweep resumes it with an error instead.
A step keeps the previous in-process wait, and still fails when its server restarts, when it passes Secrets to the command, sets a secret config value, runs as an agent tool, per item or in a session run, or when its command would carry a value the run injected that Rollout cannot prove again later, such as an upstream connection token templated into the arguments.
A parked result is stored with the run's secrets redacted and its output
strings cut, longest first, to the node output limit (256 KiB by default). The
step still completes with its status, exit code and verified output evidence;
an in-process command keeps full output up to the step's own limit. If the
secrets the command received can no longer be proven when the result arrives
(a secret was deleted or rotated meanwhile), Rollout keeps none of the output
and the step fails with device_result_unscrubbable, even when the command
succeeded.
The current desktop app holds a parked command through a short network loss and replays its result when it reconnects. Device admission requires durable children and material-before-start execution. If the device defers the command for local capacity after reporting the start, the command did not run: the step fails with "The device deferred the command for capacity after reporting it started; it did not run; retry the step."
Changing the device owner's team memberships, team role or workspace role
doesn't interrupt a running command while the device keeps its access. When
the server finds that access gone (the owner's workspace membership is
suspended or removed, or the device's credential was revoked), it ends the
device's session and the step fails with device_access_revoked: "Stopped:
the device's access changed during the command; whether it already had an
effect is unknown." This applies to a parked command too. The desktop app
stops its local commands at once, one marked to survive a reconnect included.
Deleting,
transferring or signing out the device closes its connection instead, which
fails the step as a disconnect.
Coding agents
Use AI agent and select the Claude Code, Codex or GitHub Copilot CLI harness. AI agent owns model and tool settings, registered-device or SSH/Tailscale selection, structured results and session continuation. Claude Code and Codex are no longer separate workflow nodes.
Results and recovery
Commands return captured stdout, stderr, exit status and available duration. Output byte counts and digests describe the retained console evidence; a short console tail is not a complete build log. Store important reports as files or attachments while the command runs. Workflow step storage and live display have their own size limits.
A failed or cancelled command stays failed even when a late completion arrives. Available device, request and working-directory information appears under Recovery details. Cancellation and disconnection attempt to stop execution; they do not undo effects already produced. Reconcile the machine and external results before retrying a mutation whose response was lost.
With Queue Until Available, a device that disconnects before it reports
the command started returns the command to the workspace queue for the next
eligible device. Once the device has reported the start, or the owner has
approved the command on the device, a disconnect fails the step as outcome
unknown and the command is never delivered again. A Run command that
has parked after its start fails with "Device disconnected during command
execution." instead (see After the device reports the start).
A device whose access was revoked fails its running and parked commands with
device_access_revoked instead of a disconnect; one that was not started yet
goes back to the queue.
The server limits concurrent agent turns per device (max_agent_turns, default 4).
Owners and exact Managers edit it in Settings › Connections › Devices or MCP. Parkable graph
steps wait without an execution timeout, then resume in FIFO order among runnable
turns. Tool calls, App/MCP calls, session and iterated agents cannot queue: a full
device returns device_busy immediately. Shell commands and the desktop protocol
keep their existing behavior.