# Atmos Steps

> Shared Atmos step DSL for workflows, custom commands, hooks, and cast recordings: step types, env, output, working_directory, retry, and native alternatives to shell glue

- Skill: `cloudposse/atmos-steps` (Agent Skill)
- Install (CLI): `npx skillmds@latest add cloudposse/atmos-steps`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cloudposse/atmos-steps/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: cloudposse (https://skillmd.com/u/cloudposse)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cloudposse/atmos-steps

---


# Atmos Steps

Atmos steps are the shared execution DSL used by workflows, custom commands,
hooks (`kind: step`), and cast recordings. When a task involves `steps:`, step
`type`, `working_directory`, `env`, `output`, retries, or hook `with:` payloads,
use this skill together with the surface-specific skill (`atmos-workflows`,
`atmos-custom-commands`, or `atmos-hooks`).

## Core Model

A step is a typed action with native fields. Do not treat steps as a place to
write shell scripts by default. Prefer the Atmos field that expresses the
operation directly:

- Use `working_directory` instead of `cd` for real execution. A `type: simulate` child of a
  `mode: steps` cast may display `cd <directory>` to narrate a directory transition, but it
  never changes the working directory of later real steps.
- Use `env` maps instead of inline `FOO=bar command` or `export`.
- Use `output: none` instead of redirecting to `/dev/null`.
- Use `type: script` instead of heredoc shell snippets like `python3 - <<'PY'`.
- Use `type: workdir` with `source` and `reset` instead of `mkdir`, `rm -rf`,
  and `cp`.
- Use `type: atmos` for Atmos commands instead of shelling out to `atmos ...`
  when the surface supports typed steps.
- Use Atmos Terraform/OpenTofu `rc` configuration instead of hand-managing
  temporary `.terraformrc` or `.tofurc` files.

Shell is still valid for real shell work, but it should be a conscious fallback
when no native step type or field expresses the intent.

## Where Steps Run

Step fields are shared, but defaults differ by surface:

- Workflows default command steps to `type: atmos`.
- Custom command string steps historically behave like shell commands; use
  structured step objects when you need typed behavior.
- Hooks use an envelope plus `with:`. For `kind: step`, the hook's `type`
  selects the step type, while `with:` contains the step-specific fields.
- Cast steps can run nested steps and record their terminal output.

Always load the surface-specific skill for invocation rules, path anchoring, and
template context.

## Common Fields

Most typed steps can use these fields when the step type supports them:

```yaml
steps:
  - name: validate
    type: script
    interpreter: python3
    working_directory: !repo-root .
    env:
      ATMOS_LOGS_LEVEL: warn
    output: none
    retry:
      max_attempts: 3
      delay: 2s
    script: |
      print("ok")
```

Important shared fields:

- `name`: Stable step id for logs, dependencies, outputs, and resume behavior.
- `type`: Registered step handler; the canonical catalog below is the source for authored YAML.
- `command`: Command text for command-running steps.
- `script` and `interpreter`: Inline script body and runtime for `type: script`.
- `working_directory`: Directory for the subprocess or script.
- `env`: Map of environment variables layered onto the step.
- `output`: Output mode: `raw`, `log`, `viewport`, or `none`; `test` groups instead use `failures` or `all`.
- `retry`: Retry policy around the whole step.
- `identity`: Atmos identity used when the step runs.
- `when`: Declarative condition for whether the step runs.
- `needs`: Dependency names for concurrent control steps.
- `timeout`: Duration limit for supported steps.
- `tty` and `interactive`: Terminal handoff for commands that need it.

## Step Types

Use the docs at `website/docs/workflows/workflows/workflow/steps/type.mdx` and
the type-specific files under `website/docs/workflows/workflows/workflow/steps/type/`
as the canonical reference. Current canonical step types include:

- Command and integration: `atmos`, `shell`, `script`, `exec`, `container`,
  `emulator`, `http`, `archive`, `require`, `workdir`, `cast`, `store`.
- Orchestration: `test`, `parallel`, `matrix`, `wait`, `wait-all`, `cancel`.
- Interactive: `input`, `confirm`, `choose`, `filter`, `file`, `write`.
- UI and output: `toast`, `markdown`, `spin`, `table`, `pager`, `format`,
  `join`, `style`, `log`, `junit`, `hint`, `alert`, `say`, `title`, `clear`,
  `linebreak`, `stage`, `sleep`, `env`, `exit`.

Use `webhook` only as the `http` alias and `assert` only as the `require` alias;
document and configure the canonical names unless compatibility requires an alias.

If code and docs disagree, inspect the registered step handlers under
`pkg/runner/step/` and schema constants in `pkg/schema/task.go`.

For smoke tests and integration checks, use [atmos-tests](../atmos-tests/SKILL.md)
for test groups, assertions, parallel dependencies, and matrix cases.

## Environment

Prefer map syntax:

```yaml
env:
  PATH: '{{ env "PWD" }}/../../.context/bin:{{ env "PATH" }}'
  ATMOS_LOGS_LEVEL: warn
```

For custom commands, command-level `env` supports the same map style. Use
template expressions such as `{{ env "PATH" }}` or `.Env` where supported. Use
`valueCommand` only when the value genuinely must come from a command's stdout;
do not move shell string building into `valueCommand`.

## Working Directory

Use `working_directory` at the narrowest useful scope:

```yaml
steps:
  - name: docs
    type: shell
    working_directory: !repo-root .
    command: npm run docs:build
```

Do not use `--chdir` or `cd` for real workflow, custom-command, or step execution when
`working_directory` can express the same thing. In a `mode: steps` cast, a display-only
`type: simulate` `cd <directory>` is the exception: use it to keep the recorded story coherent,
and still configure `working_directory` on every real step. Relative paths must be checked
against the surface's base path rules.

Working-directory resolution is not one rule — it differs by surface, and getting this wrong
silently anchors relative fields (`source`, `destination`, `path`, `files`, `context`, ...) to the
wrong directory instead of erroring:

| Surface | Relative `working_directory` resolves against |
|---|---|
| Custom command's own `working_directory:` (command- or step-level) | Atmos `base_path` — always, whether or not the value starts with `./` |
| A workflow's own `working_directory:` (the workflow-level default), or a `type: shell`/`exec`/`atmos` step's `working_directory:` | Atmos `base_path` — always, whether or not the value starts with `./` |
| An extended/registered step type (`archive`, `file`, `junit`, `workdir`, `container`, ...) with its own step-level `working_directory:` | The current working directory — always, whether or not the value starts with `./` |
| `kind: step`/`kind: steps` hook | The component's own working directory for a bare value or when unset; the current working directory for a dot-prefixed value (`.`, `..`, `./x`, `../x`) — see `atmos-hooks` for the full Dot/Bare rule |

A workflow-level `working_directory:` default still reaches extended step types that leave their
own `working_directory:` unset — it falls back to the same `base_path`-anchored resolution the
workflow-level default already gets for shell/exec/atmos steps.

After `working_directory` is resolved, relative handler fields such as `source`, `destination`,
`path`, `files`, and `context` resolve against that directory. For container builds, `Dockerfile`
resolves relative to the resolved `context`, not directly to `working_directory`.

## Output

Use output modes instead of pipe redirection:

```yaml
steps:
  - name: prepare
    type: atmos
    command: terraform generate varfile vpc -s dev
    output: none
```

`output: none` is for quiet setup. `raw` preserves command output, `log` routes
through Atmos logging, and `viewport` is for richer terminal display.

## Script Steps

Use `type: script` for inline scripts:

```yaml
steps:
  - name: validate-cast
    type: script
    interpreter: python3
    script: |
      from pathlib import Path

      text = Path("website/static/casts/examples/sops-secrets.cast").read_text()
      if "All proofs passed" not in text:
          raise SystemExit("cast validation failed")
```

Do not put `command` on a `script` step. The schema requires `interpreter` and
`script`.

## Workdir Steps

Use `type: workdir` for repeatable scratch directories:

```yaml
steps:
  - name: stage-fixtures
    type: workdir
    path: .context/casts/demo
    source: demo/casts/fixtures
    reset: true
```

This replaces shell sequences that create, delete, and copy directories.

## Hooks

For hooks, the hook envelope controls lifecycle behavior and `with:` is the
step payload. A scaffold template may use this bridge too, but only with
`kind: step` or `kind: steps` and its two generation events:

```yaml
hooks:
  notify:
    kind: step
    type: http
    events: [after.terraform.apply]
    on_failure: warn
    retry:
      max_attempts: 3
    with:
      url: https://example.com/hook
      method: POST
```

Use `atmos-hooks` for hook events, outcome conditions, `on_failure`, and
preflight behavior.

## Verification

When changing step YAML, verify the user-facing command from the directory where
users actually run Atmos. Prefer `working_directory` fields in YAML and command
tool `workdir` settings in tests over shell `cd` or `--chdir`.

