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:

JSON
["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:

JSON
["/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_source input, wired on the canvas from an upstream node — a Code node returning rollout.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_digest yourself (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:

SH
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 starting LD_, DYLD_ or BASH_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.