From 38711f2696e46db66e72761a8d418c547cdb0514 Mon Sep 17 00:00:00 2001 From: Giteabot Date: Thu, 28 May 2026 23:40:50 -0700 Subject: [PATCH] fix(actions): exclude `workflow_call` from workflow trigger detection (#37894) (#37899) Backport #37894 by @Zettat123 Gitea now only allows `workflow_dispatch.inputs`. If a workflow contains `workflow_call.inputs`, the workflow cannot be triggered, even though the `on:` section contains other trigger events. https://github.com/go-gitea/gitea/blob/428ee9fcce7928bf5405900345d43e9ba1b01564/modules/actions/jobparser/model.go#L402-L405 For example, this workflow cannot be triggered due to `workflow_call.inputs`: ```yaml on: push: pull_request: workflow_call: inputs: name: type: string ``` --- This PR is extracted from #37478 for backport Co-authored-by: Zettat123 Co-authored-by: silverwind Co-authored-by: Claude (Opus 4.8) --- modules/actions/jobparser/model.go | 12 +++++++ modules/actions/jobparser/model_test.go | 47 +++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/modules/actions/jobparser/model.go b/modules/actions/jobparser/model.go index 7132c278e9..eb17a43b01 100644 --- a/modules/actions/jobparser/model.go +++ b/modules/actions/jobparser/model.go @@ -298,6 +298,9 @@ func toGitContext(input map[string]any) *model.GithubContext { return gitContext } +// workflowCallEvent is only fired by another workflow's `uses:`, so it is excluded from trigger detection. +const workflowCallEvent = "workflow_call" + func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) { switch rawOn.Kind { case yaml.ScalarNode: @@ -306,6 +309,9 @@ func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) { if err != nil { return nil, err } + if val == workflowCallEvent { + return []*Event{}, nil + } return []*Event{ {Name: val}, }, nil @@ -319,6 +325,9 @@ func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) { for _, v := range val { switch t := v.(type) { case string: + if t == workflowCallEvent { + continue + } res = append(res, &Event{Name: t}) default: return nil, fmt.Errorf("invalid type %T", t) @@ -332,6 +341,9 @@ func ParseRawOn(rawOn *yaml.Node) ([]*Event, error) { } res := make([]*Event, 0, len(events)) for i, k := range events { + if k == workflowCallEvent { + continue + } v := triggers[i] switch v.Kind { case yaml.ScalarNode: diff --git a/modules/actions/jobparser/model_test.go b/modules/actions/jobparser/model_test.go index d0e8204161..7b0be62c26 100644 --- a/modules/actions/jobparser/model_test.go +++ b/modules/actions/jobparser/model_test.go @@ -254,6 +254,53 @@ func TestParseRawOn(t *testing.T) { }, }, }, + { + // `workflow_call` is only fired by another workflow's `uses:`, so ParseRawOn intentionally excludes it from trigger detection. + input: `on: + workflow_call: + inputs: + env: + type: string + required: true + outputs: + sha: + value: ${{ jobs.build.outputs.commit }} + secrets: + DEPLOY_KEY: + required: true +`, + result: []*Event{}, + }, + { + // Mixed: a workflow that is both callable AND triggered by push. Only the "push" event surfaces. + input: `on: + workflow_call: + inputs: + env: + type: string + push: + branches: [main] +`, + result: []*Event{ + { + Name: "push", + acts: map[string][]string{"branches": {"main"}}, + }, + }, + }, + { + // Scalar form: a purely reusable workflow has no event triggers. + input: "on: workflow_call", + result: []*Event{}, + }, + { + // Sequence form: `workflow_call` is excluded while sibling events are kept. + input: "on:\n - push\n - workflow_call\n - pull_request", + result: []*Event{ + {Name: "push"}, + {Name: "pull_request"}, + }, + }, } for _, kase := range kases { t.Run(kase.input, func(t *testing.T) {