diff --git a/modules/actions/jobparser/jobparser.go b/modules/actions/jobparser/jobparser.go index 6dc75072de..aeeb19f768 100644 --- a/modules/actions/jobparser/jobparser.go +++ b/modules/actions/jobparser/jobparser.go @@ -11,6 +11,7 @@ import ( "gitea.com/gitea/runner/act/exprparser" "gitea.com/gitea/runner/act/model" + "code.gitea.io/gitea/modules/log" "go.yaml.in/yaml/v4" ) @@ -88,10 +89,13 @@ func Parse(content []byte, options ...ParseOption) ([]*SingleWorkflow, error) { // Create an evaluator with access to needs/outputs for matrix evaluation matrixEvaluator := NewExpressionEvaluator(NewInterpeter(id, &evaluatedJob, nil, pc.gitContext, results, pc.vars, pc.inputs)) - // Evaluate the matrix before expanding it + // Evaluate the matrix before expanding it. + // If evaluation fails (e.g. expression references unresolved job outputs), + // continue with the unevaluated matrix — the job will be created as a + // placeholder and re-evaluated by ReEvaluateMatrixForJobWithNeeds later. if evaluatedJob.Strategy != nil && evaluatedJob.Strategy.RawMatrix.Kind != 0 { if err := matrixEvaluator.EvaluateYamlNode(&evaluatedJob.Strategy.RawMatrix); err != nil { - return nil, fmt.Errorf("error evaluating matrix for job %s: %w", id, err) + log.Debug("matrix evaluation deferred for job %s (unresolved expression): %v", id, err) } } diff --git a/services/actions/matrix.go b/services/actions/matrix.go index a71410695c..55cc91e2c6 100644 --- a/services/actions/matrix.go +++ b/services/actions/matrix.go @@ -245,8 +245,8 @@ func ReEvaluateMatrixForJobWithNeeds(ctx context.Context, job *actions_model.Act log.Warn("Skipped nil jobDef at index %d for job %d (JobID: %s)", i, job.ID, job.JobID) continue } - if id == job.JobID { - // Skip the original placeholder — we only want the expanded matrix entries + if id != job.JobID { + // Skip dependency stubs — we only want matrix-expanded entries for the target job continue } needs := jobDef.Needs() @@ -266,7 +266,8 @@ func ReEvaluateMatrixForJobWithNeeds(ctx context.Context, job *actions_model.Act JobID: id, Needs: needs, RunsOn: jobDef.RunsOn(), - Status: actions_model.StatusBlocked, + // All dependency jobs are already done at this point; start as Waiting. + Status: actions_model.StatusWaiting, }) } diff --git a/services/actions/matrix_cache.go b/services/actions/matrix_cache.go index 68f854f867..c1bb0f0a61 100644 --- a/services/actions/matrix_cache.go +++ b/services/actions/matrix_cache.go @@ -6,7 +6,7 @@ package actions import ( "crypto/sha256" "encoding/hex" - "fmt" + "errors" "sync" lru "github.com/hashicorp/golang-lru/v2" @@ -50,14 +50,12 @@ func computeCacheKey(workflowYAML []byte, taskNeeds map[string]*TaskNeed) string h.Write(workflowYAML) // Add outputs in deterministic order (sorted by job ID) - if taskNeeds != nil { - for jobID, need := range taskNeeds { - h.Write([]byte(jobID)) - if need.Outputs != nil { - for k, v := range need.Outputs { - h.Write([]byte(k)) - h.Write([]byte(v)) - } + for jobID, need := range taskNeeds { + h.Write([]byte(jobID)) + if need.Outputs != nil { + for k, v := range need.Outputs { + h.Write([]byte(k)) + h.Write([]byte(v)) } } } @@ -88,7 +86,7 @@ func (c *WorkflowParseCache) Set(key string, value []byte) { // Stats returns cache statistics for monitoring func (c *WorkflowParseCache) Stats() (size int, err error) { if c == nil || c.cache == nil { - return 0, fmt.Errorf("cache not initialized") + return 0, errors.New("cache not initialized") } c.mu.RLock() defer c.mu.RUnlock()