# Conditional Steps and Jobs — GitHub Actions

Source: https://www.skillbyai.com/en/github-actions/m-if

> Run only when appropriate.

## if with status and event checks

`if:` on a job or step decides whether it runs. Combine event and ref checks (deploy only on pushes to main), labels, matrix values and status functions: `success()` (the default), `failure()`, `always()` and `cancelled()`. Prefer `!cancelled()` over `always()` for cleanup that should not run on cancellation. Conditions keep one workflow flexible, but too many make it hard to follow; split into separate workflows when logic grows.

## A tag-only release job, linted

I checked this workflow with actionlint 1.7.12 (`actionlint -oneline .github/workflows/perms.yml`); the output and exit code are copied from that run. actionlint validates syntax, expressions, job dependencies and known actions offline; shellcheck was not installed, so shell scripts inside run: were not linted. The workflow was not executed on GitHub. The job runs only for tags; actionlint flags the permission scope pull-request, which does not exist (the correct scope is pull-requests), and lists the valid scopes.

```yaml
name: Release

on:
  push:
    tags: ["v*"]

permissions:
  contents: write
  pull-request: write

jobs:
  release:
    runs-on: ubuntu-latest
    if: ${{ github.ref_type == 'tag' }}
    steps:
      - uses: actions/checkout@v4
      - run: echo "Releasing ${{ github.ref_name }}"
```

Output:

```
.github/workflows/perms.yml:9:3: unknown permission scope "pull-request". all available permission scopes are "actions", "artifact-metadata", "attestations", "checks", "contents", "deployments", "discussions", "id-token", "issues", "models", "packages", "pages", "pull-requests", "repository-projects", "security-events", "statuses" [permissions]
(exit code 1)
```

## Upload test reports on failure

A step with if: ${{ failure() }} can upload logs and screenshots exactly when you need them.

**Quiz:** Which status function runs a step even when earlier steps failed but not when the run is cancelled?

- [ ] success()
- [x] !cancelled()
- [ ] always() only
- [ ] never()

*Answer:* !cancelled(). Cleanup without running on cancellation.
