feat: run steps in parallel with background, wait, wait-all, cancel and parallel - #6161
Draft
McNultyyy wants to merge 4 commits into
Draft
feat: run steps in parallel with background, wait, wait-all, cancel and parallel#6161McNultyyy wants to merge 4 commits into
McNultyyy wants to merge 4 commits into
Conversation
…cess Three data races that are reachable today, with no behaviour change: * ptyWriter.AutoStop is written by the goroutine running the command once it finished, while the goroutine copying the pty output reads it on every write. It becomes an atomic.Bool. The new test reproduces this reliably under -race. * containerReference.ReplaceLogWriter swaps two fields that the goroutines copying container output read concurrently, and HostEnvironment.ReplaceLogWriter swaps StdOut while a command is running. Both are now guarded, and the readers take a consistent snapshot through an accessor. * RunContext.Masks is appended to by a running step while the log formatter iterates it for every emitted line, and composite run contexts aliased the parent's slice and appended to it. Appends and reads now take a lock, and composite contexts copy. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every step wrote its file commands to the same paths in the job container: workflow/outputcmd.txt, statecmd.txt, pathcmd.txt, envs.txt and SUMMARY.md. A step therefore read whatever the previously executed step had left in them. The visible consequence is composite actions leaking outputs. GitHub scopes an output written by a composite action's inner step to that inner step, and exposes only the outputs the action declares. In act the inner step's writes to $GITHUB_OUTPUT were still sitting in the shared file when the enclosing job step processed it, so they appeared as outputs of the step that used the action. Each step execution now gets its own directory. The file commands are also recorded against the id of the step they belong to instead of the mutable CurrentStep field, and composite script names are derived from the stable id of the step the composite runs for rather than from CurrentStep. The new composite-undeclared-outputs fixture covers a plain and a nested composite action, over both $GITHUB_OUTPUT and ::set-output. It fails on master and passes here. Refs nektos#2184, nektos#2697, nektos#2553 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ommand-dirs' into act-split/6-parallel-steps
…nd parallel GitHub shipped step level parallelism in June 2026. act rejects the keys as unknown properties, so a workflow using them cannot be run locally at all. Model and schema learn the six member `steps-item` one-of that GitHub's own `workflow-v1.0.json` defines: `background` on run and regular steps, `wait` as string-or-sequence, `wait-all` as null-or-boolean, `cancel`, and `parallel`. At run time a per job registry tracks background steps behind a ten slot semaphore. `expandParallelStepGroups` desugars a `parallel:` block into background steps plus a synthesized `wait` for the group, so the group has an implicit join without a second execution path. A background step's failure is taken by the first `wait` that includes it, cancelled steps do not fail the job, and anything still running when the job ends is stopped. Steps now genuinely overlap, so the job state they share needs guarding: expression evaluators read env and step result snapshots, the runner file command directories are already per execution, and log writers travel on the context instead of being swapped into the execution environment's single global slot. That last part is why `newCompositeCommandExecutor` publishes its handler through `container.WithLogWriters` as well - with the context writers in place, replacing the writer slot no longer has any effect, and a composite action's inner `::set-output` would otherwise be attributed to the outer step. Composite actions still reject these keys, matching GitHub's action schema. Closes nektos#6124
This was referenced Aug 17, 2026
McNultyyy
added a commit
to McNultyyy/act
that referenced
this pull request
Aug 17, 2026
The composite suite and both background-step fixtures pass under docker, and the three races the detector reports reproduce identically on a clean master clone - they are the known nektos#6028 GoGitActionCache race, not anything this PR adds. Also records that TestActionCache is flaky enough to fake a regression on a single baseline run, so baseline twice before believing a failure-set delta.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #6124.
GitHub shipped step level parallelism in June 2026. act rejects the keys as unknown properties, so a workflow using them cannot be run locally at all — the reporter of #6124 gets a schema error before anything executes.
What it adds
background: trueon run and regular steps;waitas string-or-sequence;wait-allas null-or-boolean;cancel; andparallel.This is upstream GitHub syntax, not an act extension —
actions/languageservicesworkflow-v1.0.jsondefinessteps-itemas the same six member one-of, with those exact value types. Composite actions still reject all of it, because GitHub's ownaction-v1.0.jsondoes too; that is fidelity, not a gap.How it works
A per job registry tracks background steps behind a ten slot semaphore, matching GitHub's limit.
expandParallelStepGroupsdesugars aparallel:block into background steps plus a synthesizedwaitfor the group, so a group is not a second execution path — it is the same one, spelled shorter. A background step's failure is taken by the firstwaitthat includes it; deliberately cancelled steps do not fail the job; anything still running when the job ends is stopped.Steps now genuinely overlap, so the state they share needs guarding. Expression evaluators read env and step-result snapshots rather than the live maps, and log writers travel on the context instead of being swapped into the execution environment's single global writer slot.
That last part is the subtle one. Once context writers take precedence,
JobContainer.ReplaceLogWritersilently stops having any effect.newCompositeCommandExecutorused it to install the handler that parses a composite action's inner::set-output, so without the matching change those outputs get attributed to the outer step. This PR moves that call site — and the two other capture sites inexpression.goandrun_context.go— ontocontainer.WithLogWriters. I mention it because it is exactly the kind of change that looks unrelated in a diff and is not.Testing
TestExpandParallelStepGroupsplus new cases for nesting rejection and for expansion not writing through to the job model.background-steps-containerand a newbackground-steps-matrixfixture (twoparallelgroups and a loose background step across three matrix entries) are in theTestRunEventtable.Verified before opening:
uses-composite,uses-nested-composite,composite-fail-with-output,act-composite-env-test,do-not-leak-step-env-in-composite,outputs,composite-undeclared-outputs— all green under docker. This is the acceptance gate for the log-writer change.go test -race ./pkg/runner/in a linux container reports three races, all inGoGitActionCache(GoGitActionCache race condition with concurrent matrix jobs #6028) and all reproducing identically on a cleanmastercheckout. Nothing in the code this PR adds.-shortfailure set matches amasterbaseline exactly.TestActionCache/Fetch_HEADandFetch_Shaare flaky run to run on master as well.Notes for review
&inside arunstep still dies with the exec. This is a different, opt-in mechanism that requires rewriting the workflow.parallelgroup members assteps-item, as GitHub does, which means a nestedparallel:or await:inside a group validates. Since neither has a meaning as a background step, the expansion rejects them with a named error rather than dropping them silently. Happy to narrow the schema instead if you would rather it fail at validation time.parallel-steppermitsname/idwhere GitHub's permits onlyparallel. The synthesized wait step reuses the group's id, so matching GitHub exactly would need a second id scheme — I did not think that was worth it, but say the word.fmt.Sprintf("%d", i)convention and land on the index the step ends up at.