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

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: Zettat123 <zettat123@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
bircni
2026-07-25 18:07:40 +02:00
committed by GitHub
parent 7a1941e384
commit 69f0a10364
15 changed files with 292 additions and 26 deletions
+61 -1
View File
@@ -217,7 +217,8 @@ func TestCreateCommitStatus_LegacyHashRecovery(t *testing.T) {
legacyHash := git_model.HashCommitStatusContext(ctxName)
sha, err := git.NewIDFromString(branch.CommitID)
require.NoError(t, err)
creator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
// Pre-#35699 in-flight rows were posted by the Actions user with the Context-only hash.
creator := user_model.NewActionsUser()
require.NoError(t, git_model.NewCommitStatus(t.Context(), git_model.NewCommitStatusOptions{
Repo: repo,
Creator: creator,
@@ -258,6 +259,65 @@ func TestCreateCommitStatus_LegacyHashRecovery(t *testing.T) {
assert.Equal(t, 1, matches)
}
// TestCreateCommitStatus_LegacyHashExternalNotAdopted: a status from a non-Actions creator sharing a
// workflow's Context must not pull the workflow into the legacy Context-only hash group.
func TestCreateCommitStatus_LegacyHashExternalNotAdopted(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
branch := unittest.AssertExistsAndLoadBean(t, &git_model.Branch{RepoID: repo.ID, Name: repo.DefaultBranch})
workflowID := "external.yaml"
ctxName := "external.yaml / my-job (push)"
legacyHash := git_model.HashCommitStatusContext(ctxName)
distinctHash := git_model.HashCommitStatusContext(ctxName + "\x00" + workflowID)
sha, err := git.NewIDFromString(branch.CommitID)
require.NoError(t, err)
// An external status (posted by a real user, not the Actions user) sharing the same Context.
externalCreator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo.OwnerID})
require.NoError(t, git_model.NewCommitStatus(t.Context(), git_model.NewCommitStatusOptions{
Repo: repo,
Creator: externalCreator,
SHA: sha,
CommitStatus: &git_model.CommitStatus{
State: commitstatus.CommitStatusSuccess,
Context: ctxName,
ContextHash: legacyHash,
TargetURL: "https://example.invalid/external",
Description: "external check",
},
}))
run := &actions_model.ActionRun{
ID: 99311, Index: 99311, RepoID: repo.ID, Repo: repo, OwnerID: repo.OwnerID, TriggerUserID: repo.OwnerID,
WorkflowID: workflowID, CommitSHA: branch.CommitID,
}
require.NoError(t, db.Insert(t.Context(), run))
job := &actions_model.ActionRunJob{
ID: 99312, RunID: run.ID, RepoID: repo.ID, OwnerID: repo.OwnerID,
Name: "my-job", Status: actions_model.StatusSuccess,
}
require.NoError(t, db.Insert(t.Context(), job))
require.NoError(t, createCommitStatus(t.Context(), repo, "push", branch.CommitID, "", run, job))
latest, err := git_model.GetLatestCommitStatus(t.Context(), repo.ID, branch.CommitID, db.ListOptionsAll)
require.NoError(t, err)
// The external status and the workflow status must coexist under distinct hashes.
var external, workflow *git_model.CommitStatus
for _, s := range latest {
switch s.ContextHash {
case legacyHash:
external = s
case distinctHash:
workflow = s
}
}
require.NotNil(t, external, "external status must be preserved under the legacy hash")
require.NotNil(t, workflow, "workflow status must use its own distinct hash, not the external legacy hash")
assert.Equal(t, "https://example.invalid/external", external.TargetURL)
}
// TestCreateCommitStatus_UnnamedWorkflowUsesFileName: a workflow with no
// non-blank `name:` uses the file name in the Context, not an empty
// "/ job (event)" — covers both an omitted and a whitespace-only name.