Skip to content

[FEAT] Add support for dynamic matrix evaluation in Gitea Actions workflows#36564

Draft
ZPascal wants to merge 12 commits intogo-gitea:mainfrom
ZPascal:add-matrix-dynamic-job-input-support
Draft

[FEAT] Add support for dynamic matrix evaluation in Gitea Actions workflows#36564
ZPascal wants to merge 12 commits intogo-gitea:mainfrom
ZPascal:add-matrix-dynamic-job-input-support

Conversation

@ZPascal
Copy link
Copy Markdown
Contributor

@ZPascal ZPascal commented Feb 9, 2026

Add support for dynamic matrix evaluation in Gitea Actions workflows

Description

This pull request adds support for dynamic matrix evaluation based on job outputs in Gitea Actions workflows. Workflows can now expand matrix strategies using outputs from dependent jobs, enabling more flexible and dynamic CI/CD pipelines.

The problem

Currently, workflow matrix strategies are evaluated statically at workflow creation time. There is no way to dynamically generate matrix values based on job outputs, which limits the flexibility of CI/CD workflows.

The solution

The implementation adds:

  • Matrix re-evaluation: When a job has a matrix strategy that depends on outputs from dependent jobs (using needs.<job>.outputs), the matrix is re-evaluated once those jobs complete
  • Performance tracking: Metrics collection for matrix re-evaluation operations to monitor performance and identify issues

Features

Supported workflow patterns

jobs:
  generate:
    outputs:
      matrix: ${{ steps.create-matrix.outputs.result }}
    steps:
      - id: create-matrix
        run: echo "result=[1,2,3]" >> $GITHUB_OUTPUT

  build:
    needs: [generate]
    strategy:
      matrix:
        version: ${{ fromJson(needs.generate.outputs.matrix) }}
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building version ${{ matrix.version }}"

Prometheus metrics

When metrics are enabled, the following metrics are available at /metrics:

  • gitea_matrix_total_reevaluations - Total re-evaluation attempts
  • gitea_matrix_successful_reevaluations - Successful expansions
  • gitea_matrix_failed_reevaluations - Failed expansions
  • gitea_matrix_deferred_reevaluations - Evaluations waiting for dependencies
  • gitea_matrix_jobs_created_total - Total jobs created from matrix expansion
  • gitea_matrix_total_reevaluation_time_ms - Cumulative evaluation time
  • gitea_matrix_avg_reevaluation_time_ms - Average evaluation time
  • gitea_matrix_total_parse_time_ms - Cumulative parse time
  • gitea_matrix_avg_parse_time_ms - Average parse time
  • gitea_matrix_total_insert_time_ms - Cumulative database insert time
  • gitea_matrix_avg_insert_time_ms - Average insert time
  • gitea_matrix_success_rate_percent - Evaluation success rate (0-100%)

Backward compatibility

  • Fully backward compatible: existing workflows without a dynamic matrix continue to work unchanged
  • The new fields are optional and only used when matrix depends on job outputs
  • No changes to existing API or workflow syntax

Testing

  • Integration test demonstrates full workflow with dynamic matrix expansion
  • Tests verify correct job creation and execution order
  • Performance metrics are collected and available for monitoring

Checklist

  • Database migration added and tested
  • Workflow parsing implemented
  • Integration tests passing
  • Documentation updated (code comments)
  • No breaking changes
  • Backwards compatible
  • The PR contains AI-generated lines of code
  • Test the change in a dev environment

Reviewer notes

  • Matrix re-evaluation is triggered in the job resolution loop when dependent jobs complete
  • The implementation properly handles edge cases such as circular dependencies and missing outputs
  • Performance impact is minimal with metrics collection disabled by default
  • Metrics can be viewed in Prometheus when enabled with [metrics].ENABLED = true

Depends on:

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Feb 9, 2026
@github-actions github-actions bot added modifies/go Pull requests that update Go code modifies/migrations labels Feb 9, 2026
@ZPascal ZPascal force-pushed the add-matrix-dynamic-job-input-support branch 5 times, most recently from 5c73067 to aff60be Compare February 9, 2026 20:55
@ZPascal ZPascal marked this pull request as ready for review February 9, 2026 20:55
@ZPascal ZPascal force-pushed the add-matrix-dynamic-job-input-support branch 2 times, most recently from 217663a to bdb54c6 Compare February 9, 2026 21:38
@silverwind silverwind requested a review from Copilot February 11, 2026 18:15
@silverwind
Copy link
Copy Markdown
Member

