Skip to content

Add support for parallel steps (background, wait, wait-all, cancel, parallel)#694

Open
devantler wants to merge 3 commits into
rhysd:mainfrom
devantler:support-parallel-steps
Open

Add support for parallel steps (background, wait, wait-all, cancel, parallel)#694
devantler wants to merge 3 commits into
rhysd:mainfrom
devantler:support-parallel-steps

Conversation

@devantler

@devantler devantler commented Jun 25, 2026

Copy link
Copy Markdown

Summary

GitHub released parallel steps on 2026-06-25, adding five new step keys. actionlint currently rejects any workflow that uses them — e.g. unexpected key "background" for step ..., and step must run script with "run" section or run action with "uses" section for wait/wait-all/cancel/parallel steps — which blocks adoption. This teaches the parser about them.

Closes #693

What the feature looks like

steps:
  - name: Start server
    id: server
    run: npm start
    background: true        # run asynchronously, continue immediately
  - wait: server            # wait for one background step (or `wait: [a, b]`)
  - wait-all:               # wait for all preceding background steps
  - cancel: server          # cancel a single background step
  - parallel:               # run a group of steps in parallel
      - run: ./a.sh
      - run: ./b.sh

Changes

Modeled after the spec in workflow-syntax.md:

  • background (boolean) is parsed as a new optional *Bool field on a step (run or action step), mirroring continue-on-error.
  • wait/wait-all, cancel and parallel are modeled as new step execution kinds (ExecWait, ExecCancel, ExecParallel) alongside ExecRun/ExecAction, so they no longer trip the "step must run …" check.
    • wait accepts a single step ID or a list (wait: [a, b]); cancel targets a single step ID (a list is reported as a type error, per the spec).
  • parallel parses its grouped steps recursively (reusing parseSteps), and the visitor in pass.go recurses into them, so every rule still lints the nested steps.
  • Unknown keys on the new step kinds are reported the same way as for run/action steps (e.g. unexpected key "shell" for step to wait for background steps).

Context checking — intentionally deferred (with reason)

actionlint generates its context-availability table (availability.go) from GitHub's contexts.md, and that file does not yet list background/wait/cancel. So there is no published context-availability to validate ${{ }} expressions in those fields against, and adding guessed rules now would risk false positives. Expressions are still parsed (so e.g. background: ${{ ... }} is accepted), and name/if on the new step kinds are checked as before. Once these keys are added to contexts.md, regenerating availability.go plus a small follow-up will cover it.

Test plan

  • New fixtures: testdata/ok/parallel_steps.yaml (valid usage of all five keys, incl. an expression-valued background) and testdata/err/parallel_steps.yaml + .out (unexpected keys on wait/cancel/parallel steps; cancel with a list; and an invalid step nested in parallel, which proves the recursion lints it).
  • Updated the existing golden fixtures whose step-key lists now include background.
  • go test ./... is green (1881 tests); gofmt/go vet/staticcheck clean.

🤖 Generated with Claude Code

devantler and others added 2 commits June 26, 2026 00:04
…arallel)

GitHub released parallel steps on 2026-06-25, introducing five new step keys:
`background`, `wait`, `wait-all`, `cancel`, and `parallel`. Until now actionlint
rejected workflows using them ("unexpected key ..." / "step must run ..."). This
adds parsing and AST support for them:

- `background: true` is parsed as a new optional `*Bool` field on a step (run or
  action step), mirroring `continue-on-error`.
- `wait`/`wait-all`, `cancel`, and `parallel` are modeled as new step execution
  kinds (`ExecWait`, `ExecCancel`, `ExecParallel`) so they no longer trip the
  "step must run ... run/uses" check. `wait` and `cancel` accept a single ID or a
  list of IDs; `parallel` parses its grouped steps recursively, and those nested
  steps are visited by every rule so they keep getting linted.

Expression/context checking for the new fields is intentionally deferred: the
generated context-availability table does not describe them yet, so checking now
would produce false positives. `name`/`if` on the new step kinds are still
checked as before.

Closes rhysd#693

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The GitHub workflow-syntax docs specify that `cancel` "targets a single
background step by its `id`" (unlike `wait`, which is a string or an array).
Parse `cancel` as a single string instead of a string-or-list so that
`cancel: [a, b]` is correctly reported as a type error, and fix the fixture
accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@devantler

devantler commented Jun 25, 2026

Copy link
Copy Markdown
Author

It should be good to merge now. Just verified it against the spec one final time :-)

Thanks for flagging critical oversights @nikosavola!

Comment thread parse.go
Comment thread parse.go
Comment thread parse.go
Address review feedback on the parallel-steps PR:

- `wait-all` waits for all background steps and takes no arguments, so
  report an error when a value is given (e.g. `wait-all: foo`).
- `wait` and `wait-all` in the same step are contradictory (one targets
  specific background steps, the other waits for all); flag it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for parallel steps

2 participants