Skip to content

Command Payloads

Command payloads execute a single binary with a structured list of arguments on the target machine. They are ideal for simple, atomic operations.

A Command is not a freeform terminal line. It is structured:

  • bin — the binary or program to run (e.g. docker).
  • arguments — an ordered list of plain string values passed to the binary (e.g. pull, myregistry/myapp:latest).
  • working_directory — an optional absolute path the binary runs in.

Each argument is delivered to the binary as a separate, literal string. There is no shell parsing a single command line, so shell metacharacters, command chaining, pipes, and shell variable expansion are not available. For anything more complex, use a Script Payload or multiple actions/sequence steps.

Use Commands ForUse Scripts Instead
Single binary invocationsMulti-step workflows
Simple tool invocationsConditional logic
Quick system commandsError handling complexity
Docker/container commandsPiping or redirection

Instead of typing a terminal line, you specify the binary and each argument separately.

To run docker pull myregistry/myapp:latest:

  • bin: docker
  • arguments: pull, myregistry/myapp:latest

Arguments and the working directory support variable placeholders. To pull a parameterized image:

  • bin: docker
  • arguments: pull, #{registry}/#{image}:#{tag}

Variable substitution is not permitted in the bin (binary path) for security reasons — the binary path must be a literal value.

Resolved non-sensitive variables are also injected into the executed process as MANTIS_* environment variables. Variable names are uppercased before the prefix is applied, so a variable named image is available to the process as MANTIS_IMAGE, and a variable named tag is available as MANTIS_TAG.

Declared environment variable values are templates too, so an action can hand a binary any variable under a name of its own choosing, a sensitive one included: API_KEY = #{api_key}.

Shell Metacharacters Are Quoted, Not Rejected

Section titled “Shell Metacharacters Are Quoted, Not Rejected”

For the shell executors (Sh, Bash, Pwsh, Zsh) each argument is shell-quoted. Sh, Bash and Zsh wrap it in POSIX single quotes, escaping any single quote it contains as '\''; Pwsh wraps it in PowerShell single quotes, doubling any single quote to ''. A metacharacter inside an argument therefore reaches the binary as a literal character instead of being interpreted, which is what a deployment variable holding a $, an & or a quote needs in order to keep working. Arguments are checked for one character only: the null byte.

The bin value is the part that is filtered. For a shell executor it must contain none of

