lifecycle
Attach teardown and outcome-conditional steps to a step, a job, or the whole workflow.
Cleanup and outcome handling in raw YAML means smearing if: always(),
if: failure(), and steps.<id>.outcome guards across your steps and remembering
to give each guarded step an id. The lifecycle hooks — ensure, on-success,
on-failure, and on-abort — declare those steps next to the work they guard, and
Actio stamps in the right condition (and any needed id) at compile time. The same
vocabulary scales up to a workflow-level finally:.
Lifecycle hooks react to an outcome (cleanup, log upload, paging). To recover
from a failure — retry on another runner, or a catch step that changes the result —
see fallback.
Step and job hooks
| Hook | Step-level guard | Job-level guard | Runs when |
|---|---|---|---|
ensure | if: always() | if: always() | always (teardown) |
on-success | success() && steps.<id>.outcome == 'success' | if: success() | target succeeded |
on-failure | !cancelled() && steps.<id>.outcome == 'failure' | if: failure() | target failed |
on-abort | step-level cancellation | if: cancelled() | target was cancelled |
At step scope the hook steps are spliced in right after the target step; at job scope they are appended to the job. Either way the hook key is stripped from the output.
name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: ./start-db.sh
ensure:
- run: ./stop-db.sh
- run: ./build.sh
on-failure:
- run: ./upload-logs.shname: CI
on:
- push
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: ./start-db.sh
- run: ./stop-db.sh
if: always()
- run: ./build.sh
id: actio_test_step_2
- run: ./upload-logs.sh
if: "!cancelled() && steps.actio_test_step_2.outcome == 'failure'"name: CI
on: [push]
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: ./start-db.sh
ensure:
- run: ./stop-db.sh
- run: ./build.sh
on-failure:
- run: ./upload-logs.shname: CI
on:
- push
jobs:
test:
runs-on: ubuntu-latest
steps:
- run: ./start-db.sh
- run: ./stop-db.sh
if: always()
- run: ./build.sh
id: actio_test_step_2
- run: ./upload-logs.sh
if: "!cancelled() && steps.actio_test_step_2.outcome == 'failure'"./stop-db.sh gets if: always(). The guarded ./build.sh step is given a
synthetic id so the on-failure step can key off its outcome before any
continue-on-error remapping.
Workflow teardown (finally)
finally: declares jobs that run after the rest of the workflow, gated on overall
outcome. Actio injects needs: over every real job plus the matching outcome guard.
name: Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
finally:
on-failure:
page:
runs-on: ubuntu-latest
steps:
- run: ./page-oncall.shname: Deploy
on:
- push
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
page:
runs-on: ubuntu-latest
needs:
- deploy
if: failure()
steps:
- run: ./page-oncall.shname: Deploy
on: [push]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
finally:
on-failure:
page:
runs-on: ubuntu-latest
steps:
- run: ./page-oncall.shname: Deploy
on:
- push
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
page:
runs-on: ubuntu-latest
needs:
- deploy
if: failure()
steps:
- run: ./page-oncall.shGotchas
on-abort scope
At step scope on-abort only sees step-level cancellation. Whole-run
cancellation (the user hitting cancel) is a workflow concern — handle it with a
finally.on-abort job, not a step hook.
when: sugar on finally jobs
A teardown job declared directly under finally: may use when: to gate on
another job's result (e.g. when: deploy.failed) instead of an outcome branch
group.
See the lifecycle hooks and finally
entries in the syntax reference for the full guard table.