From 4282be3094a60367d76c019d895f2667f56b2415 Mon Sep 17 00:00:00 2001 From: "openbotgit[bot]" <335604065+openbotgit[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 09:53:53 +0700 Subject: [PATCH] fix(ghapp): serve queued jobs of pending runs GitHub reports a run as pending while it waits to start, for example behind a concurrency group, and its jobs are already queued. The controller listed only queued and in_progress runs, so such a job was never served (Polity run 39189840366 waited 25+ minutes). --- CHANGELOG.md | 3 +++ internal/ghapp/conditional_test.go | 4 ++-- internal/ghapp/ghapp.go | 4 +++- internal/ghapp/ghapp_test.go | 28 ++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f5756f1..fc4f736 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ the major version is 0, a minor release may change configuration (each such chan ## [Unreleased] +### Fixed +- Jobs queued in a run that GitHub reports as `pending` (for example while it waits on a concurrency group) are served; the controller listed only `queued` and `in_progress` runs and left such jobs waiting forever. + ### Changed - A queued-job webhook starts a cycle after a 3 s debounce instead of a fixed 20 s; cycles still start at least 20 s apart (measured from the previous cycle's start). diff --git a/internal/ghapp/conditional_test.go b/internal/ghapp/conditional_test.go index 69acba4..84511de 100644 --- a/internal/ghapp/conditional_test.go +++ b/internal/ghapp/conditional_test.go @@ -40,8 +40,8 @@ func TestASecondIdenticalReadIsConditionalAndReusesTheBody(t *testing.T) { if err != nil || len(second) != 1 || second[0].ID != first[0].ID { t.Fatalf("second %v %v, want the same job as the first", second, err) } - if len(conditional) != 3 { - t.Fatalf("304s for %v, want the two run lists and the jobs list", conditional) + if len(conditional) != 4 { + t.Fatalf("304s for %v, want the three run lists (queued, pending, in_progress) and the jobs list", conditional) } } diff --git a/internal/ghapp/ghapp.go b/internal/ghapp/ghapp.go index 6092c5f..c3e7ae7 100644 --- a/internal/ghapp/ghapp.go +++ b/internal/ghapp/ghapp.go @@ -420,7 +420,9 @@ func (c *Client) QueuedJobs(ctx context.Context, s reconcile.Scope) ([]Job, erro seen := map[int64]bool{} var out []Job for _, repo := range repos { - for _, status := range []string{"queued", "in_progress"} { + // A run waiting to start reports "pending" (behind a concurrency group, say) while its + // jobs are already queued, so pending runs are read too. + for _, status := range []string{"queued", "pending", "in_progress"} { var runIDs []int64 var runs struct { Runs []struct { diff --git a/internal/ghapp/ghapp_test.go b/internal/ghapp/ghapp_test.go index c9cd7cf..5f74c04 100644 --- a/internal/ghapp/ghapp_test.go +++ b/internal/ghapp/ghapp_test.go @@ -235,6 +235,34 @@ func TestQueuedJobsCountOnlyQueuedJobsWithOurLabelInTheScopesRepos(t *testing.T) } } +// GitHub reports a run as "pending" (not "queued") while it waits to start, for example behind a +// concurrency group, and its jobs are queued all the same: they must still be served. +func TestQueuedJobsIncludeQueuedJobsOfPendingRuns(t *testing.T) { + f := &fake{h: func(w http.ResponseWriter, r *http.Request) { + switch { + case r.URL.Path == "/installation/repositories": + io.WriteString(w, `{"total_count":1,"repositories":[{"name":"a","owner":{"login":"acme"}}]}`) + case strings.HasSuffix(r.URL.Path, "/actions/runs") && r.URL.Query().Get("status") == "pending": + io.WriteString(w, `{"workflow_runs":[{"id":21}]}`) + case strings.HasSuffix(r.URL.Path, "/actions/runs"): + io.WriteString(w, `{"workflow_runs":[]}`) + case strings.HasSuffix(r.URL.Path, "/runs/21/jobs"): + io.WriteString(w, `{"jobs":[{"id":7,"status":"queued","labels":["self-hosted","railway"],"created_at":"2026-10-19T02:26:25Z"}]}`) + default: + t.Errorf("unexpected %s", r.URL.RequestURI()) + w.WriteHeader(404) + } + }} + c := newGH(t, f) + jobs, err := c.QueuedJobs(context.Background(), org) + if err != nil { + t.Fatal(err) + } + if len(jobs) != 1 || jobs[0].ID != 7 { + t.Errorf("jobs %+v, want job 7 of the pending run", jobs) + } +} + func TestQueuedJobsOfARepoScopeLookOnlyAtThatRepo(t *testing.T) { f := &fake{h: func(w http.ResponseWriter, r *http.Request) { switch {