You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A GitHub workflow run with a null head_branch or name leaves a permanent "Pending" row with an empty branch and pins its build at High polling priority #400
The GitHub OpenAPI schema marks workflow-run.head_branch and name as nullable. A run from a fork PR whose fork has since been deleted is the usual case. The GitHub provider maps both without a null guard:
SemanticString.As<T>() throws ArgumentNullException on null (ktsu.Semantics.Strings 5.12.1). The run is also inserted into build.Runs via GetOrCreate(runId, build.CreateRun(...)) (L431) beforeUpdateRunFromWorkflowAsync fills it in. So when L520 throws, a Run is already stored with Started set and the default RunStatus.Pending. The exception isn't an ApiException, so MakeGitHubRequestAsync doesn't handle it. SyncGuard logs it and the loop moves on.
The Azure DevOps provider already guards the same field (build.SourceBranch ?? string.Empty).
Failure scenario (reproduced in a scratch MSTest)
UpdateRunFromWorkflowAsync is given a WorkflowRun with "head_branch": null, "status": "completed", "conclusion": "success". The call throws ArgumentNullException and leaves run.Status == Pending, IsOngoing == true, Branch == "", build.HasOngoingRun == true and BuildSync.Priority == High.
In the app:
The table shows a row with an empty branch and a progress ring that never completes. The row is persisted, so it survives restarts.
Every poll throws at the same run, so the other runs in the 10-run page after it are never updated, and an error is logged each time.
Once the run leaves the page, a RunSync is added for the stuck run. GitHubRuns.Get returns the same null and throws, and because the run still reads as ongoing it is retried every 10 s indefinitely.
Suggested fix / acceptance criteria
Map HeadBranch ?? string.Empty (or a placeholder such as the head SHA) and Name ?? build.Name.
Build and fill the Run completely before adding it to build.Runs, so a mapping exception never leaves a half-initialised Pending run behind.
Test: a WorkflowRun with a null head_branch and/or name maps to a completed run without throwing, and no Pending run is left in build.Runs.
Category: Bug Priority: High. GitHub sends a null head_branch for any run from a fork PR whose fork has since been deleted, so ordinary repositories hit this. The result lasts: a persisted Pending row with an empty branch, a build pinned at High priority (polled every 30 s, even in critical-budget mode), the other runs in the same 10-run page never updated, and then a RunSync retried every 10 s indefinitely. Every poll spends budget and logs an error.
Suggested area / assignment: GitHub provider mapping in BuildMonitor/Providers/GitHub.cs (UpdateRunFromWorkflowAsync, L430/L520, and the GetOrCreate at L431). The Azure DevOps provider already guards SourceBranch, so this lines the two providers up.
Duplicates / in progress: Not a duplicate. It interacts with #396 (closed), which made HasOngoingRun raise priority, and with #332, where ongoing runs with no estimate are polled every 10 s. That interaction is what turns one bad run into constant polling. Open PR #398 (pruning old runs) touches run bookkeeping but does not cover this: a stuck run that still reads as ongoing would not be pruned safely. Fix this first, or check the pruning rule against it.
Next step: Map HeadBranch ?? string.Empty and Name ?? build.Name, build the Run completely before inserting it into build.Runs, and add the null-field mapping test from the acceptance criteria.
Activity
matt-edmondson commentedon Nov 5, 2026
Triage
Category: Bug
Priority: High. GitHub sends a null
head_branchfor any run from a fork PR whose fork has since been deleted, so ordinary repositories hit this. The result lasts: a persisted Pending row with an empty branch, a build pinned at High priority (polled every 30 s, even in critical-budget mode), the other runs in the same 10-run page never updated, and then aRunSyncretried every 10 s indefinitely. Every poll spends budget and logs an error.Suggested area / assignment: GitHub provider mapping in
BuildMonitor/Providers/GitHub.cs(UpdateRunFromWorkflowAsync, L430/L520, and theGetOrCreateat L431). The Azure DevOps provider already guardsSourceBranch, so this lines the two providers up.Duplicates / in progress: Not a duplicate. It interacts with #396 (closed), which made
HasOngoingRunraise priority, and with #332, where ongoing runs with no estimate are polled every 10 s. That interaction is what turns one bad run into constant polling. Open PR #398 (pruning old runs) touches run bookkeeping but does not cover this: a stuck run that still reads as ongoing would not be pruned safely. Fix this first, or check the pruning rule against it.Next step: Map
HeadBranch ?? string.EmptyandName ?? build.Name, build theRuncompletely before inserting it intobuild.Runs, and add the null-field mapping test from the acceptance criteria.Generated by Claude Code