; | & $ ` ( ) { } < > ' " \ ! * ? [ ] # ~

nor a newline or carriage return, nor a .. path component — so it cannot be made to name something other than the intended executable. A script’s path is checked for null bytes and .. only, because it is passed as argv rather than composed into a shell line.

Because quoting makes the shell ignore the argument’s contents, you still cannot:

  • Chain commands with &&, ||, or ;
  • Pipe with |
  • Expand shell variables with $VAR — $HOME arrives at the binary as those five characters
  • Use command substitution with backticks or $(...)
  • Use globs such as * or ? — they are passed through unexpanded
  • Use redirection with < or >

For the Default executor no shell is involved at all: arguments go to the process as argv, so nothing is interpreted and nothing needs quoting.

To run multiple commands, use a Script Payload or split the work across multiple actions / sequence steps.

Choose the appropriate executor for command execution:

ExecutorCommand InterpretationPlatform
DefaultDirect execution (no shell)Cross-platform
Shsh -cLinux/macOS (WSL on Windows)
Bashbash -cLinux/macOS (WSL/Git Bash on Windows)
PwshPowerShellCross-platform
Zshzsh -f -c (no user startup files)Linux/macOS (WSL/MSYS2 on Windows)

Each example lists the binary and its ordered argument strings.

Pull an image:

  • bin: docker
  • arguments: pull, #{image}:#{tag}

Start a container:

  • bin: docker
  • arguments: run, -d, --name, #{container_name}, -p, #{port}:80, #{image}

Stopping and removing a container requires two separate operations — run them as two actions or two sequence steps (one docker stop, one docker rm), since you cannot chain them in a single command.

APT (Debian/Ubuntu):

  • bin: apt-get
  • arguments: install, -y, #{package}

(Run apt-get update as a separate step rather than chaining it.)

YUM (RHEL/CentOS):

  • bin: yum
  • arguments: install, -y, #{package}

Chocolatey (Windows):

  • bin: choco
  • arguments: install, #{package}, -y

Systemd:

  • bin: systemctl
  • arguments: restart, #{service_name}

Windows Services:

  • bin: pwsh (Pwsh executor)
  • arguments: Restart-Service, -Name, #{service_name}

Download file:

  • bin: curl
  • arguments: -o, #{destination}, #{url}

Extract archive:

  • bin: tar
  • arguments: -xzf, #{archive}, -C, #{destination}

Argument strings, declared environment variable values and working_directory are templates. Mantis renders every #{...} in them before the command runs:

TemplateDescription
#{version}Replaced with the version variable value
#{version | ToLower}The value passed through a filter
#{Mantis.Environment.Name}A system variable describing the deployment

See Variables for filters, conditionals and the Mantis.* system variables. The bin field is never rendered.

Arguments are plain ordered strings — there is no type, default, or required metadata attached to an individual argument. Provide the values you need as ordered argument strings.

In addition to placeholder substitution, every resolved variable is injected into the executed process as a MANTIS_* environment variable. Variable names are uppercased before the prefix is applied — for example, the variable tag is available as MANTIS_TAG. This is the supported way to pass values to a binary that reads configuration from the environment.

Commands report success or failure via exit codes:

Exit CodeMeaningMantis Behavior
0SuccessStep marked complete
Non-zeroFailureStep marked failed

The exit code of the invoked binary is used directly. There is no shell, so there is no chaining to influence the reported result — the binary either exits 0 (success) or non-zero (failure).

A Command runs in the Tarsus agent’s default working directory unless you set the working_directory field. Set it to an absolute path:

  • bin: docker
  • arguments: compose, up, -d
  • working_directory: /opt/myapp

The working_directory field supports variable substitution (e.g. /opt/#{app_name}). Because there is no shell, you cannot change directories with cd && ... — use working_directory or absolute paths instead.

Commands have a configurable timeout (default: 300 seconds). If exceeded:

  • The command is terminated
  • The step is marked as failed
  • The error message indicates a timeout

Set appropriate timeouts for long-running commands:

OperationSuggested Timeout
Docker pull600s (10 min)
Package install300s (5 min)
Service restart60s (1 min)
File downloadVariable by size

Do not embed secret literals in argument strings, and do not attempt $API_KEY-style shell expansion — a shell executor quotes each argument, so $API_KEY reaches the binary as those eight characters rather than as the variable’s value.

Instead, define the secret as a sensitive variable and declare an environment variable on the action whose value is #{api_key}. The value is filled in when the command is delivered to the target, so it never appears in the command definition or in the queued command, and the binary reads it from its environment. A sensitive variable reaches only the actions that reference it; it is not added to the MANTIS_* set.

Arguments are passed as literal strings: a shell executor quotes each one, so a *, ?, ;, |, & or quote inside an argument is inert rather than interpreted. Quoting, not rejection, is what prevents command injection through argument values — the argument is accepted, so validate the variable values you supply. An argument such as #{path} is substituted verbatim into a single argument string, so ensure it contains the value you intend.

Error: command not found: docker

Solutions:

  • Set bin to an absolute path, e.g. /usr/bin/docker
  • Ensure the binary is in the agent’s PATH
  • Check that the executor selection matches the target OS
Error: permission denied

Solutions:

  • Check file permissions
  • Verify the agent runs with the correct user
Error: Command rejected: invalid characters in command or arguments

This names the binary or the script path, not an argument value: an argument is quoted, and is refused only for a null byte.

Solutions:

  • Remove shell metacharacters (;, |, &, $, quotes, globs, etc.) from bin
  • Remove any .. component from bin or from a script’s filename
  • Split chained operations into separate actions or a script — quoting means a ; in an argument is passed to the binary rather than acted on by the shell
  • Provide dynamic values as variables (#{name}) rather than shell expansion

If you see #{variable} in the output:

  • Verify the variable is defined for the deployment
  • Check that the name matches exactly (case-sensitive)
  • Ensure the template is in an argument or working_directory, not in bin
  1. Keep commands simple — one binary invocation per command
  2. Use absolute paths — for bin and for working_directory
  3. Use variables — #{name} for dynamic argument values
  4. Provide secrets as sensitive variables — referenced as #{name}, for example in a declared environment variable
  5. Reach for scripts — when you need chaining, pipes, conditionals, or redirection