fix(actions): correctness and hardening fixes (#38518) (#38631)

Backport #38518 by @bircni

Various fixes to actions

1. **Cap total jobs per run in reusable-workflow expansion** — only
nesting depth was capped, so fan-out + nested reusable workflows could
explode job-row inserts and exhaust the DB from a single push. Now
enforces `MaxJobNumPerRun` in the insert path.
2. **Reject rerun-failed when a run has no failed jobs** — an empty job
list meant "re-run everything", so `rerun-failed` on a green run re-ran
all jobs. Now errors (web + API).
3. **Don't adopt external commit statuses into the legacy hash** — the
pre-#35699 Context-only hash matched API-posted statuses too, collapsing
two same-named workflows into one check. Now limited to Actions-user
rows.
4. **Don't cut post-cancel cleanup short in `StopEndlessTasks`** — the
sweep force-stopped just-cancelled jobs mid-cleanup. Now targets
`StatusRunning` only; stalled cancels stay covered by `StopZombieTasks`.
5. **Avoid redundant run reload in `GenerateGiteaContext`** — resolving
`github.triggering_actor` reloaded the run already passed in. Now loads
only the trigger user via new `ActionRunAttempt.LoadTriggerUser`.

Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
Giteabot
2026-07-25 19:49:33 +02:00
committed by GitHub
co-authored by bircni Zettat123 silverwind wxiaoguang
parent a54324e2b7
commit 31c435454b
15 changed files with 292 additions and 26 deletions
+38
View File
@@ -9,7 +9,9 @@ import (
actions_model "gitea.dev/models/actions"
"gitea.dev/models/db"
repo_model "gitea.dev/models/repo"
"gitea.dev/models/unittest"
user_model "gitea.dev/models/user"
"gitea.dev/modules/json"
api "gitea.dev/modules/structs"
@@ -318,3 +320,39 @@ func TestFindTaskNeeds(t *testing.T) {
assert.Equal(t, "abc", ret["job1"].Outputs["output_a"])
assert.Equal(t, "bbb", ret["job1"].Outputs["output_b"])
}
// TestGenerateGiteaContext_NilAttempt verifies that, with no explicit attempt,
// use GetLatestAttempt to load the latest attempt and resolve attempt-related context variables.
func TestGenerateGiteaContext_NilAttempt(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
require.NoError(t, repo.LoadOwner(t.Context()))
actor := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) // initiated the run
triggerer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) // initiated the latest attempt
run := &actions_model.ActionRun{
RepoID: repo.ID, Repo: repo, OwnerID: repo.OwnerID,
TriggerUserID: actor.ID, TriggerUser: actor,
WorkflowID: "test.yml", Index: 99600, Ref: "refs/heads/main",
CommitSHA: "c2d72f548424103f01ee1dc02889c1e2bff816b0", TriggerEvent: "push",
Status: actions_model.StatusRunning,
}
require.NoError(t, db.Insert(t.Context(), run))
attempt := &actions_model.ActionRunAttempt{
RepoID: repo.ID, RunID: run.ID, Attempt: 3, TriggerUserID: triggerer.ID, Status: actions_model.StatusRunning,
}
require.NoError(t, db.Insert(t.Context(), attempt))
run.LatestAttemptID = attempt.ID
job := &actions_model.ActionRunJob{
RunID: run.ID, RunAttemptID: attempt.ID, AttemptJobID: 1, RepoID: repo.ID, OwnerID: repo.OwnerID,
Name: "j", JobID: "j", Attempt: attempt.Attempt, Status: actions_model.StatusRunning,
}
require.NoError(t, db.Insert(t.Context(), job))
// attempt == nil forces the fallback lookup via run.GetLatestAttempt.
gitCtx := GenerateGiteaContext(t.Context(), run, nil, job)
assert.Equal(t, actor.Name, gitCtx["actor"])
assert.Equal(t, triggerer.Name, gitCtx["triggering_actor"])
assert.Equal(t, "3", gitCtx["run_attempt"])
}