Chris Collins

Tools · dev

GitHub Actions workflow explainer

Paste a GitHub Actions workflow: the job graph with a what-if-this-fails simulator, every matrix job after include/exclude, triggers with a branch/tag/path filter tester and next cron runs, a ${{ }} expression evaluator with an editable context, GITHUB_TOKEN permissions per job, concurrency groups, and actionlint's checks plus security checks.

Added

How to use it

I read a lot of other people’s workflows, and the questions are always the same: which jobs does this matrix make, will a push to this branch run it, why was that job skipped, and is the pull_request_target in here safe. This page answers them from the YAML.

How to use it

Paste a workflow (.github/workflows/.yml), open a file, or pick an example: Go, Rust, Java with Gradle, a Node matrix, a reusable workflow with a composite action, an unsafe pull request helper, and a deliberately broken one. If the workflow uses local actions (uses: ./.github/actions/x) or calls a reusable workflow (uses: ./.github/workflows/y.yml), add those files under *Other files with their paths and they are checked too.

  • Lints — what actionlint reports, apart from its shellcheck and pyflakes passes: unknown keys, ${{ }} syntax and functions, contexts used where GitHub doesn’t allow them, matrix., steps., needs. and inputs. references that don’t exist, needs cycles, event names and activity types, cron syntax and the five-minute minimum, runner labels, inputs of popular actions and of your local actions and reusable workflows, permission scopes, deprecated ::set-output, and an if: that is always true because of text around ${{ }}. Then the security checks: script injection from untrusted github.event fields in run: and actions/github-script, pull_request_target or workflow_run checking out the pull request’s code, third-party actions pinned to a tag or branch (with the gh api command that finds the commit SHA to pin to), missing or broad permissions, secrets: inherit and self-hosted runners on pull requests. Click a position to jump to the line.

  • Jobs & matrix — the needs graph. Click a job to make it fail (or be cancelled) and see which jobs still run, with if: evaluated against needs.<job>.result and the implicit success() &&. Each job lists every matrix combination with its display name, which ones came from include, which exclude removed, and what fail-fast and max-parallel do.

  • Triggers — each event explained, activity types (including the defaults for pull requests), filters, the next runs of each schedule (in its timezone, linked to the cron tool), the workflow_dispatch form with a matching gh workflow run command, and Would it run?: give an event, a ref and the changed files and it says yes or no, and why.

  • Expressions — evaluate any expression against a context built from the workflow (github and a sample event payload, env, vars, secrets, inputs, matrix, needs, steps, runner, job, strategy) that you can edit, and see every ${{ }} in the file evaluated with it. Secrets are masked as *.

  • Permissions — the effective GITHUB_TOKEN scopes for each job, given the repository’s default setting, the event, and whether the pull request comes from a fork. Then each concurrency group with its value for the chosen context.

How it matches GitHub

The expression evaluator follows GitHub’s documented rules: strings compare without regard to case, values of different types are compared as numbers ('' == 0, null == 0, '1' == 1), NaN equals nothing, arrays and objects are equal only to themselves, && and || return one of their operands (so cond && 'a' || 'b' works as a ternary), and .* filters arrays. The matrix expansion follows the documented include algorithm: an include entry is added to every original combination it doesn’t contradict, and becomes a new combination when it fits none.

I compared the lints with actionlint 1.7.12 on 400 real workflow files: it reports 364 problems and this page finds 357 of them on the same line. The other seven need actionlint’s type inference (comparing an object to a string) or a self-hosted label list. The YAML parser gave the same values as js-yaml on all of them. Things this page can’t know: whether an action or a tag exists, a repository’s own settings, and the contents of files that hashFiles() would hash — it returns a made-up hash.

The runner labels, webhook activity types and the inputs of popular actions come from actionlint’s data (MIT licence).

Browser support

Any current browser. Nothing to install.

Privacy

Everything runs in this page. The workflow and files you paste are kept in this browser’s local storage so they are still there next time; nothing is uploaded. Share link puts the workflow in the link itself.

Runs entirely in your browser; nothing you type leaves this page.