fix: loading action logs on a task that isn't fetched yet, fails when the job is fetched (#9293)

Discovered a regression caused by #9017.

Steps to reproduce:
- Disable the forgejo-runner that will pick up a workflow
- Trigger any workflow to run
- Through the Actions list, click on the new workflow that is pending a runner to fetch it
- You'll be redirected to /user/repo/actions/runs/73/jobs/0/attempt/0  (attempt = 0)
  - The UI will appear normal with the job "Waiting"...
- Startup the forgejo-runner to pick up the workflow
- The UI will begin to have errors:
    - JavaScript promise rejection: JSON.parse: unexpected keyword at line 1 column 1 of the JSON data. Open browser console to see more details. (5)

The cause is that the redirect to `/attempt/0` occurs for a job that hasn't been started, but once the job is started attempt 0 is not a valid attempt and errors will occur when polling for data.  This fix corrects the problem by redirecting to the attempt that will be present (attempt 1) when the job is fetched.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9293
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Mathieu Fenniak <mathieu@fenniak.net>
Co-committed-by: Mathieu Fenniak <mathieu@fenniak.net>
This commit is contained in:
Mathieu Fenniak 2025-09-14 14:30:02 +02:00 committed by Gusted
commit eb21dd17b8
3 changed files with 31 additions and 4 deletions

View file

@ -66,7 +66,14 @@ func (job *ActionRunJob) HTMLURL(ctx context.Context) (string, error) {
return "", fmt.Errorf("action_run_job: unable to find job on run: %d", job.ID)
}
return fmt.Sprintf("%s/actions/runs/%d/jobs/%d/attempt/%d", job.Run.Repo.HTMLURL(), job.Run.Index, jobIndex, job.Attempt), nil
attempt := job.Attempt
// If a job has never been fetched by a runner yet, it will have attempt 0 -- but this attempt will never have a
// valid UI since attempt is incremented to 1 if it is picked up by a runner.
if attempt == 0 {
attempt = 1
}
return fmt.Sprintf("%s/actions/runs/%d/jobs/%d/attempt/%d", job.Run.Repo.HTMLURL(), job.Run.Index, jobIndex, attempt), nil
}
func (job *ActionRunJob) Duration() time.Duration {

View file

@ -136,7 +136,7 @@
commit_sha: 985f0301dba5e7b34be866819cd15ad3d8f508ee
is_fork_pull_request: 0
name: job_2
attempt: 1
attempt: 0
job_id: job_2
task_id: null
status: 5

View file

@ -386,6 +386,8 @@ func TestActionsViewRedirectToLatestAttempt(t *testing.T) {
jobIndex int64
expectedCode int
expectedURL string
userID int64
repoID int64
}{
{
name: "no job index",
@ -420,13 +422,31 @@ func TestActionsViewRedirectToLatestAttempt(t *testing.T) {
jobIndex: 500,
expectedURL: "https://try.gitea.io/user2/repo1/actions/runs/187/jobs/0/attempt/1",
},
// This ActionRunJob has Attempt: 0 and TaskID: null, which indicates its first run is pending pickup by a
// runner. Should redirect to the attempt/1 since that's what it will be when it is running.
{
name: "redirect to non-picked task",
userID: 2,
repoID: 4,
runIndex: 190,
jobIndex: 0,
expectedURL: "https://try.gitea.io/user5/repo4/actions/runs/190/jobs/0/attempt/1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx, resp := contexttest.MockContext(t, "user2/repo1/actions/runs/0")
contexttest.LoadUser(t, ctx, 2)
contexttest.LoadRepo(t, ctx, 1)
if tt.userID == 0 {
contexttest.LoadUser(t, ctx, 2)
} else {
contexttest.LoadUser(t, ctx, tt.userID)
}
if tt.repoID == 0 {
contexttest.LoadRepo(t, ctx, 1)
} else {
contexttest.LoadRepo(t, ctx, tt.repoID)
}
ctx.SetParams(":run", fmt.Sprintf("%d", tt.runIndex))
if tt.jobIndex != -1 {
ctx.SetParams(":job", fmt.Sprintf("%d", tt.jobIndex))