Skip to content

Prevent duplicate actions email #35215

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 48 commits into from
Aug 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
21e7eef
fix
NorthRealm Aug 5, 2025
0ec1bb3
add trace
NorthRealm Aug 5, 2025
3957352
add trace
NorthRealm Aug 5, 2025
cdb1e80
assume
NorthRealm Aug 5, 2025
7384a65
add trace
NorthRealm Aug 5, 2025
1d9c378
update
NorthRealm Aug 5, 2025
c3a7f15
Revert "assume"
NorthRealm Aug 5, 2025
bc3a467
remove duplicate workflow run trigger + add test for cancel
ChristopherHX Aug 6, 2025
cf547bb
update
NorthRealm Aug 6, 2025
8901e59
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 8, 2025
a9a826f
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 8, 2025
e212011
remove timeout
ChristopherHX Aug 8, 2025
0506162
update
NorthRealm Aug 9, 2025
6bcdd7e
fix more workflow_run completion events
ChristopherHX Aug 10, 2025
3a44248
modernize
ChristopherHX Aug 10, 2025
3d8b2f5
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 11, 2025
5eaa9c8
error logs
NorthRealm Aug 13, 2025
a7ddfc9
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 13, 2025
1539d9f
update
NorthRealm Aug 14, 2025
4e52f0f
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 14, 2025
f19e9e3
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 15, 2025
bab5ff2
update
NorthRealm Aug 15, 2025
ec62f81
Revert "update"
NorthRealm Aug 15, 2025
20b5777
update
NorthRealm Aug 15, 2025
a1e64e7
update
NorthRealm Aug 15, 2025
7bb8e16
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 15, 2025
1bc9097
Revert "update"
NorthRealm Aug 15, 2025
2330dcf
Revert "update"
NorthRealm Aug 15, 2025
bfc4f6e
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 16, 2025
341c5d8
fix maybe
NorthRealm Aug 19, 2025
846731a
wtf
NorthRealm Aug 19, 2025
7a41096
Revert "wtf"
NorthRealm Aug 19, 2025
42b6b4e
f
NorthRealm Aug 19, 2025
45dcc12
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 19, 2025
4773ea5
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 20, 2025
cef6eba
update
NorthRealm Aug 20, 2025
b7719ee
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 20, 2025
aa85d80
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 21, 2025
d00d5cf
do not explicitly expect a workflow run completion event while jobs a…
ChristopherHX Aug 21, 2025
24d46df
update
NorthRealm Aug 22, 2025
030b1de
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 22, 2025
aee9f6a
Merge branch 'main' into patch-actions-email-2
NorthRealm Aug 22, 2025
5efd4b9
UPDATE
NorthRealm Aug 22, 2025
e4a5b82
Merge branch 'main' into patch-actions-email-2
GiteaBot Aug 23, 2025
502f387
Merge branch 'main' into patch-actions-email-2
GiteaBot Aug 23, 2025
ab8abf9
Merge branch 'main' into patch-actions-email-2
GiteaBot Aug 23, 2025
a27e5bd
Merge branch 'main' into patch-actions-email-2
GiteaBot Aug 23, 2025
05b3384
Merge branch 'main' into patch-actions-email-2
GiteaBot Aug 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions routers/web/repo/actions/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,12 @@ func Rerun(ctx *context_module.Context) {
ctx.ServerError("UpdateRun", err)
return
}

if err := run.LoadAttributes(ctx); err != nil {
ctx.ServerError("run.LoadAttributes", err)
return
}
notify_service.WorkflowRunStatusUpdate(ctx, run.Repo, run.TriggerUser, run)
}

job, jobs := getRunJobs(ctx, runIndex, jobIndex)
Expand Down Expand Up @@ -485,7 +491,6 @@ func rerunJob(ctx *context_module.Context, job *actions_model.ActionRunJob, shou
}

actions_service.CreateCommitStatus(ctx, job)
actions_service.NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)

return nil
Expand Down Expand Up @@ -560,9 +565,8 @@ func Cancel(ctx *context_module.Context) {
if len(updatedjobs) > 0 {
job := updatedjobs[0]
actions_service.NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
}
ctx.JSON(http.StatusOK, struct{}{})
ctx.JSONOK()
}

func Approve(ctx *context_module.Context) {
Expand Down Expand Up @@ -606,15 +610,14 @@ func Approve(ctx *context_module.Context) {
if len(updatedjobs) > 0 {
job := updatedjobs[0]
actions_service.NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
}

for _, job := range updatedjobs {
_ = job.LoadAttributes(ctx)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
}

ctx.JSON(http.StatusOK, struct{}{})
ctx.JSONOK()
}

func Delete(ctx *context_module.Context) {
Expand Down
30 changes: 22 additions & 8 deletions services/actions/clear_tasks.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,8 @@ func notifyWorkflowJobStatusUpdate(ctx context.Context, jobs []*actions_model.Ac
_ = job.LoadAttributes(ctx)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
}
if len(jobs) > 0 {
job := jobs[0]
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
}
job := jobs[0]
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
}
}

Expand Down Expand Up @@ -101,7 +99,7 @@ func stopTasks(ctx context.Context, opts actions_model.FindTaskOptions) error {
return nil
}

