feat(github): list runs and jobs, stream job logs, edit labels #89

Merged
deadstyle merged 2 commits from feature/85-github-runs-labels into develop 2026-10-09 10:10:50 -05:00
Owner

Summary

On GitHub, the [3] Actions box can now list a repo's recent workflow runs and their jobs, job logs can be read, and labels can be edited, the same as on Forgejo. Nothing is user-visible until #86 makes GitHub selectable.

Refs #85 (part of #83)

Decisions (ADR 0017 updated)

  • Recent runs only. ListRuns returns the newest 100 runs, which is one request. Core asks with no filter, and a busy GitHub repo has tens of thousands of runs, so returning the whole history would spend hundreds of requests from the rate limit. The design describes the box as "recent workflow runs". This is a deliberate exception to ADR 0006's rule that list methods return everything.
  • Logs bypass the response cache. Job logs stream instead of going through the cache, so a large log is never held in memory.
  • The token stays on the API origin. On any log redirect that leaves the API origin, the token is dropped. Go's default would still forward it to a subdomain or another port. Transport errors also drop the signed redirect URL, so it can't reach an error message.
  • Labels by name. SetLabels maps label IDs to the names GitHub's API takes. An unknown ID is an error and nothing is sent.

Verification

make check (golangci-lint v2.14.0, the version CI pins):

golangci-lint run
0 issues.
ok  	git.bobparsons.dev/deadstyle/lazyforge/internal/forge/github
No vulnerabilities found.
go build ... -o bin/lazyforge ./cmd/lazyforge
/bin/sh: line 1: shellcheck: command not found

shellcheck isn't installed locally. No shell files changed, and CI runs that step.

Mutation checks, each reverted afterwards:

Change Result
Reintroduce paging Both ListRuns tests fail
Send the log request through the plain client The token reaches the other-port host
Disable URL unwrapping The error contains sig=secret
Route JobLog through the cache "client buffered the log"

Fixtures were recorded from cli/cli with gh api and trimmed. One hand edit, a run set to queued, is noted in testdata/README.md. The fixtures contain no tokens.

Review

Three fresh reviewers ran: standards, comments and spec. Spec passed.

Fixed:

  • runs paging
  • token forwarded to a subdomain or another port
  • signed URL in errors
  • a run with no start time getting a duration
  • t.Fatal in handler goroutines and an unclosed log in tests
  • label shadowing
  • three restating comments

Dismissed:

  • startup_failure/stale mapping: the fold already maps them.
  • 30s client timeout on long log streams: the Logs tab is post-v1, and gitea has the same shape.
  • Expired-log error wording: Logs tab, post-v1.
  • Unknown label ID as ErrNotFound: that is accurate.
  • Test-server locking: the tests pass under -race.

I re-checked the fixes myself rather than re-dispatching the reviewers.

Docs

docs/architecture.md: the GitHub runs, jobs and logs rows are verified, the runs row says "newest 100", and there is a labels row. ADR 0017 is updated. No design.md change.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QhCwUhPRAFrAziCumaEa67

## Summary On GitHub, the `[3]` Actions box can now list a repo's recent workflow runs and their jobs, job logs can be read, and labels can be edited, the same as on Forgejo. Nothing is user-visible until #86 makes GitHub selectable. Refs #85 (part of #83) ## Decisions (ADR 0017 updated) - **Recent runs only.** `ListRuns` returns the newest 100 runs, which is one request. Core asks with no filter, and a busy GitHub repo has tens of thousands of runs, so returning the whole history would spend hundreds of requests from the rate limit. The design describes the box as "recent workflow runs". This is a deliberate exception to ADR 0006's rule that list methods return everything. - **Logs bypass the response cache.** Job logs stream instead of going through the cache, so a large log is never held in memory. - **The token stays on the API origin.** On any log redirect that leaves the API origin, the token is dropped. Go's default would still forward it to a subdomain or another port. Transport errors also drop the signed redirect URL, so it can't reach an error message. - **Labels by name.** `SetLabels` maps label IDs to the names GitHub's API takes. An unknown ID is an error and nothing is sent. ## Verification `make check` (golangci-lint v2.14.0, the version CI pins): ``` golangci-lint run 0 issues. ok git.bobparsons.dev/deadstyle/lazyforge/internal/forge/github No vulnerabilities found. go build ... -o bin/lazyforge ./cmd/lazyforge /bin/sh: line 1: shellcheck: command not found ``` `shellcheck` isn't installed locally. No shell files changed, and CI runs that step. Mutation checks, each reverted afterwards: | Change | Result | |---|---| | Reintroduce paging | Both `ListRuns` tests fail | | Send the log request through the plain client | The token reaches the other-port host | | Disable URL unwrapping | The error contains `sig=secret` | | Route `JobLog` through the cache | "client buffered the log" | Fixtures were recorded from `cli/cli` with `gh api` and trimmed. One hand edit, a run set to `queued`, is noted in `testdata/README.md`. The fixtures contain no tokens. ## Review Three fresh reviewers ran: standards, comments and spec. Spec passed. Fixed: - runs paging - token forwarded to a subdomain or another port - signed URL in errors - a run with no start time getting a duration - `t.Fatal` in handler goroutines and an unclosed log in tests - `label` shadowing - three restating comments Dismissed: - **startup_failure/stale mapping:** the fold already maps them. - **30s client timeout on long log streams:** the Logs tab is post-v1, and gitea has the same shape. - **Expired-log error wording:** Logs tab, post-v1. - **Unknown label ID as `ErrNotFound`:** that is accurate. - **Test-server locking:** the tests pass under `-race`. I re-checked the fixes myself rather than re-dispatching the reviewers. ## Docs `docs/architecture.md`: the GitHub runs, jobs and logs rows are verified, the runs row says "newest 100", and there is a labels row. ADR 0017 is updated. No design.md change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01QhCwUhPRAFrAziCumaEa67
The GitHub adapter now implements RunLister, LogReader and Labeler.
Job logs stream past the ETag cache and the token stays off the log
host; SetLabels maps label IDs to the names GitHub's API takes.

Refs #85

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhCwUhPRAFrAziCumaEa67
fix(github): cap runs at the newest page and keep the token off log redirects
All checks were successful
ci / check (pull_request) Successful in 1m3s
2407c7c5cf
Review follow-up: ListRuns returns the newest 100 runs instead of the
whole history, JobLog drops Authorization on any off-origin redirect
(Go forwards it to subdomains and other ports), transport errors no
longer carry the signed log URL, and a run without a start time has no
duration.

Refs #85

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QhCwUhPRAFrAziCumaEa67
deadstyle merged commit 701682646d into develop 2026-10-09 10:10:50 -05:00
deadstyle deleted branch feature/85-github-runs-labels 2026-10-09 10:10:50 -05:00
Sign in to join this conversation.
No description provided.