Documentation
¶
Index ¶
- Constants
- Variables
- func CheckGHAuth(ctx context.Context) error
- func CheckGHAuthForHost(ctx context.Context, host string) error
- func ClosePR(ctx context.Context, ref RepoRef, prNumber int) error
- func DeleteKeychainToken() error
- func DeleteKeychainTokenForAccount(host, username string) error
- func GeneratePRPrompt(pr *PRInfo, includeDescription bool) string
- func GetCLIToken(ctx context.Context, host string) (string, error)
- func GetCurrentUserLogin(ctx context.Context, host string) (string, error)
- func GetCurrentUserLoginWithToken(ctx context.Context, host, token string) (string, error)
- func GetKeychainToken() string
- func GetKeychainTokenForAccount(host, username string) string
- func GetKeychainTokenForHost(host string) string
- func GetPRDiff(ctx context.Context, ref RepoRef, prNumber int) (string, error)
- func GetRemoteURL(repoPath string) (string, error)
- func GhBaseURL() string
- func GitHubCallSiteFrom(ctx context.Context) string
- func HTTPClient() *http.Client
- func IsGitHubCom(host string) bool
- func IsGitHubRef(input string) bool
- func IsGitHubRefWithHosts(input string, enterpriseHosts []string) bool
- func IsTerminal(priority PRPriority) bool
- func MergePR(ctx context.Context, ref RepoRef, prNumber int, method string) error
- func NewConditionalRequest(ctx context.Context, path string, cache *ETagCache) (*http.Request, error)
- func NewConditionalRequestNoCache(ctx context.Context, path string) (*http.Request, error)
- func NormalizeHost(host string) string
- func PollDeviceAuth(ctx context.Context, host, clientIDOverride, deviceCode string) (string, error)
- func PostPRComment(ctx context.Context, ref RepoRef, prNumber int, body string) error
- func ResetPriorityAdmissionFlagCacheForTest()
- func ResetRateLimiterForTest(t testing.TB)
- func ResourceForRequest(req *http.Request) string
- func RestBaseURLForHost(host string) string
- func SetCommitStatus(repo RepoRef, req CommitStatusRequest) error
- func SetEnterpriseBaseURLOverride(host, url string)
- func SetGHHTTPBaseTransportForTest(rt http.RoundTripper) (restore func())
- func SetGhBaseURLForTest(url string) (restore func())
- func SetKeychainToken(token string) error
- func SetKeychainTokenForAccount(host, username, token string) error
- func StoreTokenForDiscoveredUser(ctx context.Context, host, token string) error
- func WithGitHubCallOrigin(ctx context.Context, origin CallOrigin) context.Context
- func WithGitHubCallSite(ctx context.Context, callSite string) context.Context
- type AccountRef
- type AccountToken
- type CLIHost
- type CachedAccount
- type CallOrigin
- type CheckItem
- type CommitResult
- type CommitStatusRequest
- type CommitStatusState
- type DeviceAuthStart
- type ETagCache
- type IssueResult
- type PRAnnotationSession
- type PRAnnotationWorktree
- type PRComment
- type PRInfo
- func GetPRByNumber(ctx context.Context, ref RepoRef, prNumber int) (*PRInfo, error)
- func GetPRForBranch(ctx context.Context, ref RepoRef, branch string) (*PRInfo, error)
- func GetPRForBranchConditional(ctx context.Context, ref RepoRef, branch, etag string) (info *PRInfo, newEtag string, changed bool, err error)
- func GetPRInfo(owner, repo string, prNumber int) (*PRInfo, error)
- func GetPRInfoConditional(ctx context.Context, ref RepoRef, prNumber int, cache *ETagCache) (*PRInfo, bool, error)
- func GetPRInfoCtx(ctx context.Context, ref RepoRef, prNumber int) (*PRInfo, error)
- func GetPRInfoGraphQL(ctx context.Context, owner, repo string, prNumber int) (*PRInfo, error)
- type PRPriority
- type PRResult
- type ParsedGitHubRef
- type RateLimiter
- type RateLimiterSnapshot
- type RefType
- type RepoRef
- type RepoResult
- type ResourceQuota
- type ReviewItem
- type UserPR
- type UserPRCache
- func (c *UserPRCache) Annotate(sessions []PRAnnotationSession, worktrees []PRAnnotationWorktree)
- func (c *UserPRCache) GetAll() []UserPR
- func (c *UserPRCache) GetCachedAccounts() []CachedAccount
- func (c *UserPRCache) GetCachedLogin() string
- func (c *UserPRCache) GetCachedLogins() []string
- func (c *UserPRCache) InvalidateLoginCache()
- func (c *UserPRCache) Refresh(ctx context.Context) error
- func (c *UserPRCache) SetOnUpdated(fn func(prs []UserPR))
- func (c *UserPRCache) Start(ctx context.Context)
- func (c *UserPRCache) Stop()
- func (c *UserPRCache) Subscribe(id string, ch chan []UserPR)
- func (c *UserPRCache) Unsubscribe(id string)
- type UserPRCacheConfig
Constants ¶
const ( PRStateOpen = "open" PRStateClosed = "closed" PRStateMerged = "merged" )
PR state strings. These are the single source of truth for the three PR lifecycle states surfaced by PRInfo.State — GetPRByNumber and any other consumer of PRInfo.State should compare against these constants rather than hardcoding "open"/"closed"/"merged" string literals.
Variables ¶
var DefaultRateLimiter = &RateLimiter{}
DefaultRateLimiter is the shared GitHub API rate limiter used by all native HTTP calls. It is updated automatically by rateLimitTransport on every response; pollers check IsLimited() before dispatching work.
var ErrAuthorizationPending = errors.New("authorization_pending")
ErrAuthorizationPending is returned by PollDeviceAuth while waiting for the user to complete the browser authorization step.
var ErrDeviceFlowExpired = errors.New("expired_token")
ErrDeviceFlowExpired is returned when the device code has expired before the user authorized. The caller should restart the flow.
var ErrGitHubAccessDenied = errors.New("github: access denied")
ErrGitHubAccessDenied is 401, or 403 with no rate-limit signal — retrying with the same credentials will not change the outcome.
var ErrGitHubRefNotFound = errors.New("github: reference not found")
ErrGitHubRefNotFound is a definitive 404: the referenced PR/issue/commit doesn't exist, or exists but is invisible to the configured token (GitHub disguises "exists, no access" as "not found" for security).
var ErrNoPR = errors.New("no pull request found for branch")
ErrNoPR is returned by GetPRForBranch when no pull request exists for the branch.
var ErrNotAuthenticated = errors.New("github token not configured")
ErrNotAuthenticated is returned by SearchUserRepos and ListRepoIssues when no GitHub token is configured (GITHUB_TOKEN, GH_TOKEN, or OS keychain).
Functions ¶
func CheckGHAuth ¶
CheckGHAuth verifies github.com authentication. Equivalent to CheckGHAuthForHost(ctx, "") — kept for callers with no host to give it yet.
ctx is threaded through to the underlying request so GitHubCallOriginFrom sees the caller's origin tag (e.g. an interactive PR action) instead of always falling back to the background-tier default — a singleflight-joined call still only uses the first caller's ctx for the shared in-flight request, the same limitation singleflight coalescing always has.
func CheckGHAuthForHost ¶ added in v1.54.0
CheckGHAuthForHost verifies GitHub authentication for host ("" means github.com) via GET /user using the native HTTP client. No subprocess is invoked — avoids forkExec lock contention. Results are cached for 5 minutes, keyed per host so a github.com auth failure/success is never conflated with a GHE host's. Concurrent callers for the same host share a single inflight call via singleflight.
func DeleteKeychainToken ¶ added in v1.35.0
func DeleteKeychainToken() error
DeleteKeychainToken removes the legacy single-account token.
func DeleteKeychainTokenForAccount ¶ added in v1.35.0
DeleteKeychainTokenForAccount removes the token for username on host and removes it from the accounts list.
func GeneratePRPrompt ¶
GeneratePRPrompt generates a context prompt from PR information This can be used to initialize a Claude Code session with PR context
func GetCLIToken ¶ added in v1.41.0
GetCLIToken shells out to `gh auth token --hostname <host>` to retrieve the token gh has stored (keyring or hosts.yml) for host. Requires the gh CLI to be installed and already authenticated to that host.
func GetCurrentUserLogin ¶ added in v1.35.0
GetCurrentUserLogin returns the GitHub login of the authenticated user on host ("" means github.com) via GET /user. Returns an empty string (not an error) when unauthenticated so callers can degrade gracefully. Returns a non-nil error when the request is rate limited instead — that isn't the same as being unauthenticated.
func GetCurrentUserLoginWithToken ¶ added in v1.35.0
GetCurrentUserLoginWithToken fetches the GitHub login for an explicit token on host ("" means github.com). Returns ("", nil) when the token is invalid or unauthenticated, and a non-nil error when the request is rate limited instead.
func GetKeychainToken ¶ added in v1.35.0
func GetKeychainToken() string
GetKeychainToken returns a token for the default single-token auth flow (getGHToken/newGHRequest), which always targets api.github.com. It must therefore prefer a github.com account's token over any other configured host: returning an enterprise account's token here sends valid credentials to the wrong API and GitHub correctly rejects them as 401 Bad credentials, regardless of account order in ListKeychainAccounts. Falls back to the first account of any host, then the legacy single-account slot, only when no github.com account is configured.
func GetKeychainTokenForAccount ¶ added in v1.35.0
GetKeychainTokenForAccount returns the stored token for username on host, or "".
func GetKeychainTokenForHost ¶ added in v1.41.0
GetKeychainTokenForHost returns any stored token for host, regardless of which account it belongs to. Backlog sync plugins (session/backlog_plugin_github.go, session/backlog_plugin_github_prs.go) only know a host, not a username, so they can't call GetKeychainTokenForAccount directly. Falls back to the legacy single-account slot for github.com when no named account matches.
func GetRemoteURL ¶
GetRemoteURL returns the remote URL of a repository (used to determine owner/repo). Reads the URL out of the repo's own git config via go-git (session/git.OpenRepo, which sets EnableDotGitCommonDir so linked worktrees resolve correctly) instead of shelling out to `git remote get-url` — no subprocess, so no ForkLock contention. Cached and singleflight-coalesced per canonical repo (shared across all its worktrees), not per repoPath — a repo's remote is identical for every worktree, so without the shared key, every session worktree caused its own cache miss.
func GhBaseURL ¶ added in v1.35.0
func GhBaseURL() string
GhBaseURL returns the current GitHub REST/GraphQL API base URL for github.com.
func GitHubCallSiteFrom ¶ added in v1.54.0
GitHubCallSiteFrom returns the call site tagged on ctx, or "" ("unattributed") when none was set — an untagged native HTTP call is still counted, just not attributable to a specific wrapper.
func HTTPClient ¶ added in v1.42.0
HTTPClient returns the shared GitHub HTTP client, so other packages (e.g. session's backlog GitHub plugins) route their calls through the same rate-limit-observing Transport instead of constructing their own client.
func IsGitHubCom ¶ added in v1.41.0
IsGitHubCom reports whether host (after normalization) is github.com.
func IsGitHubRef ¶
IsGitHubRef checks if the input string looks like a GitHub URL or reference This is a quick check that doesn't validate the full format
func IsGitHubRefWithHosts ¶ added in v1.41.0
IsGitHubRefWithHosts is the host-aware variant of IsGitHubRef, additionally recognizing URLs against any of the supplied enterpriseHosts.
func IsTerminal ¶ added in v1.12.0
func IsTerminal(priority PRPriority) bool
IsTerminal returns true if the priority indicates a terminal PR state. Terminal sessions should not be polled at normal frequency.
func NewConditionalRequest ¶ added in v1.54.0
func NewConditionalRequest(ctx context.Context, path string, cache *ETagCache) (*http.Request, error)
NewConditionalRequest builds an authenticated GET request to the github.com REST API for path, setting If-None-Match from cache's last-known ETag for path (if any) so GitHub can answer with a zero-rate-limit-cost 304 when nothing has changed. This only builds the request — the caller reads the response's ETag header and stores it back via cache.set(...) itself, mirroring GetPRInfoConditional's existing split between building the request and handling the response. This is one of the two approved constructors for new native GitHub call sites (the other being NewConditionalRequestNoCache); see .claude/rules/norawghrequest.md.
func NewConditionalRequestNoCache ¶ added in v1.54.0
NewConditionalRequestNoCache builds an authenticated GET request to the github.com REST API for path with no conditional (If-None-Match) semantics — the deliberate, reviewable opt-out for a call site that genuinely has no need for ETag caching (e.g. a one-off fetch with no meaningful cache key), so skipping conditional semantics is a visible decision rather than a silent omission. Prefer NewConditionalRequest whenever an *ETagCache is available.
func NormalizeHost ¶ added in v1.41.0
NormalizeHost returns the canonical form of a GitHub host: lowercased, no scheme, no trailing slash, and "" mapped to github.com. Hostnames are case-insensitive (DNS), and GHE hosts are free-text admin/user input, so lowercasing here keeps registration and URL-match comparisons consistent regardless of how a host was typed.
func PollDeviceAuth ¶ added in v1.35.0
PollDeviceAuth polls GitHub's token endpoint once.
- Returns (token, nil) on success — the caller should store the token.
- Returns ("", ErrAuthorizationPending) if the user hasn't approved yet.
- Returns ("", ErrDeviceFlowExpired) if the device code has expired.
- Returns ("", err) for any other error.
func PostPRComment ¶
PostPRComment posts a comment on a pull request.
func ResetPriorityAdmissionFlagCacheForTest ¶ added in v1.54.0
func ResetPriorityAdmissionFlagCacheForTest()
ResetPriorityAdmissionFlagCacheForTest clears priorityAdmissionEnabled's TTL cache so a consumer-package test that flips githubPriorityAdmissionFlagName sees the change immediately. Exported because _test.go files (where this package keeps its own copy) aren't importable across packages.
func ResetRateLimiterForTest ¶ added in v1.45.1
ResetRateLimiterForTest swaps in a fresh DefaultRateLimiter for the duration of t, restoring the original via t.Cleanup. Any test — in this package or a consumer package (e.g. session's GitHub backlog plugins) — that drives a request through HTTPClient() and can trigger a rate-limit -setting response (403 with Retry-After, 403 with X-RateLimit-Remaining: 0, or 429) must call this. DefaultRateLimiter is a package-level global (rate_limit.go) shared by every test in the same test binary/process, and since rateLimitTransport.RoundTrip fails fast when it's already limited, one test's rate-limit fixture otherwise poisons every test that runs after it in that process.
func ResourceForRequest ¶ added in v1.54.0
ResourceForRequest classifies an outbound GitHub HTTP request into the GitHub API resource bucket it consumes quota from (core, search, graphql), mirroring GitHub's own per-endpoint resource assignment. Derived from the request rather than the eventual response, since AdmitOrigin must decide before dispatch — before any X-RateLimit-Resource response header exists.
func RestBaseURLForHost ¶ added in v1.41.0
RestBaseURLForHost returns the REST API base URL for host, including a trailing slash. For github.com this returns GhBaseURL unchanged, preserving the test seam that overrides it directly.
func SetCommitStatus ¶ added in v1.44.0
func SetCommitStatus(repo RepoRef, req CommitStatusRequest) error
SetCommitStatus posts a commit status to the GitHub Statuses API (POST /repos/{owner}/{repo}/statuses/{sha}) via the gh CLI. repo.Host() "" means github.com; a GHE host is targeted via --hostname/GH_HOST, mirroring IsForkRepo/GetPRComments in github/client.go.
This targets the Statuses API rather than the Checks API because Checks API writes are restricted to GitHub Apps, and this repo's entire GitHub write path is PAT/OAuth-token-based via the gh CLI (see ADR-001 in project_plans/pr-comment-check-runs).
func SetEnterpriseBaseURLOverride ¶ added in v1.52.0
func SetEnterpriseBaseURLOverride(host, url string)
SetEnterpriseBaseURLOverride registers a per-host API override for tests (pass url == "" to clear it), mirroring GhBaseURL's test seam for github.com. host is normalized the same way RestBaseURLForHost/ graphQLURLForHost normalize their lookup key, so callers may pass a raw host (with scheme, casing, or trailing slash) and still hit the override.
func SetGHHTTPBaseTransportForTest ¶ added in v1.54.0
func SetGHHTTPBaseTransportForTest(rt http.RoundTripper) (restore func())
SetGHHTTPBaseTransportForTest swaps the innermost RoundTripper in ghHTTPClient's transport chain (normally http.DefaultTransport) for rt, returning a restore func — mirroring SetGhBaseURLForTest's swap-a-var-return-a-restore-func pattern. The githubTelemetryTransport, otelhttp.NewTransport, and rateLimitTransport layers above rt keep running unchanged, so a test using this seam can observe GitHubCallOriginFrom(req.Context()) on whatever request actually reaches the "wire" while those real layers still execute.
func SetGhBaseURLForTest ¶ added in v1.52.0
func SetGhBaseURLForTest(url string) (restore func())
SetGhBaseURLForTest overrides GhBaseURL for the duration of a test (e.g. to point at an httptest.Server so requests never reach the real API) and returns a restore func. A missing trailing slash is added — graphQLURLForHost builds the GraphQL endpoint via straight concatenation (GhBaseURL()+"graphql"), so an un-slashed override would otherwise produce a malformed URL.
func SetKeychainToken ¶ added in v1.35.0
SetKeychainToken stores a token under the legacy single-account slot. Prefer SetKeychainTokenForAccount when the username is known.
func SetKeychainTokenForAccount ¶ added in v1.35.0
SetKeychainTokenForAccount stores a token under a per-account key and adds the account to the accounts list if not already present.
func StoreTokenForDiscoveredUser ¶ added in v1.35.0
StoreTokenForDiscoveredUser fetches the GitHub login for token on host and stores it under the per-account keychain slot. Falls back to the legacy slot (github.com only) if the login cannot be determined.
func WithGitHubCallOrigin ¶ added in v1.54.0
func WithGitHubCallOrigin(ctx context.Context, origin CallOrigin) context.Context
WithGitHubCallOrigin returns a copy of ctx tagged with the given CallOrigin.
func WithGitHubCallSite ¶ added in v1.54.0
WithGitHubCallSite returns a copy of ctx tagged with callSite, the fixed, bounded name of the wrapper function issuing a GitHub call (e.g. "pr.view.graphql"). Mirrors WithGitHubCallOrigin's pattern (call_origin.go) but for the orthogonal "which wrapper" dimension: runGHCLICommand (gh_exec.go) sets this per gh-CLI call site directly on its own span, and githubTelemetryTransport.RoundTrip (telemetry_transport.go) reads it via GitHubCallSiteFrom for every native HTTP call site, so both paths populate plan.md's Observability Plan `call_site` label the same way.
Types ¶
type AccountRef ¶ added in v1.41.0
AccountRef identifies one connected GitHub account by username and host.
func ListKeychainAccounts ¶ added in v1.35.0
func ListKeychainAccounts() []AccountRef
ListKeychainAccounts returns the ordered list of connected GitHub accounts. The stored shape is normally []AccountRef; a legacy []string of usernames (from before per-host support) is transparently read as github.com accounts.
type AccountToken ¶ added in v1.35.0
type AccountToken struct {
Username string // empty for the legacy single-account slot
Host string
Token string
}
AccountToken pairs a GitHub username and host with its token.
func GetAllKeychainTokens ¶ added in v1.35.0
func GetAllKeychainTokens() []AccountToken
GetAllKeychainTokens returns all stored tokens across all named accounts plus the legacy single-account slot. Each entry is a (username, host, token) tuple.
type CLIHost ¶ added in v1.41.0
CLIHost describes a GitHub host the `gh` CLI is already authenticated to.
func ListCLIHosts ¶ added in v1.41.0
ListCLIHosts reads gh CLI's hosts.yml to discover which GitHub hosts the user has already run `gh auth login` against. It only reads the host and associated username — safe to display in the UI — never a token: gh 2.x stores tokens in the OS keyring, not in this file, so retrieving the actual token still requires GetCLIToken. Returns an empty slice (not an error) when gh has never been configured on this machine.
type CachedAccount ¶ added in v1.41.0
CachedAccount is a resolved (login, host) pair for the accounts list RPC.
type CallOrigin ¶ added in v1.54.0
type CallOrigin string
CallOrigin identifies which code path is making a GitHub call, so telemetry (Phase 1) and admission control (Phase 3) can attribute and prioritize traffic by source.
const ( // OriginInteractive is a call made synchronously on behalf of a user-facing // RPC (e.g. GetPRInfo, MergePR triggered from the UI). OriginInteractive CallOrigin = "interactive" // OriginPRStatusPoller is a call made by the background PR status poller. OriginPRStatusPoller CallOrigin = "pr_status_poller" // OriginWorktreePRPoller is a call made by the background worktree PR poller. OriginWorktreePRPoller CallOrigin = "worktree_pr_poller" // OriginBacklogSync is a call made by the backlog GitHub-issue sync loop. OriginBacklogSync CallOrigin = "backlog_sync" // OriginWebhookReconcile is a call made in response to a GitHub webhook // event reconciling local state. OriginWebhookReconcile CallOrigin = "webhook_reconcile" )
func GitHubCallOriginFrom ¶ added in v1.54.0
func GitHubCallOriginFrom(ctx context.Context) CallOrigin
GitHubCallOriginFrom returns the CallOrigin tagged on ctx. Per ADR-002, an untagged context defaults to OriginPRStatusPoller (the background tier) rather than OriginInteractive, so an un-instrumented call path is treated as background traffic instead of silently getting interactive priority.
type CheckItem ¶ added in v1.49.0
CheckItem is one itemized CI check from a PR's statusCheckRollup — the data getCheckConclusion collapses into a single string; exported so callers that want the itemized view (e.g. the VCS tab's "why blocked" rollup) don't need to.
type CommitResult ¶ added in v1.41.0
CommitResult is the domain return type for GetCommit.
func GetCommit ¶ added in v1.41.0
func GetCommit(ctx context.Context, account AccountRef, repo RepoRef, sha string) (*CommitResult, error)
GetCommit fetches a single commit by SHA via the GitHub REST API (native net/http, same auth/error-classification mechanism as GetIssue/GetPR). account.Host "" means github.com; account.Username, when non-empty, selects among multiple connected accounts for that host (see getGHTokenForAccount). Returns ErrNotAuthenticated when no token is configured.
type CommitStatusRequest ¶ added in v1.44.0
type CommitStatusRequest struct {
SHA string
State CommitStatusState
StatusContext string
Description string
TargetURL string
}
CommitStatusRequest bundles the fields needed to post a commit status, avoiding a same-typed-string parameter pile on SetCommitStatus (see primitive-obsession-checklist.md).
func NewCommitStatusRequest ¶ added in v1.44.0
func NewCommitStatusRequest(sha string, state CommitStatusState, statusContext, description string) (CommitStatusRequest, error)
NewCommitStatusRequest validates and constructs a CommitStatusRequest. TargetURL is optional and may be left empty.
type CommitStatusState ¶ added in v1.44.0
type CommitStatusState string
CommitStatusState is the state of a commit status posted via the GitHub Statuses API.
const ( CommitStatusStatePending CommitStatusState = "pending" CommitStatusStateSuccess CommitStatusState = "success" CommitStatusStateError CommitStatusState = "error" CommitStatusStateFailure CommitStatusState = "failure" )
type DeviceAuthStart ¶ added in v1.35.0
type DeviceAuthStart struct {
DeviceCode string
UserCode string
VerificationURI string
// ExpiresIn is how long the code is valid (seconds).
ExpiresIn int
// Interval is the minimum poll interval suggested by GitHub (seconds).
Interval int
}
DeviceAuthStart holds the values returned by the first step of the Device Flow: the user-visible code and the URL where they enter it.
func StartDeviceAuth ¶ added in v1.35.0
func StartDeviceAuth(ctx context.Context, host, clientIDOverride string) (*DeviceAuthStart, error)
StartDeviceAuth initiates the GitHub Device Flow against host and returns the codes the user must enter at verification_uri. clientID is the OAuth App client ID to use; pass "" for github.com to use the default/env-configured one. The returned DeviceAuthStart.DeviceCode must be passed to PollDeviceAuth.
type ETagCache ¶ added in v1.12.0
type ETagCache struct {
// contains filtered or unexported fields
}
ETagCache stores ETags and cached PRInfo responses per (owner, repo, prNumber). Using conditional requests (If-None-Match) allows GitHub to return 304 Not Modified responses that cost zero rate-limit quota when the PR has not changed. sync.Map gives lock-free reads in the steady state — entries are written once on first PR discovery and then read on every subsequent poll tick.
func NewETagCache ¶ added in v1.12.0
func NewETagCache() *ETagCache
NewETagCache creates a new empty ETagCache.
func (*ETagCache) Invalidate ¶ added in v1.54.0
Invalidate drops the cached entry for (ref, prNumber), safe to call concurrently with get/set. The next GetPRInfoConditional call for that key omits If-None-Match, forcing a fresh, unconditional 200 fetch instead of a stale 304.
type IssueResult ¶ added in v1.35.0
type IssueResult struct {
Number int
Title string
Body string
Author string
State string
URL string
Labels []string
CreatedAt time.Time
UpdatedAt time.Time
IsPR bool
// Host is the GitHub host the result came from ("" means github.com),
// echoed from the account.Host passed to ListRepoIssues.
Host string
}
IssueResult is the domain return type for ListRepoIssues and GetIssue.
func GetIssue ¶ added in v1.41.0
func GetIssue(ctx context.Context, account AccountRef, repo RepoRef, number int) (*IssueResult, error)
GetIssue fetches a single issue (including its body) by number. account.Host "" means github.com; account.Username, when non-empty, selects among multiple connected accounts for that host (see getGHTokenForAccount). Returns ErrNotAuthenticated when no token is configured.
func ListRepoIssues ¶ added in v1.35.0
func ListRepoIssues(ctx context.Context, account AccountRef, repo RepoRef, state, search string, limit int) ([]IssueResult, error)
ListRepoIssues fetches issues for a specific repo. When search is empty it uses GET /repos/{owner}/{repo}/issues. When search is non-empty it uses GET /search/issues. account.Host "" means github.com; account.Username, when non-empty, selects among multiple connected accounts for that host (see getGHTokenForAccount). Returns ErrNotAuthenticated when no token is configured.
type PRAnnotationSession ¶ added in v1.35.0
type PRAnnotationSession struct {
ID string
Branch string
Repo RepoRef
PRNumber int // fallback: match by PR number when branch name doesn't match headRef
}
PRAnnotationSession carries session data needed to annotate UserPR entries. Defined here (not in the session package) to avoid an import cycle: session imports github, so github cannot import session.
Repo is a typed value object: holding a valid RepoRef proves owner and repo are non-empty. Sessions without a resolvable GitHub repo are skipped.
type PRAnnotationWorktree ¶ added in v1.35.0
PRAnnotationWorktree carries worktree data for annotation.
type PRComment ¶
type PRComment struct {
ID int `json:"id"`
Author string `json:"author"`
Body string `json:"body"`
CreatedAt time.Time `json:"createdAt"`
Path string `json:"path,omitempty"` // For review comments
Line int `json:"line,omitempty"` // For review comments
IsReview bool `json:"isReview,omitempty"` // True if this is a review comment
}
PRComment represents a comment on a PR (either issue comment or review comment)
type PRInfo ¶
type PRInfo struct {
Number int `json:"number"`
Title string `json:"title"`
Body string `json:"body"`
HeadRef string `json:"headRefName"`
HeadSHA string `json:"headRefOid"`
BaseRef string `json:"baseRefName"`
State string `json:"state"`
Author string `json:"author"`
Labels []string `json:"labels"`
HTMLURL string `json:"url"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
IsDraft bool `json:"isDraft"`
Mergeable string `json:"mergeable"`
Additions int `json:"additions"`
Deletions int `json:"deletions"`
ChangedFiles int `json:"changedFiles"`
// Review and CI status fields (populated by GetPRInfo with extended fields)
ReviewDecision string // "approved" / "changes_requested" / "review_required" / ""
ApprovedCount int // Count of current non-dismissed APPROVED reviews
ChangesRequestedCount int // Count of current non-dismissed CHANGES_REQUESTED reviews
CheckConclusion string // "success" / "failure" / "pending" / "action_required" / "neutral" / ""
CheckStatus string // "completed" / "in_progress" / ""
Checks []CheckItem
Reviews []ReviewItem
}
PRInfo contains metadata about a GitHub pull request
func GetPRByNumber ¶ added in v1.41.0
GetPRByNumber fetches a single pull request by its number using the GitHub REST API directly (no gh subprocess). Unlike GetPRForBranch, which looks a PR up by head branch name and can therefore match the wrong PR when a branch is reused or renamed, GetPRByNumber looks a PR up by its immutable number — the root-cause fix for branch-name-keyed lookups matching stale or unrelated PRs.
Returns ErrNoPR when no pull request exists for the given number (HTTP 404). Before returning success, the response's base.repo.full_name is compared against the requested owner/repo; a mismatch returns a non-nil, non-ErrNoPR error rather than trusting the response body blindly.
func GetPRForBranch ¶ added in v1.12.0
GetPRForBranch finds the GitHub PR associated with a branch. Uses the GitHub REST API directly (no gh subprocess) to avoid forkExec lock contention. Returns ErrNoPR when no pull request exists for the branch.
func GetPRForBranchConditional ¶ added in v1.37.0
func GetPRForBranchConditional(ctx context.Context, ref RepoRef, branch, etag string) (info *PRInfo, newEtag string, changed bool, err error)
GetPRForBranchConditional is GetPRForBranch with ETag conditional request support. Pass the previously returned newEtag (empty string for first call). Returns (nil, etag, false, nil) on 304 Not Modified — caller should treat as unchanged. Every error path also returns changed=false, including ErrNotAuthenticated when no token is configured — callers must check err before treating changed=false as "unchanged, no error."
func GetPRInfoConditional ¶ added in v1.12.0
func GetPRInfoConditional(ctx context.Context, ref RepoRef, prNumber int, cache *ETagCache) (*PRInfo, bool, error)
GetPRInfoConditional fetches PR info using ETag conditional requests. Uses native net/http instead of a gh subprocess to avoid forkExec lock contention. Returns (info, changed, error).
- changed=false means 304 Not Modified; info contains the cached value.
- changed=true means 200 OK; info contains freshly fetched data.
- Both info and changed may be zero values when an error is returned.
func GetPRInfoCtx ¶ added in v1.12.0
GetPRInfoCtx fetches metadata for a pull request with context support. Includes review decisions and CI/check status. ref.Host() "" means github.com.
func GetPRInfoGraphQL ¶ added in v1.54.0
GetPRInfoGraphQL fetches metadata for a pull request via GitHub's GraphQL API instead of GetPRInfoCtx's `gh pr view` subprocess shell-out, producing a field-identical *PRInfo (verified by the parity test in client_graphql_test.go). It issues one POST through ghHTTPClient (see http_client.go), so it automatically gets the same rate-limit admission control and OTel telemetry (githubTelemetryTransport) every other native GitHub call gets — no separate instrumentation call needed here.
Unlike GetPRInfoCtx, this does not call CheckGHAuth() first: none of this package's other native-HTTP call sites (GetPRForBranch, GetPRByNumber) do either — they dispatch the request and let classifyGHResponse turn a 401/403 into an error, which is what this function does too. CheckGHAuth's pre-flight check exists specifically to give the `gh` subprocess path a clearer error than a raw exec failure would; that rationale doesn't apply to a direct HTTP call.
This function is wired: GetPRInfoCtx (client.go) dispatches here when the github:graphql-pr-info feature flag (githubGraphQLMigrationFlagName) is enabled — see that constant's doc comment for the flag's default-off rationale.
type PRPriority ¶ added in v1.12.0
type PRPriority string
PRPriority is a derived single-enum priority computed from compound PR state.
const ( PRPriorityBlocking PRPriority = "blocking" // changes requested or CI failing PRPriorityReady PRPriority = "ready" // approved + CI passing PRPriorityPending PRPriority = "pending" // awaiting review or checks running PRPriorityDraft PRPriority = "draft" // PR is a draft PRPriorityComplete PRPriority = "complete" // PR is merged or closed PRPriorityNoPR PRPriority = "no_pr" // no PR found for branch PRPriorityAuthError PRPriority = "auth_error" // gh CLI not authenticated PRPriorityError PRPriority = "error" // transient error fetching status )
func DerivePRPriority ¶ added in v1.12.0
func DerivePRPriority(info *PRInfo) PRPriority
DerivePRPriority computes a single priority enum from compound PR state. Returns PRPriorityNoPR if info is nil.
type PRResult ¶ added in v1.41.0
PRResult is the domain return type for GetPR — a lean existence check, not the richer PRInfo returned by GetPRInfoCtx (which shells out to `gh`).
func GetPR ¶ added in v1.41.0
GetPR fetches a single pull request by number via the GitHub REST API (native net/http, not the `gh` CLI subprocess GetPRInfoCtx uses) — a lean existence check sharing GetIssue's auth mechanism and error classification. account.Host "" means github.com; account.Username, when non-empty, selects among multiple connected accounts for that host (see getGHTokenForAccount). Returns ErrNotAuthenticated when no token is configured.
type ParsedGitHubRef ¶
type ParsedGitHubRef struct {
Type RefType
Host string // GitHub host, e.g. "github.com" or a GHES hostname; "" means github.com
Owner string
Repo string
PRNumber int // Only populated for RefTypePR
IssueNumber int // Only populated for RefTypeIssue
Branch string // Populated for RefTypeBranch, or PR head branch after fetching
CommitSHA string // Only populated for RefTypeCommit
FilePath string // Only populated for RefTypeFile (path within repo)
LineStart int // Only populated for RefTypeFile (optional line number)
LineEnd int // Only populated for RefTypeFile (optional end line for range)
BaseBranch string // Only populated for RefTypeCompare (the base of comparison)
HeadBranch string // Only populated for RefTypeCompare (the head being compared)
Tag string // Only populated for RefTypeRelease
OriginalURL string // The original input string
}
ParsedGitHubRef represents a parsed GitHub URL or reference
func ParseGitHubRef ¶
func ParseGitHubRef(input string) (*ParsedGitHubRef, error)
ParseGitHubRef parses a GitHub URL or shorthand reference into a ParsedGitHubRef Supported formats:
- https://github.com/owner/repo/pull/123
- https://github.com/owner/repo/tree/branch-name
- https://github.com/owner/repo/blob/branch/path/to/file.go
- https://github.com/owner/repo/blob/branch/file.go#L10 (with line number)
- https://github.com/owner/repo/blob/branch/file.go#L10-L20 (with line range)
- https://github.com/owner/repo/commit/abc123
- https://github.com/owner/repo/issues/42
- https://github.com/owner/repo/compare/main...feature
- https://github.com/owner/repo/releases/tag/v1.0.0
- https://github.com/owner/repo
- github.com/owner/repo
- git@github.com:owner/repo.git (SSH)
- ssh://git@github.com/owner/repo (SSH protocol)
- owner/repo:branch-name
- owner/repo
func ParseGitHubRefWithHosts ¶ added in v1.41.0
func ParseGitHubRefWithHosts(input string, enterpriseHosts []string) (*ParsedGitHubRef, error)
ParseGitHubRefWithHosts parses a GitHub URL or shorthand reference into a ParsedGitHubRef, matching either github.com or any of the supplied enterpriseHosts. The Host field of the returned ref is set to the matched host (or left empty for shorthand references, which default to github.com).
func (*ParsedGitHubRef) CloneURL ¶
func (p *ParsedGitHubRef) CloneURL() string
CloneURL returns the HTTPS clone URL for the repository
func (*ParsedGitHubRef) DisplayName ¶
func (p *ParsedGitHubRef) DisplayName() string
DisplayName returns a human-readable name for the reference
func (*ParsedGitHubRef) HTMLURL ¶
func (p *ParsedGitHubRef) HTMLURL() string
HTMLURL returns the human-readable GitHub URL
func (*ParsedGitHubRef) RepoFullName ¶
func (p *ParsedGitHubRef) RepoFullName() string
RepoFullName returns "owner/repo" format
func (*ParsedGitHubRef) SuggestedSessionName ¶
func (p *ParsedGitHubRef) SuggestedSessionName() string
SuggestedSessionName returns a suggested session name based on the reference
type RateLimiter ¶ added in v1.37.0
type RateLimiter struct {
// contains filtered or unexported fields
}
RateLimiter tracks GitHub primary and secondary rate limit state.
Primary rate limit — hourly quota per authenticated token (5000 req/hr for PAT).
Signalled by X-RateLimit-Remaining → 0 and X-RateLimit-Reset (Unix epoch, seconds). Response: 403 or 429 with X-RateLimit-Remaining: 0.
Secondary rate limit — concurrent connection / per-minute burst limits.
Signalled by 429 or 403 with Retry-After header present. X-RateLimit-Remaining may still be nonzero.
Detection order: Retry-After present → secondary; remaining == 0 → primary; neither → auth/permission error (do not pause polling).
func (*RateLimiter) AdmitOrigin ¶ added in v1.54.0
func (r *RateLimiter) AdmitOrigin(origin CallOrigin, resource string) (bool, string)
AdmitOrigin decides whether a call for origin targeting resource (as classified by ResourceForRequest) should proceed. OriginInteractive is always admitted while the token isn't globally rate limited — callers must still check IsLimited() themselves first (rateLimitTransport.RoundTrip does), since AdmitOrigin only makes the priority-tier decision, not the already-limited one. Any other origin is rejected once resource's own Remaining drops below backgroundHeadroomPercent of its Limit, so a background poller backs off before it can starve an interactive call. A resource never observed yet (ok == false) is treated as "no data — admit," not "exhausted — reject," since a zero-value ResourceQuota would otherwise read as Remaining: 0. The decision is scoped strictly to resource's own bucket — another resource's quota (e.g. a low-limit search response) never affects it (pre-mortem P1 #2).
func (*RateLimiter) IsLimited ¶ added in v1.37.0
func (r *RateLimiter) IsLimited() (bool, time.Time)
IsLimited returns true and the resume time if the client is currently rate limited.
func (*RateLimiter) Reset ¶ added in v1.47.0
func (r *RateLimiter) Reset()
Reset clears any recorded rate-limit state. DefaultRateLimiter is a package-level singleton shared by every caller of HTTPClient() (see http_client.go) — a test that deliberately simulates a rate-limit response (e.g. to verify the fail-fast behavior rateLimitTransport.RoundTrip added) leaves it limited for every later test in the same binary otherwise, since RoundTrip's IsLimited() pre-check has no per-test scoping.
func (*RateLimiter) Snapshot ¶ added in v1.54.0
func (r *RateLimiter) Snapshot() RateLimiterSnapshot
Snapshot returns a lock-free, point-in-time read of the limiter's state. If Update has never published one, it returns a zero-value RateLimiterSnapshot (Resources is nil) — a caller reading Snapshot().Resources["core"] off a nil map gets ResourceQuota{}'s zero value via Go's safe nil-map-read semantics, not a panic.
func (*RateLimiter) Update ¶ added in v1.37.0
func (r *RateLimiter) Update(resp *http.Response)
Update reads GitHub rate-limit headers from resp and updates the limiter. Called automatically by rateLimitTransport on every response — callers do not need to invoke this manually.
type RateLimiterSnapshot ¶ added in v1.54.0
type RateLimiterSnapshot struct {
Resources map[string]ResourceQuota
RateLimitedUntil time.Time
}
RateLimiterSnapshot is a lock-free, point-in-time read of RateLimiter's state, partitioned by resource so a low-limit resource (e.g. search, 30/hr) can never clobber or mask another resource's (e.g. core, 5000/hr) quota — core/search/graphql share one token but have independent budgets (see pre-mortem P1 #2).
type RefType ¶
type RefType int
RefType represents the type of GitHub reference parsed from a URL or shorthand
type RepoRef ¶ added in v1.35.0
type RepoRef struct {
// contains filtered or unexported fields
}
RepoRef is a value object that bundles a GitHub owner and repository name, plus the host that owns them (empty means github.com). Both owner and repo are non-empty by construction — holding a RepoRef proves the invariant holds without any further nil/empty checks at use sites.
func GetOwnerRepoFromRemote ¶ added in v1.35.0
GetOwnerRepoFromRemote returns a RepoRef for a local git repository by reading the origin remote URL and parsing it. Returns an invalid zero-value RepoRef (not an error) when the remote is not a GitHub URL.
func NewRepoRef ¶ added in v1.35.0
NewRepoRef constructs a github.com RepoRef, returning an error if either field is empty. Equivalent to NewRepoRefWithHost(owner, repo, "").
func NewRepoRefWithHost ¶ added in v1.54.0
NewRepoRefWithHost constructs a RepoRef scoped to host (empty means github.com), returning an error if owner or repo is empty.
func (RepoRef) BranchKey ¶ added in v1.35.0
BranchKey returns the map key used to match sessions by branch: "owner/branch".
func (RepoRef) Host ¶ added in v1.54.0
Host returns the GitHub Enterprise host that owns this repo, or "" for github.com.
func (RepoRef) IsValid ¶ added in v1.35.0
IsValid reports whether the ref was constructed with non-empty owner and repo. The zero value (RepoRef{}) is not valid.
type RepoResult ¶ added in v1.35.0
type RepoResult struct {
Owner string
Repo string
Description string
Private bool
// Host is the GitHub host the result came from ("" means github.com),
// echoed from the account.Host passed to SearchUserRepos so callers can
// tell results from different accounts/hosts apart after merging.
Host string
}
RepoResult is the domain return type for SearchUserRepos.
func SearchUserRepos ¶ added in v1.35.0
func SearchUserRepos(ctx context.Context, account AccountRef, query string, limit int) ([]RepoResult, error)
SearchUserRepos fetches repos accessible to the authenticated user. When query is empty it uses GET /user/repos (all accessible repos sorted by push time). When non-empty it uses GET /search/repositories. account.Host "" means github.com; account.Username, when non-empty, selects among multiple connected accounts for that host (see getGHTokenForAccount). Returns ErrNotAuthenticated when no token is configured.
type ResourceQuota ¶ added in v1.54.0
ResourceQuota is one GitHub API resource's (core, search, graphql, ...) last-observed quota, as reported by that resource's own X-RateLimit-* response headers.
type ReviewItem ¶ added in v1.49.0
ReviewItem is one PR review — the data parseReviewCounts collapses into approved/changes-requested counts, exported so callers can also read the reviewer's Body text (e.g. a CHANGES_REQUESTED review's stated reason).
type UserPR ¶ added in v1.35.0
type UserPR struct {
Owner string
Repo string
Number int
Title string
URL string
HeadRef string
BaseRef string
State string
IsDraft bool
UpdatedAt time.Time
ClosedAt time.Time
MergedAt time.Time
ApprovedCount int
ChangesReqCount int
CheckConclusion string // "success" / "failure" / "pending" / ""
SessionIDs []string
LocalWorktreePath string
}
UserPR is an open GitHub pull request authored by the authenticated user, optionally annotated with local session IDs and worktree paths.
type UserPRCache ¶ added in v1.35.0
type UserPRCache struct {
// contains filtered or unexported fields
}
func NewUserPRCache ¶ added in v1.35.0
func NewUserPRCache() *UserPRCache
NewUserPRCache creates a cache with default configuration.
func NewUserPRCacheWithConfig ¶ added in v1.35.0
func NewUserPRCacheWithConfig(cfg UserPRCacheConfig) *UserPRCache
NewUserPRCacheWithConfig creates a cache with custom configuration.
func (*UserPRCache) Annotate ¶ added in v1.35.0
func (c *UserPRCache) Annotate(sessions []PRAnnotationSession, worktrees []PRAnnotationWorktree)
Annotate enriches the current snapshot with session IDs and worktree paths. It performs a COW update: load → copy → mutate → store. No-op if the snapshot hasn't been populated yet.
func (*UserPRCache) GetAll ¶ added in v1.35.0
func (c *UserPRCache) GetAll() []UserPR
GetAll returns a copy of the current PR snapshot. Returns nil before the first successful fetch.
func (*UserPRCache) GetCachedAccounts ¶ added in v1.41.0
func (c *UserPRCache) GetCachedAccounts() []CachedAccount
GetCachedAccounts returns all connected GitHub accounts with their host.
func (*UserPRCache) GetCachedLogin ¶ added in v1.35.0
func (c *UserPRCache) GetCachedLogin() string
GetCachedLogin returns the first connected GitHub login, or "" if none yet.
func (*UserPRCache) GetCachedLogins ¶ added in v1.35.0
func (c *UserPRCache) GetCachedLogins() []string
GetCachedLogins returns all connected GitHub logins.
func (*UserPRCache) InvalidateLoginCache ¶ added in v1.35.0
func (c *UserPRCache) InvalidateLoginCache()
InvalidateLoginCache clears the cached login state so the next Refresh call re-fetches the authenticated user from the GitHub API. Call this after storing a new token (e.g. after a successful Device Flow auth) so the cache picks up the new credentials immediately.
Bumping loginGen (not just clearing multiLogin) matters because a stale resolveAllLogins call from before the invalidation may still be in flight (e.g. loop()'s unconditional first fetch, started with zero tokens before a caller adds one) — the static singleflight key "login" would otherwise coalesce a Refresh arriving right after this call into that stale call's empty result. Including the generation in the key forces a genuinely new call instead. Confirmed live via go test -race: TestListGitHubAccounts_ AccountOnUnconfiguredEnterpriseHost_IncludesHostInEnterpriseHosts saw EnterpriseHosts come back empty because its Refresh() joined the just-started cache's own stale zero-token fetch.
func (*UserPRCache) Refresh ¶ added in v1.35.0
func (c *UserPRCache) Refresh(ctx context.Context) error
Refresh triggers an immediate fetch from GitHub, coalescing concurrent calls.
func (*UserPRCache) SetOnUpdated ¶ added in v1.35.0
func (c *UserPRCache) SetOnUpdated(fn func(prs []UserPR))
SetOnUpdated atomically registers a callback invoked after every successful refresh. Pass nil to clear. The callback receives the current PR slice. Safe to call at any time, including after Start.
func (*UserPRCache) Start ¶ added in v1.35.0
func (c *UserPRCache) Start(ctx context.Context)
Start launches the background polling goroutine. Safe to call multiple times; only the first call starts the goroutine.
func (*UserPRCache) Stop ¶ added in v1.35.0
func (c *UserPRCache) Stop()
Stop halts background polling and blocks until loop() has actually exited. Without waiting here, a caller (notably a test's t.Cleanup) can return while loop()'s unconditional first fetch (see loop's doc comment) is still running, letting it race the next caller's use of shared package-level state (e.g. go-keyring's mock, which a subsequent test re-initializes via MockInit) — confirmed live via `go test -race`: TestListGitHubAccounts_ AccountOnUnconfiguredEnterpriseHost_IncludesHostInEnterpriseHosts raced against a prior test's still-running fetch() on go-keyring's global state. Safe to call before Start (done is nil, no-op) or more than once (cancel and a receive on an already-closed channel are both idempotent).
func (*UserPRCache) Subscribe ¶ added in v1.35.0
func (c *UserPRCache) Subscribe(id string, ch chan []UserPR)
Subscribe registers a channel to receive PR snapshot updates. The channel must be buffered. The caller is responsible for calling Unsubscribe.
func (*UserPRCache) Unsubscribe ¶ added in v1.35.0
func (c *UserPRCache) Unsubscribe(id string)
Unsubscribe removes a previously registered subscriber channel.
type UserPRCacheConfig ¶ added in v1.35.0
type UserPRCacheConfig struct {
// PollInterval controls how often the cache refreshes from GitHub.
PollInterval time.Duration
// LoginCacheTTL controls how long the authenticated login is cached.
LoginCacheTTL time.Duration
}
UserPRCacheConfig controls polling behaviour.
func DefaultUserPRCacheConfig ¶ added in v1.35.0
func DefaultUserPRCacheConfig() UserPRCacheConfig
DefaultUserPRCacheConfig returns sensible defaults.