fix(actions): dynamic matrix expansion correctness fixes (#38690)

Follow-up to https://github.com/go-gitea/gitea/pull/36564 (dynamic
matrix) and https://github.com/go-gitea/gitea/pull/36357 (max-parallel),
fixing issues found reviewing the two features together.

- **A placeholder could stall its run forever.** Its payload keeps the
raw matrix but loses its `needs`, so `ParseJob` re-expanded it instead
of reading it back — fatal for `include: ${{ fromJson(needs.*.outputs.*)
}}`.
- **An `if:` reading `matrix.*` skipped the whole job**, with or without
the `${{ }}`. It now reduces to the needs gate, except under
`always()`/`failure()`/`cancelled()`, and each combination is decided on
its own values once the matrix expands.
- **Dependents could be skipped before the combinations ran**, since
inserted siblings are absent from the resolver's job set. The pass now
stops after an insert and defers to the re-emit it schedules.
- **Expansion failures stranded the placeholder.** A retryable one is
returned so the queue retries it; a malformed payload fails the job
instead of requeueing forever.
- **Rerun could rewind a pass-through row** into a raw placeholder
keeping its old terminal status, which nothing expands. Now gated on the
anchor itself.

Plus: `max-parallel` distinguishes an unevaluated `${{ }}` (debug) from
a non-numeric literal (warn — it silently drops the cap).

Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
bircni
2026-07-30 09:13:47 +00:00
committed by GitHub
co-authored by Zettat123 silverwind
parent e80a62f555
commit 11d0ed699b
17 changed files with 653 additions and 94 deletions
+10 -1
View File
@@ -6,6 +6,7 @@ package actions
import (
"math"
"strconv"
"strings"
actions_model "gitea.dev/models/actions"
"gitea.dev/modules/log"
@@ -20,7 +21,15 @@ func parseMaxParallel(jobID, maxParallelString string) int {
}
maxParallel, err := strconv.ParseFloat(maxParallelString, 64)
if err != nil || math.IsNaN(maxParallel) {
log.Debug("job %s: unsupported max-parallel value %q, treating as unlimited", jobID, maxParallelString)
// Both fall back to unlimited, but an expression is a gap in Gitea while a non-number is the
// author's mistake, so dropping the cap must not be reported the same way.
if strings.Contains(maxParallelString, "${{") {
// TODO: evaluate it against the contexts `if:` and the matrix already resolve, so that an
// expression can actually cap a job instead of quietly disabling the cap.
log.Debug("job %s: max-parallel %q is an expression, which is not evaluated yet: treating as unlimited", jobID, maxParallelString)
} else {
log.Warn("job %s: max-parallel %q is not a number, treating as unlimited", jobID, maxParallelString)
}
return 0
}
// a run can never hold more jobs than MaxJobNumPerRun, so clamping there keeps the cast total