Shell Actions¶
Shell actions execute arbitrary commands with full Jinja2 template support for dynamic command construction and access to workflow context variables.
Configuration¶
toml
[[actions]]
name = "action-name"
type = "shell"
command = "command to execute"
working_directory = "path" # Optional, default: repository:///
ignore_errors = false # Optional, default: false
Fields¶
command (required)¶
The shell command to execute. Supports full Jinja2 template syntax for variable substitution.
Type: string
Template Variables Available:
workflow: Workflow configuration objectimbi_project: Complete Imbi project datagithub_repository: GitHub repository object (if applicable)working_directory: Path to workflow working directorystarting_commit: Initial Git commit SHA
working_directory (optional)¶
Directory to execute the command in.
Type: ResourceUrl (string path)
Default: repository:/// (the cloned repository directory)
ignore_errors (optional)¶
Whether to continue workflow execution if the command fails (non-zero exit code).
Type: boolean
Default: false
Examples¶
Basic Command Execution¶
toml
[[actions]]
name = "run-tests"
type = "shell"
command = "pytest tests/ -v"
Command with Working Directory¶
toml
[[actions]]
name = "build-project"
type = "shell"
command = "python setup.py build"
working_directory = "{{ working_directory }}/repository"
Template Variable Usage¶
toml
[[actions]]
name = "create-tag"
type = "shell"
command = "git tag -a v{{ version }} -m 'Release {{ version }} for {{ imbi_project.name }}'"
working_directory = "repository:///"
Multi-Step Script Execution¶
Note: Shell actions execute commands directly without a shell, so shell operators like &&, ||, ;, and | do not work. For multi-step operations, use a shell wrapper:
toml
[[actions]]
name = "setup-and-test"
type = "shell"
command = """
bash -c 'python -m venv .venv && source .venv/bin/activate && pip install -e .[dev] && pytest tests/ --cov={{ imbi_project.slug }}'
"""
working_directory = "{{ working_directory }}/repository"
Conditional Execution with Shell¶
toml
[[actions]]
name = "npm-install-if-needed"
type = "shell"
command = "bash -c 'if [ -f package.json ]; then npm install; fi'"
working_directory = "repository:///"
Ignore Errors¶
toml
[[actions]]
name = "optional-linting"
type = "shell"
command = "ruff check src/"
working_directory = "repository:///"
ignore_errors = true # Don't fail workflow if linting fails
Common Use Cases¶
Running Tests¶
```toml [[actions]] name = "run-python-tests" type = "shell" command = "pytest tests/ -v --tb=short" working_directory = "{{ working_directory }}/repository"
[[actions]] name = "run-javascript-tests" type = "shell" command = "npm test" working_directory = "repository:///" ```
Building Artifacts¶
```toml [[actions]] name = "build-python-package" type = "shell" command = "python -m build" working_directory = "{{ working_directory }}/repository"
[[actions]] name = "build-docker-image" type = "shell" command = "docker build -t {{ imbi_project.slug }}:latest ." working_directory = "repository:///" ```
Code Quality Tools¶
```toml [[actions]] name = "run-linter" type = "shell" command = "ruff check --fix src/" working_directory = "repository:///"
[[actions]] name = "format-code" type = "shell" command = "ruff format src/ tests/" working_directory = "repository:///"
[[actions]] name = "type-check" type = "shell" command = "mypy src/" working_directory = "repository:///" ```
Git Operations¶
```toml [[actions]] name = "get-current-version" type = "shell" command = "git describe --tags --abbrev=0" working_directory = "repository:///"
[[actions]] name = "list-changed-files" type = "shell" command = "git diff --name-only {{ starting_commit }} HEAD" working_directory = "{{ working_directory }}/repository" ```
Environment Setup¶
```toml [[actions]] name = "setup-python-env" type = "shell" command = """ python -m venv .venv && \ .venv/bin/pip install --upgrade pip setuptools wheel """ working_directory = "repository:///"
[[actions]] name = "setup-node-env" type = "shell" command = "npm ci" working_directory = "repository:///" ```
Advanced Template Examples¶
Using Imbi Project Data¶
toml
[[actions]]
name = "project-specific-command"
type = "shell"
command = """
echo "Processing {{ imbi_project.name }}"
echo "Type: {{ imbi_project.project_type }}"
echo "Namespace: {{ imbi_project.namespace }}"
"""
Conditional Logic with Jinja2¶
toml
[[actions]]
name = "environment-specific-deploy"
type = "shell"
command = """
{% if imbi_project.project_type == 'api' %}
python deploy_api.py
{% elif imbi_project.project_type == 'consumer' %}
python deploy_consumer.py
{% else %}
echo "Unknown project type"
{% endif %}
"""
working_directory = "repository:///"
Using Project Facts¶
toml
[[actions]]
name = "language-specific-test"
type = "shell"
command = """
{% if imbi_project.facts.get('Programming Language') == 'Python 3.12' %}
pytest tests/ --python=3.12
{% elif imbi_project.facts.get('Programming Language') == 'Python 3.11' %}
pytest tests/ --python=3.11
{% endif %}
"""
working_directory = "repository:///"
Iterating Over Lists¶
toml
[[actions]]
name = "install-dependencies"
type = "shell"
command = """
{% for dep in dependencies %}
pip install {{ dep }}
{% endfor %}
"""
working_directory = "repository:///"
Path Resolution¶
Working directory supports all ResourceUrl schemes:
```toml
Repository directory¶
[[actions]] type = "shell" command = "ls -la" working_directory = "repository:///"
Workflow directory¶
[[actions]] type = "shell" command = "cat templates/README.md" working_directory = "workflow:///"
Extracted files directory¶
[[actions]] type = "shell" command = "find . -name '*.conf'" working_directory = "extracted:///"
Explicit working directory path¶
[[actions]] type = "shell" command = "pwd" working_directory = "{{ working_directory }}/repository" ```
Command Output¶
Captured Output¶
- stdout: Logged at DEBUG level
- stderr: Logged at DEBUG level
- Exit Code: Non-zero exit codes cause workflow failure (unless
ignore_failure = true)
Output in Logs¶
```python
Logger output example:¶
DEBUG: Executing shell command: pytest tests/ -v DEBUG: Command stdout: ===== test session starts ===== DEBUG: Command stderr: DEBUG: Command exit code: 0 ```
Error Handling¶
Exit Code Handling¶
```toml
Fail workflow on error (default)¶
[[actions]] name = "critical-command" type = "shell" command = "important-operation"
Fails workflow if exit code != 0¶
Continue on error¶
[[actions]] name = "optional-command" type = "shell" command = "optional-operation" ignore_errors = true # Continues even if exit code != 0 ```
Command Not Found¶
```toml [[actions]] name = "missing-command" type = "shell" command = "nonexistent-command"
Raises FileNotFoundError: Command not found: nonexistent-command¶
```
Security Considerations¶
Command Injection Prevention¶
Template variables are NOT shell-escaped automatically. Be cautious with user-provided data:
```toml
UNSAFE - if imbi_project.name contains shell metacharacters¶
[[actions]] type = "shell" command = "echo {{ imbi_project.name }}"
SAFER - use quotes¶
[[actions]] type = "shell" command = "echo '{{ imbi_project.name }}'"
SAFEST - avoid untrusted input in shell commands¶
```
Environment Variables¶
Commands execute with the same environment as the workflow process. Note that environment variables in the command string itself are NOT expanded (no shell):
toml
[[actions]]
name = "use-env-var"
type = "shell"
command = "bash -c 'echo $HOME && echo $USER'" # Need bash -c for shell features
Performance Tips¶
Chaining Commands¶
Important: Shell operators require wrapping the command in bash -c or sh -c:
```toml
Use && for dependent commands (fail fast):¶
[[actions]] type = "shell" command = "bash -c 'cd repository && make build && make test'"
Use ; for independent commands (always run all):¶
[[actions]] type = "shell" command = "bash -c 'make clean; make build; make test'" ```
Background Processes¶
Not recommended - commands block until completion. For long-running operations, consider using Docker actions instead.
Implementation Notes¶
- Commands execute in a subprocess using
asyncio.create_subprocess_exec - No shell by default: Commands are parsed with
shlex.split()and executed directly - Shell features require explicit shell: Use
bash -c '...'orsh -c '...'for: - Pipes (
|), redirects (>,<), wildcards (*) - Command chaining (
&&,||,;) - Environment variable expansion (
$VAR) - Built-in shell commands (
cd,export, etc.) - Working directory resolved before command execution via
utils.resolve_path() - Template rendering occurs before command execution
- Commands are parsed as shell-like arguments (respecting quotes and escapes)
- Default timeout of 1 hour (configurable via
timeoutfield, see Action Timeouts) - On timeout: Process terminated gracefully (SIGTERM → SIGKILL)
- stdout and stderr captured and logged at DEBUG level
- Non-zero exit codes raise
subprocess.CalledProcessError(unlessignore_errors=true)