Shell Action
The shell action executes shell commands and scripts securely, providing comprehensive output capture and error handling.
Basic Syntax
A shell step needs the command; everything else has a default.
steps:
- name: "Execute Build Script"
uses: shell
with:
cmd: "npm run build"
test: res.code == 0Parameters
A shell step needs a command, and optionally the interpreter, the directory and the environment it runs in, plus a limit on how long it may take.
cmd (required)
Type: String
Description: The shell command to execute
Supports: Template expressions
vars:
api_url: "{{API_URL}}"
with:
cmd: "echo 'Hello World'"
cmd: "npm run {{vars.build_script}}"
cmd: "curl -f {{vars.api_url}}/health"shell (optional)
Type: String
Default: /bin/sh
Allowed Values: /bin/sh, /bin/bash, /bin/zsh, /bin/dash, /usr/bin/sh, /usr/bin/bash, /usr/bin/zsh, /usr/bin/dash
with:
cmd: "echo $0"
shell: "/bin/bash"workdir (optional)
Type: String
Description: Working directory for command execution (must be absolute path)
Supports: Template expressions
with:
cmd: "pwd && ls -la"
workdir: "/app/src"
workdir: "{{vars.project_path}}"timeout (optional)
Type: String or Duration
Default: 30s
Format: Go duration format (30s, 5m, 1h) or plain number (seconds)
with:
cmd: "npm test"
timeout: "10m"
timeout: "300" # 300 secondsenv (optional)
Type: Object
Description: Environment variables to set for the command
Supports: Template expressions in values
vars:
production_api_url: "{{PRODUCTION_API_URL}}"
with:
cmd: "npm run build"
env:
NODE_ENV: "production"
API_URL: "{{vars.production_api_url}}"
BUILD_VERSION: "{{vars.version}}"Response Format
The result carries the exit code and both output streams.
res:
code: 0 # Exit code (0 = success)
stdout: "Build successful" # Standard output
stderr: "" # Standard error output
req:
cmd: "npm run build" # Original command
shell: "/bin/sh" # Shell used
workdir: "/app" # Working directory
timeout: "30s" # Timeout setting
env: # Environment variables
NODE_ENV: "production"Usage Examples
The examples below go from a single command to a pipeline that builds, tests, deploys per environment, and reports what failed.
Basic Command Execution
A single command, with its exit code as the test.
- name: "System Information"
uses: shell
with:
cmd: "uname -a"
test: res.code == 0Build and Test Pipeline
Each stage is its own step, so a failure names the stage that broke.
- name: "Install Dependencies"
uses: shell
with:
cmd: "npm ci"
workdir: "/app"
timeout: "5m"
test: res.code == 0
- name: "Run Tests"
uses: shell
with:
cmd: "npm test"
workdir: "/app"
env:
NODE_ENV: "test"
CI: "true"
test: res.code == 0 && (res.stdout | contains("All tests passed"))Environment-specific Deployment
The command itself can be assembled from variables, which is how one step deploys to different targets.
vars:
target_env: "{{TARGET_ENV}}"
deploy_key: "{{DEPLOY_KEY}}"
- name: "Deploy to Environment"
uses: shell
with:
cmd: "./deploy.sh {{vars.target_env}}"
workdir: "/deploy"
shell: "/bin/bash"
timeout: "15m"
env:
DEPLOY_KEY: "{{vars.deploy_key}}"
TARGET_ENV: "{{vars.target_env}}"
test: res.code == 0Error Handling and Debugging
When the command’s own exit code is not enough, the test reads res.stdout and res.stderr.
- name: "Service Health Check"
uses: shell
with:
cmd: "curl -f http://localhost:8080/health || echo 'Service down'"
test: res.code == 0 || (res.stderr | contains("Service down"))
- name: "Debug Failed Build"
uses: shell
with:
cmd: "npm run build:debug"
# Allow failure to capture debug output
outputs:
debug_info: res.stderrSecurity Features
The shell action implements several security measures:
- Shell Path Restriction: Only allows approved shell executables
- Working Directory Validation: Ensures absolute paths and directory existence
- Timeout Protection: Prevents infinite execution
- Environment Variable Filtering: Safely handles environment variable passing
- Output Sanitization: Safely captures and returns command output
Error Handling
Common exit codes and their meanings:
- 0: Success
- 1: General error
- 2: Misuse of shell builtins
- 126: Command cannot execute (permission denied)
- 127: Command not found
- 130: Script terminated by Ctrl+C
- 255: Exit status out of range
- name: "Handle Different Exit Codes"
uses: shell
with:
cmd: "some_command_that_might_fail"
test: |
res.code == 0 ? true :
res.code == 127 ? (res.stderr | contains("not found")) :
res.code < 128