// CancelAbandonedJobs cancels the jobs which have waiting status, but haven't been picked by a runner for a long time
// CancelAbandonedJobs cancels jobs that have not been picked by any runner for a long time
func CancelAbandonedJobs(ctx context.Context) error {
jobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{
Statuses: []actions_model.Status{actions_model.StatusWaiting, actions_model.StatusBlocked},
Expand All @@ -113,24 +111,40 @@ func CancelAbandonedJobs(ctx context.Context) error {
}

now := timeutil.TimeStampNow()

// Collect one job per run to send workflow run status update
updatedRuns := map[int64]*actions_model.ActionRunJob{}

for _, job := range jobs {
job.Status = actions_model.StatusCancelled
job.Stopped = now
updated := false
if err := db.WithTx(ctx, func(ctx context.Context) error {
n, err := actions_model.UpdateRunJob(ctx, job, nil, "status", "stopped")
updated = err == nil && n > 0
return err
if err != nil {
return err
}
if err := job.LoadAttributes(ctx); err != nil {
return err
}
updated = n > 0
if updated && job.Run.Status.IsDone() {
updatedRuns[job.RunID] = job
}
return nil
}); err != nil {
log.Warn("cancel abandoned job %v: %v", job.ID, err)
// go on
}
CreateCommitStatus(ctx, job)
if updated {
NotifyWorkflowRunStatusUpdateWithReload(ctx, job)
notify_service.WorkflowJobStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job, nil)
}
}

for _, job := range updatedRuns {
notify_service.WorkflowRunStatusUpdate(ctx, job.Run.Repo, job.Run.TriggerUser, job.Run)
}

return nil
}
39 changes: 24 additions & 15 deletions services/mailer/mail_workflow_run.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,18 @@ func generateMessageIDForActionsWorkflowRunStatusEmail(repo *repo_model.Reposito
return fmt.Sprintf("<%s/actions/runs/%d@%s>", repo.FullName(), run.Index, setting.Domain)
}

func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun, sender *user_model.User, recipients []*user_model.User) {
func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo_model.Repository, run *actions_model.ActionRun, sender *user_model.User, recipients []*user_model.User) error {
jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
if err != nil {
return err
}
for _, job := range jobs {
if !job.Status.IsDone() {
log.Debug("composeAndSendActionsWorkflowRunStatusEmail: A job is not done. Will not compose and send actions email.")
return nil
}
}

subject := "Run"
switch run.Status {
case actions_model.StatusFailure:
Expand All @@ -48,11 +59,6 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
messageID := generateMessageIDForActionsWorkflowRunStatusEmail(repo, run)
metadataHeaders := generateMetadataHeaders(repo)

jobs, err := actions_model.GetRunJobsByRunID(ctx, run.ID)
if err != nil {
log.Error("GetRunJobsByRunID: %v", err)
return
}
sort.SliceStable(jobs, func(i, j int) bool {
si, sj := jobs[i].Status, jobs[j].Status
/*
Expand Down Expand Up @@ -111,11 +117,11 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
"Jobs": convertedJobs,
"locale": locale,
}); err != nil {
log.Error("ExecuteTemplate [%s]: %v", tplWorkflowRun, err)
return
return err
}
msgs := make([]*sender_service.Message, 0, len(tos))
for _, rec := range tos {
log.Trace("Sending actions email to %s (UID: %d)", rec.Name, rec.ID)
msg := sender_service.NewMessageFrom(
rec.Email,
displayName,
Expand All @@ -135,14 +141,16 @@ func composeAndSendActionsWorkflowRunStatusEmail(ctx context.Context, repo *repo
}
SendAsync(msgs...)
}

return nil
}

func MailActionsTrigger(ctx context.Context, sender *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) {
func MailActionsTrigger(ctx context.Context, sender *user_model.User, repo *repo_model.Repository, run *actions_model.ActionRun) error {
if setting.MailService == nil {
return
return nil
}
if run.Status.IsSkipped() {
return
if !run.Status.IsDone() || run.Status.IsSkipped() {
return nil
}

recipients := make([]*user_model.User, 0)
Expand All @@ -151,15 +159,16 @@ func MailActionsTrigger(ctx context.Context, sender *user_model.User, repo *repo
notifyPref, err := user_model.GetUserSetting(ctx, sender.ID,
user_model.SettingsKeyEmailNotificationGiteaActions, user_model.SettingEmailNotificationGiteaActionsFailureOnly)
if err != nil {
log.Error("GetUserSetting: %v", err)
return
return err
}
if notifyPref == user_model.SettingEmailNotificationGiteaActionsAll || !run.Status.IsSuccess() && notifyPref != user_model.SettingEmailNotificationGiteaActionsDisabled {
recipients = append(recipients, sender)
}
}

if len(recipients) > 0 {
composeAndSendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients)
log.Debug("MailActionsTrigger: Initiate email composition")
return composeAndSendActionsWorkflowRunStatusEmail(ctx, repo, run, sender, recipients)
}
return nil
}
5 changes: 2 additions & 3 deletions services/mailer/notify.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,8 +208,7 @@ func (m *mailNotifier) RepoPendingTransfer(ctx context.Context, doer, newOwner *
}

func (m *mailNotifier) WorkflowRunStatusUpdate(ctx context.Context, repo *repo_model.Repository, sender *user_model.User, run *actions_model.ActionRun) {
if !run.Status.IsDone() {
return
if err := MailActionsTrigger(ctx, sender, repo, run); err != nil {
log.Error("MailActionsTrigger: %v", err)
}
MailActionsTrigger(ctx, sender, repo, run)
}
Loading