Actio
Macros

soft_fail

Tolerate failure on a single step without failing the job — for any exit code, or only the ones you name.

Sometimes a step is allowed to fail. GitHub's built-in answer is continue-on-error: true, which tolerates any non-zero exit. But often you only want to forgive specific exit codes (a linter's "issues found" 42, say) while still failing hard on a real crash. soft_fail covers both: true is the native all-or-nothing tolerance, and a list of exit codes is a Buildkite-style selective tolerance that nothing in raw Actions YAML can express.

soft_fail tolerates a failure and moves on. When you instead need to react to one — run a recovery step, notify, or retry on another runner — use fallback.

true — tolerate any failure

soft_fail: true compiles straight to continue-on-error: true. The step still shows as failed, but the job continues. Works on run and uses steps alike.

.actio.yml
name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: ./lint.sh
        soft_fail: true
generated .yml
name: CI
on:
  - push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: ./lint.sh
        continue-on-error: true
.actio.yml
name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: ./lint.sh
        soft_fail: true
generated .yml
name: CI
on:
  - push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: ./lint.sh
        continue-on-error: true

A list — tolerate only named exit codes

A list of exit codes wraps a run step in a build-time shell wrapper that maps only the listed codes to success (the step shows green) and re-exits any other code unchanged.

.actio.yml
name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: ./flaky-check.sh
        soft_fail: [0, 42]
generated .yml
name: CI
on:
  - push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: |-
          __actio_sf_file="$(mktemp)"
          printf '%s\n' './flaky-check.sh' > "$__actio_sf_file"
          __actio_sf_code=0
          bash --noprofile --norc -eo pipefail "$__actio_sf_file" || __actio_sf_code=$?
          rm -f "$__actio_sf_file"
          case "$__actio_sf_code" in 0|42) exit 0 ;; *) exit "$__actio_sf_code" ;; esac
        shell: bash
.actio.yml
name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: ./flaky-check.sh
        soft_fail: [0, 42]
generated .yml
name: CI
on:
  - push
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - run: |-
          __actio_sf_file="$(mktemp)"
          printf '%s\n' './flaky-check.sh' > "$__actio_sf_file"
          __actio_sf_code=0
          bash --noprofile --norc -eo pipefail "$__actio_sf_file" || __actio_sf_code=$?
          rm -f "$__actio_sf_file"
          case "$__actio_sf_code" in 0|42) exit 0 ;; *) exit "$__actio_sf_code" ;; esac
        shell: bash

The wrapper runs your script in a fresh child shell with the same strict flags GitHub uses, so multi-line scripts, set -e / pipefail fail-fast, and explicit exit N all behave exactly as they would normally — only the final exit code is remapped.

Type

Prop

Type

Gotchas

Lists need a POSIX shell on a run step

The list form is supported on bash, sh, and pwsh run steps. On any other shell, or on a uses step, a list is a compile error — use soft_fail: true there instead.

See the soft_fail entry in the syntax reference for the exact wrapper semantics.

On this page