Aggregates multiple job and matrix statuses into a single pass/fail status check.
- 🔒 single dependency (Github's
@actions/corepackage) - 📌 immutable releases — tags are locked via repository rulesets
This quick start shows the smallest complete setup.
- Add your normal CI jobs.
- Add a final
are-we-goodjob that depends on those jobs. - Pass
jobs: ${{ toJSON(needs) }}. - Set
if: always()so the final job runs even when upstream jobs fail.
jobs:
test:
strategy:
matrix:
node: [22, 24]
runs-on: ubuntu-slim
steps:
- run: npm test
are-we-good:
runs-on: ubuntu-slim
needs: [test]
if: always()
steps:
- uses: lowlydba/are-we-good@375b418aa07a163e0614537a3fa5c51e53a757e9 # v1.0.0
with:
jobs: ${{ toJSON(needs) }}Expected result:
are-we-goodproduces a single pass/fail check you can require in branch protection.- A markdown step summary is written by default.
Use allowlists when some jobs are advisory.
jobs:
test:
strategy:
matrix:
node: [22, 24]
runs-on: ubuntu-latest
steps:
- run: npm test
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
are-we-good:
runs-on: ubuntu-latest
needs: [test, lint]
if: always()
steps:
- uses: lowlydba/are-we-good@375b418aa07a163e0614537a3fa5c51e53a757e9 # v1.0.0
with:
jobs: ${{ toJSON(needs) }}
allowed-to-fail: lint
allowed-to-cancel: lintBy default, skipped jobs are accepted for all jobs. To require explicit skip permissions, set allowed-to-skip to a non-empty list.
with:
jobs: ${{ toJSON(needs) }}
allowed-to-skip: docs-only-jobwith:
jobs: ${{ toJSON(needs) }}
summary: "false"Enable runner debug mode in GitHub Actions to emit per-job decision logs.
- Docs: Enable debug logging
| Input | Required | Default | Description |
|---|---|---|---|
| jobs | yes | — | JSON string of job results. Pass ${{ toJSON(needs) }} from the calling workflow. |
| allowed-to-skip | no | "" | Comma-separated list of job names whose skipped result is acceptable. Empty = all jobs may be skipped (wildcard). |
| allowed-to-cancel | no | "" | Comma-separated list of job names whose cancelled result is acceptable. |
| allowed-to-fail | no | "" | Comma-separated list of job names whose failure result is acceptable. |
| summary | no | "true" | Set to "false" to disable the markdown step summary table. |
| Key | Value |
|---|---|
| result | "success" | "failure" |
| are-we-good | "true" | "false" |
| Result | Default behavior | Override input |
|---|---|---|
| success | ✅ always ok | n/a |
| skipped | ✅ ok for all jobs | allowed-to-skip |
| cancelled | ❌ fails | allowed-to-cancel |
| failure | ❌ fails | allowed-to-fail |
- Run this action in a final job.
- Use
needs: [job-a, job-b, ...]. - Use
if: always(). - Pass
jobs: ${{ toJSON(needs) }}.
The usual native approach is a final job with a run: | step that checks ${{ contains(needs.*.result, 'failure') }} and exits 1. That works for the happy path, but it isn't ideal:
- It treats every skipped or cancelled job as a failure unless you manually handle each case with nested conditionals.
- It gives you no visibility: the step produces no output, no per-job breakdown, and no indication of which job caused the failure.
- Once you have matrix jobs, path-filtered jobs, or advisory jobs that are allowed to fail, the if-expression grows into something fragile and hard to review.
are-we-good replaces that pattern with a single action that is easy to read and configure. It handles skipped, cancelled, and failed jobs through explicit allowlists and writes a step summary table with a per-job breakdown. When runner debug mode is on, it emits per-job decision logs so you can trace exactly why a check passed or failed, even if you aren't a GitHub Workflow expert.
It also simplifies branch protection. GitHub requires you to list every required status check by name, and when you run a matrix build those names include the matrix values, so the list grows every time a dimension changes. are-we-good reports a single named check regardless of how many jobs feed into it, which means your branch protection configuration stays stable as your matrix evolves.
The same idea applies to monorepos: jobs filtered by changed paths may be skipped on a given PR yet still appear as required checks. Because are-we-good accepts skipped jobs by default, filtered jobs never block a merge.