Tests are failing. And please stop force-pushing, we want to see the history of changes.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for dynamic GitHub Actions-style matrix expansion in Gitea Actions by deferring matrix evaluation until upstream needs.<job>.outputs are available, and exposes related Prometheus metrics for observability.

Changes:

  • Persist per-job raw strategy YAML + an is_matrix_evaluated flag so matrix expansion can be deferred until dependencies complete.
  • Re-evaluate/expand matrix jobs during the job resolution loop once needs jobs have finished.
  • Add Prometheus collector + tests and an integration test demonstrating dynamic matrix expansion.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
tests/integration/actions_job_test.go Adds an integration test validating matrix expansion from a producer job’s outputs.
services/actions/run.go Passes workflow content to insertion and stores raw strategy for deferred matrix evaluation.
services/actions/matrix.go Implements raw strategy extraction and matrix re-evaluation/DB job creation.
services/actions/job_emitter.go Hooks matrix re-evaluation into the blocked-job resolver and adds logging + job reload.
services/actions/matrix_metrics.go Adds in-memory metrics tracking for re-evaluation performance.
services/actions/matrix_metrics_prometheus.go Exposes the above stats via a Prometheus collector.
services/actions/matrix_metrics_test.go Adds unit tests and a benchmark for the Prometheus collector.
routers/web/web.go Registers the matrix metrics collector when metrics are enabled.
models/actions/run_job.go Adds RawStrategy + IsMatrixEvaluated fields and a helper to insert multiple jobs.
models/migrations/v1_26/v326.go Adds DB columns for raw strategy storage and matrix evaluation state.
models/migrations/migrations.go Registers the new migration (v326).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// If no new jobs were created, mark as evaluated
if len(newJobs) == 0 {
job.IsMatrixEvaluated = true
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If no valid expanded jobs are produced (len(newJobs)==0), the code marks job.IsMatrixEvaluated in-memory only. Persist that flag (or mark the job terminal) to avoid re-running expansion every resolver iteration.

Suggested change
job.IsMatrixEvaluated = true
job.IsMatrixEvaluated = true
if _, err := actions_model.UpdateRunJob(ctx, job, nil, "is_matrix_evaluated"); err != nil {
log.Error("Failed to update job %d is_matrix_evaluated flag after empty matrix expansion: %v", job.ID, err)
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a design fault, parsing the whole workflow again just to set job.IsMatrixEvaluated = true

@ZPascal
Copy link
Copy Markdown
Contributor Author

ZPascal commented Feb 11, 2026

Tests are failing. And please stop force-pushing, we want to see the history of changes.

Hi @silverwind, as I've mentioned in the PR description, it is necessary to review/merge beforehand this PR.

@ChristopherHX
Copy link
Copy Markdown
Contributor

Tests are failing. And please stop force-pushing, we want to see the history of changes.

Hi @silverwind, as I've mentioned in the PR description, it is necessary to review/merge beforehand this PR.

mode draft + temporary act dependency override via go.mod replace has been done multiple times for this to be

  • able to checkout and build the PR
  • be able to check if tests pass generally

Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
@ZPascal ZPascal force-pushed the add-matrix-dynamic-job-input-support branch from bdb54c6 to b000875 Compare February 15, 2026 22:24
@ZPascal ZPascal marked this pull request as draft February 15, 2026 22:24
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
@github-actions github-actions bot added modifies/api This PR adds API routes or modifies them modifies/internal modifies/dependencies labels Feb 15, 2026
@ChristopherHX
Copy link
Copy Markdown
Contributor

ChristopherHX commented Feb 15, 2026

Could you please not force push? This makes reviews harder to follow changes you make

  • use git revert <sha> to undo a change
  • use git pull --rebase= <remote> <branch> to merge changes

@silverwind
Copy link
Copy Markdown
Member

silverwind commented Feb 15, 2026

git pull --rebase=

git merge origin/main should be even better.

Sometimes AI agents like to amend commits and force-push, so it might not be the author doing it, but I recently added instructions to AGENTS.md to not force-push, which may help.

@silverwind
Copy link
Copy Markdown
Member

@ZPascal please also resolve the open copilot review comments above.

@ZPascal
Copy link
Copy Markdown
Contributor Author

ZPascal commented Feb 15, 2026

Could you please not force push? This makes reviews harder to follow changes you make

  • use git revert <sha> to undo a change
  • use git pull --rebase= <remote> <branch> to merge changes

I've used the rebase button of the GH UI.

@ZPascal
Copy link
Copy Markdown
Contributor Author

ZPascal commented Feb 15, 2026

git pull --rebase=

git merge origin/main should be even better.

Sometimes AI agents like to amend commits and force-push, so it might not be the author doing it, but I recently added instructions to AGENTS.md to not force-push, which may help.

I don't like merge commits in a feature branch, and thank you for the hint, but I have not handled the rebase via an AI agent.

@silverwind
Copy link
Copy Markdown
Member

silverwind commented Feb 15, 2026

Ok, just avoid rewriting git history after reviews have been given because reviewers need to see how their points were adressed.

Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Copy link
Copy Markdown
Contributor

@ChristopherHX ChristopherHX left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this is my stale review that nobody has seen)


// constructWorkflowWithNeeds creates a workflow YAML that includes the target job
// and stub definitions for its dependencies so the jobparser can resolve needs.*.outputs expressions
func constructWorkflowWithNeeds(job *actions_model.ActionRunJob, taskNeeds map[string]*TaskNeed) ([]byte, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this is how you use the jobparser, this looks like a hack...

https://gitea.com/gitea/act/pulls/149#issuecomment-1079741

  • The additional parsing is not something I really like...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea to handle the case: The evaluation of the dynamic matrix can be performed before execution starts. @ChristopherHX, what do you think about this idea? Any other ideas?


// If no new jobs were created, mark as evaluated
if len(newJobs) == 0 {
job.IsMatrixEvaluated = true
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a design fault, parsing the whole workflow again just to set job.IsMatrixEvaluated = true


// ExtractRawStrategies extracts strategy definitions from the raw workflow content
// Returns a map of jobID to strategy YAML for jobs that have matrix dependencies
func ExtractRawStrategies(content []byte) (map[string]string, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This answers my question, where you get the RawStrategy...

But this design looks like we parse the workflow also for this again and again.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other option would be to analyze it once and use the raw strategy to evaluate whether a job output should be used for the subsequent matrix jobs.

@silverwind
Copy link
Copy Markdown
Member

silverwind commented Feb 17, 2026

BTW I just noticed the title and PR contains references to "GitHub Actions" but this is for "Gitea Actions". This is a common issue with AI which likes to confuse these names. Correct that and make you actually proof-read the content.

@ZPascal ZPascal changed the title [FEAT] Add support for dynamic matrix evaluation in GitHub Actions workflows [FEAT] Add support for dynamic matrix evaluation in Gitea Actions workflows Feb 17, 2026
@ZPascal
Copy link
Copy Markdown
Contributor Author

ZPascal commented Feb 17, 2026

BTW I just noticed the title and PR contains references to "GitHub Actions" but this is for "Gitea Actions". This is a common issue with AI which likes to confuse these names. Correct that and make you actually proof-read the content.

I've adapted the title and the description.

Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
@TheFox0x7
Copy link
Copy Markdown
Contributor

I have two questions for this as I also touched this topic before:

  1. What's the point of those metrics?
  2. Why are we inserting the job that can't be ran and won't ever be ran into the list of jobs to run and (per comment) changing it's status to skipped? For one it's just not true - it's not skipped, it can't be skipped as it's not runnable in the first place. Secondly now you have a bag of real jobs which you either ran or will run, and fake ones which do nothing.

@silverwind
Copy link
Copy Markdown
Member

silverwind commented Feb 18, 2026

Please run make tidy to fix the license file conflict coming from b9d323c.

Also, check the test and failures.

Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
@Hansehart
Copy link
Copy Markdown

We ran into this exact issue trying to use dynamic matrices in Gitea Actions. After tracing through the source code we confirmed the root cause: the act_runner executes one job at a time via PlanJob(jobID) and only receives the static YAML payload, so needs.<job>.outputs is never resolved when strategy.matrix is evaluated.

Is there anything blocking this from moving forward? Happy to help if that would be useful.

@ZPascal
Copy link
Copy Markdown
Contributor Author

ZPascal commented Apr 8, 2026

We ran into this exact issue trying to use dynamic matrices in Gitea Actions. After tracing through the source code we confirmed the root cause: the act_runner executes one job at a time via PlanJob(jobID) and only receives the static YAML payload, so needs..outputs is never resolved when strategy.matrix is evaluated. Is there anything blocking this from moving forward? Happy to help if that would be useful.

I don't think so. I'll continue today to adapt the code.

ZPascal added 2 commits April 12, 2026 17:46
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
ZPascal added 2 commits April 12, 2026 19:58
Signed-off-by: Pascal Zimmermann <pascal.zimmermann@theiotstudio.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. modifies/api This PR adds API routes or modifies them modifies/dependencies modifies/go Pull requests that update Go code modifies/internal modifies/migrations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants