Skip to content

Show GitHub errors in build-monitor, mark running runs, and list org private repos [patch] - #350

Open
matt-edmondson wants to merge 3 commits into
mainfrom
fix/207-build-monitor-api-errors
Open

matt-edmondson wants to merge 3 commits into
mainfrom
fix/207-build-monitor-api-errors

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #207

What was wrong

  1. BuildMonitorService caught only HttpRequestException, but Octokit reports API failures as ApiException and its subclasses. On the first refresh, a mistyped token or an unknown owner escaped from Live as an unhandled exception. On a later refresh the exception faulted IntervalAction, which stopped polling and left the last table on screen indefinitely. This is what happens after about 50 minutes at the default --refresh 30 for an org-sized owner, when the rate limit runs out.
  2. GetWorkflowRunsAsync passed r.UpdatedAt as CompletedAt. UpdatedAt is never null, so In progress... was unreachable.
  3. GetRepositoriesAsync called /users/{owner}/repos, which for an organization lists only its public repositories. project uses the same service, so it had the same gap.

Change

  • New internal RenderDashboardAsync returns either the table or a message, so Tick can't throw. It handles each error class:

    • RateLimitExceededException: shows the hourly limit and when it resets.
    • AuthorizationException: shows the status code and that GitHub rejected the token.
    • Any other ApiException, and HttpRequestException: shows the status and message.

    Each failure is retried on the next tick instead of ending the polling.

  • Per repository: an ApiException marks only that repo's row as Error. A rate-limit error is rethrown instead, so the whole refresh reports it once rather than as a table full of errors.

  • GitHubService.CompletedAtOf: a run's UpdatedAt becomes CompletedAt only when its status is completed, so queued, waiting and running runs show In progress....

  • GetRepositoriesAsync looks up the owner with User.Get, then uses GetAllForOrg for an organization and GetAllForUser otherwise. This costs one extra request per refresh.

  • GitHubService now holds an IGitHubClient, with an internal constructor for tests. KtsuTools.BuildMonitor now references Octokit directly, which the SDK's KTSU0006 rule requires.

Not done: the optional warning when --refresh × repository count would exceed the hourly limit. A user who is the authenticated account still goes through GetAllForUser, which omits their own private repos. GetAllForCurrent would fix that, but it is outside this issue's org case.

Tests

New BuildMonitorServiceTests, using Moq for IGitHubService and IGitHubClient, with Octokit models deserialized from JSON:

  • 401, 404 and rate-limit errors render as messages instead of throwing. The rate-limit message includes the limit and reset time.
  • A 404 on one repository's runs marks only that row, and the other repository's running run shows In progress....
  • CompletedAtOf is null for queued, in_progress and waiting, and is UpdatedAt for completed.
  • An organization is listed through GetAllForOrg, and a user through GetAllForUser.

With the old behaviour restored in BuildMonitorService.cs and GitHubService.cs (new signatures kept so the tests compile), 8 of the 10 tests fail. The other 2, the completed-run and user-endpoint cases, are guards that pass either way. Full suite with the fix: 395/395.

Builds were run with -p:WarningsNotAsErrors=NU1903, because main currently fails restore on the SixLabors.ImageSharp advisory (#270).


Generated by Claude Code

…private repos [patch]

- Tick only caught HttpRequestException, but Octokit reports API
  failures as ApiException. A bad token or unknown owner crashed the
  first refresh, and a later failure (e.g. hitting the rate limit after
  ~50 minutes) faulted IntervalAction and froze the dashboard. The
  dashboard is now rendered by RenderDashboardAsync, which shows rate
  limit (with reset time), authorization and other API errors and is
  retried on the next tick. One repository's API error marks only its
  own row; a rate limit fails the whole refresh.
- GetWorkflowRunsAsync passed UpdatedAt as CompletedAt, so no run ever
  showed "In progress...". CompletedAt is now set only for completed runs.
- GetRepositoriesAsync used /users/{owner}/repos, which lists only an
  organization's public repositories. Organizations now go through
  GetAllForOrg.

Fixes #207

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW48r2AEHXkyMC1wF3i
main fails restore on NU1903 (GHSA-v76p-62qx-wwq2) against 3.1.12, so no
PR can build. This ports the bump from #270.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW48r2AEHXkyMC1wF3i

matt-edmondson commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor Author

CI: all three Test on * jobs fail at restore with NU1903 (SixLabors.ImageSharp 3.1.12, GHSA-v76p-62qx-wwq2). This isn't caused by this change: main fails the same way.

No fix can be ported here yet. The 3.2.0 bump from #270 fails the build in CI without a Six Labors license key, and no patched 3.1.x exists, so I reverted it. Details and the three options for a maintainer are in #348 (comment). Once one of them lands on main, I'll merge it into this branch. Locally, with NU1903 not treated as an error, the full suite passes 395/395.


Generated by Claude Code

3.2.0 fails the build in CI without a Six Labors license key, so the bump
is not a usable fix for NU1903. That decision belongs to the maintainer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017vfJW48r2AEHXkyMC1wF3i
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

build-monitor crashes or silently freezes on any GitHub API error, and never shows a run as "In progress"

2 participants