Skip to main content

Shell Activities

One activity, shell.run, executes a command on the worker and returns its exit code and output.

Setup

No configuration. The command runs as the worker's own operating-system user, with the worker's filesystem and network access.

This is the broadest privilege in the catalog

shell.run can do anything the worker process can do. Every call asserts the internal.shell_activities / invoke privilege, so a deployment can restrict who may use it — but if that policy is not deployed the check passes, leaving arbitrary command execution open to any workflow. See Authz Activities.

For the same reason shell.run can never be bridged into claude_agent.query as an agent tool, at any privilege level.


shell.run

Runs a command, waits for it to finish, and returns return_code, stdout and stderr.

A non-zero exit code is not an error by default — the activity returns normally with return_code set. Use check: true to fail the activity instead.

Input

FieldTypeRequiredDefaultDescription
commandstryesThe command line to run
shellboolnotrueRun through the system shell, so pipes and redirects work
capture_outputboolnotrueCapture stdout/stderr. When false, both come back null
checkboolnofalseRaise and fail the activity when the exit code is non-zero
cwdstrnoworker's cwdWorking directory for the command
envdict[str, str]nonullExtra environment variables
timeoutnumbernoretry_policy.timeout_secSeconds before the process is killed
textboolnotrueDecode output as text. When false, stdout/stderr are raw bytes
encodingstrno"utf-8"Decoding used when text is true

Output

FieldTypeDescription
return_codeintProcess exit code; 0 means success
stdoutstr | bytes | nullStandard output, or null when capture_output is false
stderrstr | bytes | nullStandard error, or null when capture_output is false

Examples

Reading a file into workflow context, from moco-examples/task-manager/tests/run-all-tests.yaml:

- activity:
name: load-submit-task-tests
type: shell.run
input_data:
command: "cat {{ test_dir }}/submit-task.test.yaml"
output_data:
- submit_task_yaml: "{{ _raw_output['stdout'] }}"

Running a script that has side effects — note max_attempts: 1:

- activity:
name: rebuild-index
type: shell.run
input_data:
command: "./bin/reindex.sh --since {{ since_date }}"
cwd: "/opt/pipeline"
env:
PIPELINE_MODE: batch
check: true # non-zero exit fails the activity
timeout: 900
retry_policy:
timeout_sec: 900
max_attempts: 1 # the script is not idempotent
output_name: reindex # -> return_code, stdout, stderr
The default retry policy runs the command up to three times

shell.run inherits the platform default of 3 attempts. Set max_attempts: 1 for any command with side effects.

Timeouts nest

When timeout is omitted it inherits retry_policy.timeout_sec, so the process can never outlive its own activity budget. Set timeout explicitly only when you want the process killed sooner than the activity gives up.

Secrets in env are plaintext

Anything you put in env is written in the workflowspec and passed through workflow context. To use a credential, resolve it inside an activity that accepts a *_secret_key field instead — see Secret Activities.