fix(actions): populate github.event for scheduled runs (#38446) (#38452)

Backport #38446 by @silverwind

Scheduled runs stored a `null` event payload, so
`github.event.repository`/`sender`/`organization` were empty in
scheduled workflows. Every other event carries them, and so does GitHub
Actions. `CreateScheduleTask` now synthesizes them.

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
Giteabot
2026-07-14 10:32:08 -07:00
committed by GitHub
parent b969123b7f
commit a98468da30
2 changed files with 42 additions and 7 deletions

View File

@@ -6,16 +6,22 @@ package actions
import (
"context"
"fmt"
"maps"
"time"
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
"gitea.dev/models/organization"
perm_model "gitea.dev/models/perm"
access_model "gitea.dev/models/perm/access"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unit"
user_model "gitea.dev/models/user"
"gitea.dev/modules/json"
"gitea.dev/modules/log"
"gitea.dev/modules/timeutil"
webhook_module "gitea.dev/modules/webhook"
"gitea.dev/services/convert"
)
// StartScheduleTasks start the task
@@ -102,7 +108,19 @@ func startTasks(ctx context.Context) error {
// It creates an action run based on the schedule, inserts it into the database, and creates commit statuses for each job.
func CreateScheduleTask(ctx context.Context, spec *actions_model.ActionScheduleSpec) error {
cron := spec.Schedule
eventPayload := withScheduleInEventPayload(cron.EventPayload, spec.Spec)
// Scheduled runs carry no webhook payload; synthesize what github.event.* expects.
if err := spec.Repo.LoadOwner(ctx); err != nil {
return fmt.Errorf("LoadOwner: %w", err)
}
fields := map[string]any{
"repository": convert.ToRepo(ctx, spec.Repo, access_model.Permission{AccessMode: perm_model.AccessModeRead}),
"sender": convert.ToUser(ctx, user_model.NewActionsUser(), nil),
}
if spec.Repo.Owner.IsOrganization() {
fields["organization"] = convert.ToOrganization(ctx, organization.OrgFromUser(spec.Repo.Owner))
}
eventPayload := withScheduleInEventPayload(cron.EventPayload, spec.Spec, fields)
// Create a new action run based on the schedule
run := &actions_model.ActionRun{
@@ -134,7 +152,7 @@ func CreateScheduleTask(ctx context.Context, spec *actions_model.ActionScheduleS
return nil
}
func withScheduleInEventPayload(eventPayload, schedule string) string {
func withScheduleInEventPayload(eventPayload, schedule string, fields map[string]any) string {
if schedule == "" {
return eventPayload
}
@@ -153,6 +171,7 @@ func withScheduleInEventPayload(eventPayload, schedule string) string {
event = map[string]any{}
}
maps.Copy(event, fields)
event["schedule"] = schedule
updatedPayload, err := json.Marshal(event)
if err != nil {

View File

@@ -7,6 +7,7 @@ import (
"testing"
"gitea.dev/modules/json"
api "gitea.dev/modules/structs"
"github.com/stretchr/testify/assert"
)
@@ -14,7 +15,7 @@ import (
func TestWithScheduleInEventPayload(t *testing.T) {
t.Run("adds schedule to existing payload", func(t *testing.T) {
payload := `{"ref":"refs/heads/main"}`
updated := withScheduleInEventPayload(payload, "*/5 * * * *")
updated := withScheduleInEventPayload(payload, "*/5 * * * *", nil)
event := map[string]any{}
assert.NoError(t, json.Unmarshal([]byte(updated), &event))
@@ -23,7 +24,7 @@ func TestWithScheduleInEventPayload(t *testing.T) {
})
t.Run("adds schedule to null payload", func(t *testing.T) {
updated := withScheduleInEventPayload("null", "37 12 5 1 2")
updated := withScheduleInEventPayload("null", "37 12 5 1 2", nil)
event := map[string]any{}
assert.NoError(t, json.Unmarshal([]byte(updated), &event))
@@ -31,22 +32,37 @@ func TestWithScheduleInEventPayload(t *testing.T) {
})
t.Run("adds schedule to empty payload", func(t *testing.T) {
updated := withScheduleInEventPayload("", "37 12 5 1 2")
updated := withScheduleInEventPayload("", "37 12 5 1 2", nil)
event := map[string]any{}
assert.NoError(t, json.Unmarshal([]byte(updated), &event))
assert.Equal(t, "37 12 5 1 2", event["schedule"])
})
t.Run("adds schedule with repository, sender, organization", func(t *testing.T) {
updated := withScheduleInEventPayload("null", "@weekly", map[string]any{
"repository": &api.Repository{Name: "test-repo"},
"sender": &api.User{UserName: "test-user"},
"organization": &api.Organization{Name: "test-org"},
})
event := map[string]any{}
assert.NoError(t, json.Unmarshal([]byte(updated), &event))
assert.Equal(t, "@weekly", event["schedule"])
assert.Equal(t, "test-repo", event["repository"].(map[string]any)["name"])
assert.Equal(t, "test-user", event["sender"].(map[string]any)["login"])
assert.Equal(t, "test-org", event["organization"].(map[string]any)["name"])
})
t.Run("keeps payload when schedule empty", func(t *testing.T) {
payload := `{"ref":"refs/heads/main"}`
updated := withScheduleInEventPayload(payload, "")
updated := withScheduleInEventPayload(payload, "", nil)
assert.Equal(t, payload, updated)
})
t.Run("keeps payload when malformed JSON", func(t *testing.T) {
payload := `not a json object`
updated := withScheduleInEventPayload(payload, "*/5 * * * *")
updated := withScheduleInEventPayload(payload, "*/5 * * * *", nil)
assert.Equal(t, payload, updated)
})
}