Address review findings #1 and #2: the response body was closed explicitly
rather than via defer, which could leak if future code paths were added.
Extract handleResponse helper method that uses defer resp.Body.Close() to
guarantee cleanup. This avoids the loop-defer antipattern (defer inside a
for loop accumulates defers until function exit) by isolating the body
handling into its own function scope.
- Handle io.ReadAll error on error body read (client.go:265)
- Remove unused State field from commitStatusResponse (pr.go)
- Guard via slice access in defaultCheckRedirect (client.go:117)
- Move GetFileContentAtRef from pr.go to files.go (logical home)
Sonnet MINOR #1: Stop timer after <-timer.C fires for idiomatic cleanup.
Sonnet MINOR #2: Document that empty array from contents API is valid (empty dir).
Sonnet MINOR #3: Document that GetPullRequestFiles returns nil for no files.
Sonnet NIT #4: Strengthen SetHTTPClient/SetRetryBackoff docs to clarify test-only intent.
Sonnet NIT #5: Document GetCommitStatuses fail-fast behavior.
Sonnet NIT #6: Document double-slash collapsing in escapePath.
Security MINOR #1: Document redirect policy responsibility when providing custom client.
Security MINOR #2: Reduce maxErrorBodyBytes from 64KB to 4KB to limit sensitive data exposure.
- SetHTTPClient(nil): preserve CheckRedirect auth-stripping policy
instead of restoring a plain http.Client that loses cross-host
protection.
- Authorization header: add comment documenting why Bearer scheme is
correct (OAuth2 standard, works for both classic PATs and
fine-grained tokens).
- Retry-After parsing: support HTTP-date format (RFC 7231) in addition
to integer seconds. GitHub only sends integers today, but the
implementation is now spec-compliant.
- escapePath dot-segment removal: document the behavior in public API
doc comments for ListContents and GetFileContentAtRef so callers are
aware without reading the internal helper.
- Use net/url.Parse for HTTPS scheme check (case-insensitive)
- Guard SetHTTPClient against nil (restores default 30s client)
- Rename 'url' param to 'reqURL' in doRequest/doGet for clarity
- Return error when response exceeds maxResponseBytes instead of
silently truncating
Finding #1 (Bearer auth scheme) intentionally kept: GitHub REST API
officially supports and recommends Bearer for all token types.
See: https://docs.github.com/en/rest/authentication/authenticating-to-the-rest-api
- Remove redundant timer.Stop() after timer fires (Sonnet #1, GPT #2)
- Remove unused TotalCount field from checkRunsResponse (Sonnet #2)
- Improve escapePath doc comment to explain deliberate silent stripping (Sonnet #3)
- Fix ListContents to handle both array (directory) and object (single file)
responses from GitHub Contents API (GPT #3)
- Add HTTPS enforcement: refuse to send credentials over non-HTTPS URLs
unless AllowInsecureHTTP() option is passed (Security #1)
- Replace constant-value test with actual behavior test for response
body limiting (Sonnet #6)
- Run gofmt for consistent formatting (Sonnet #4)
- Add tests for HTTPS enforcement and ListContents single-file handling
- Unexport RetryBackoff, add SetRetryBackoff method (#17286)
- Rename http field to httpClient to avoid shadowing (#17289)
- Group const blocks into single declaration (#17291)
- Fix CheckRedirect to compare against previous hop, not first (#17302)
- Strip auth header on protocol downgrade https→http (#17297)
- Add maxPages safeguard to pagination loops (#17299, #17300)
- Document mapCheckRunStatus unused second parameter (#17287, #17303)
- Add User-Agent header to all requests (gpt-review-bot)
- Limit successful response body to 10 MiB via io.LimitReader (security-review-bot)
- Add CheckRedirect to strip Authorization on cross-host redirects (security-review-bot)
- Fix decodeBase64Content to strip both \r and \n (gpt-review-bot)
- Document that transport errors are not retried (sonnet-review-bot)
- Update package doc to reflect current scope (no review submission yet)
- Add tests for User-Agent, empty-token auth skip, CRLF base64, CheckRedirect
- Fix Retry-After slice mutation: copy c.RetryBackoff before modifying
to prevent permanent mutation of the shared slice (sonnet#1, security#1)
- Cap Retry-After to 120s maximum to prevent excessive sleeps (security#2)
- Guard auth header: only set Authorization when token is non-empty (gpt#2)
- Fix GetFileContent doc comment to match actual behavior (sonnet#3, gpt#1)
- Remove dead 'in_progress/queued' case in mapCheckRunStatus (sonnet#4)
- Add testing.Short() guard to slow retry test (sonnet#5)
- Reject dot-segments in escapePath to prevent path traversal (security#3)
- Add regression tests for non-mutation and escapePath safety
Implement the GitHub API client with PRReader and FileReader interface
conformance for both github.com and GitHub Enterprise.
New files:
- github/client.go: Client struct, NewClient with configurable base URL,
HTTP helpers with 429 retry and Retry-After support
- github/pr.go: GetPullRequest, GetPullRequestDiff (per-request Accept
header), GetPullRequestFiles (paginated, populates Patch field),
GetFileContentAtRef (base64 decode), GetCommitStatuses (merges commit
statuses + check runs with conclusion mapping)
- github/files.go: GetFileContent (delegates to GetFileContentAtRef),
ListContents, escapePath, decodeBase64Content helpers
Type changes:
- vcs/types.go: Add Patch field to ChangedFile struct
Tests cover: happy path, 404, 401, 429+retry, malformed response,
pagination, binary files, check run conclusion mapping, base64 decoding.
Compile-time checks:
var _ vcs.PRReader = (*Client)(nil)
var _ vcs.FileReader = (*Client)(nil)
Exit criteria met:
- go test ./github/... passes (all methods)
- NewClient with empty baseURL uses https://api.github.com
- NewClient with GHE URL targets correctly
- GetFileContent delegates to GetFileContentAtRef with empty ref
- GetPullRequestFiles paginates and populates Patch field
- GetCommitStatuses merges both commit statuses and check-runs