Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

GitHub Integration

Modules: src-tauri/src/github.rs, src-tauri/src/github_auth.rs, src-tauri/src/github_account.rs, src-tauri/src/improvement_scan.rs

Integrates with GitHub via GraphQL API for PR status, CI checks, and batch queries. Supports OAuth Device Flow login as an alternative to gh CLI tokens, plus multiple accounts (additional github.com logins and GitHub Enterprise Server) with per-repo bindings.

Multi-Account Model (github_account.rs)

The integration is account-centric: the primary key is a stable GitHubAccountId, not the host. This keeps github.com behaving exactly as before behind an “ambient default” account while enabling additional accounts.

  • GitHubHost — canonical (lowercased, validated) host. is_cloud() → github.com; graphql_url() / rest_base() return api.github.com (+/graphql) for cloud and https://{host}/api/graphql / https://{host}/api/v3 for GHE. is_ambient_default() routes the global-vs-per-account branch points.
  • Account kindsGithubComOAuth / GithubComEnv / GithubComGhCli (the ambient default, existing auth chain), additional named github.com accounts, and GhePat (GitHub Enterprise Server via pasted PAT).
  • Credential storage — github.com keeps Credential::GithubOauthToken (github/oauth-token) unchanged; per-account PATs use Credential::GithubToken(account_id)github/account/{id}/token.
  • Repo bindings{repo_path → account_id, owner, repo, remote_name} persisted per canonical repo root (worktrees resolve to the main root). resolve_repo_account(repo_path) returns RepoResolution::{Bound | NeedsBind(candidates) | NeedsAccount | Unmonitored} — binding-first, single-candidate auto-confirm, ambiguity surfaces all candidates (never a silent origin pick).
  • Per-account isolation (hybrid) — github.com keeps the global breaker/viewer/rate/cooldown fields byte-for-byte; GHE accounts get isolated ghe_state: DashMap<AccountId, GheAccountState>. The poller groups repos by resolved account and runs one batch per account, so a fault on one never opens another’s breaker. Cooldown keys: owner/repo (cloud, unchanged) vs {account_id}:owner/repo (GHE).
  • Limitationfetch_ci_failure_logs (gh-CLI-assisted) is disabled with a clear message for non-github.com accounts; all REST + GraphQL paths route through github_rest_url(host, path) / account-scoped tokens (no hardcoded api.github.com outside GitHubHost + tests).

Multi-account commands

CommandSignatureDescription
github_list_accounts() -> Vec<GitHubAccount>Additional accounts beyond the ambient github.com default
github_add_account(host: String, pat: String) -> GitHubAccountValidate PAT against {rest_base}/user, store token + record (github.com rejected → device flow)
github_remove_account(id: String) -> ()Cascade-remove token + record + bindings + per-account caches
github_bind_repo(repo_path, account_id, remote_name) -> ()Persist a repo→account binding
github_unbind_repo(repo_path: String) -> ()Remove a repo binding
github_list_bindings() -> Vec<Binding>All persisted repo→account bindings
github_resolve_repo(repo_path: String) -> RepoResolutionDtobound / needs-bind / needs-account / unmonitored + candidates

Token Resolution

Priority order (first non-empty wins) for the ambient github.com account:

  1. GH_TOKEN environment variable
  2. GITHUB_TOKEN environment variable
  3. OAuth keyring token (github_auth.rs — stored in OS keyring via keyring crate)
  4. gh_token crate (reads ~/.config/gh/hosts.yml)
  5. gh auth token CLI subprocess

The active token source is tracked in AppState.github_token_source as a TokenSource enum (Env, OAuth, GhCli, Pat, None). resolve_token_for_account(&GitHubAccount) runs this exact chain for github.com and returns the vault PAT (TokenSource::Pat) for GHE accounts.

Tauri Commands — Authentication (github_auth.rs)

CommandSignatureDescription
github_start_login() -> DeviceCodeResponseStart OAuth Device Flow, returns user code
github_poll_login(device_code: String) -> PollResultPoll for token, saves to keyring on success
github_logout() -> ()Delete OAuth token from keyring, fall back to env/CLI
github_auth_status() -> AuthStatusCurrent auth status with login, avatar, source
github_disconnect() -> ()Disconnect GitHub — clear all tokens from keyring and env cache
github_diagnostics() -> ValueDiagnostics: token sources, scopes, API connectivity

Tauri Commands — GitHub Data (github.rs)

CommandSignatureDescription
get_github_status(path: String) -> GitHubStatusPR + CI status for current branch
get_ci_checks(path: String) -> Vec<Value>Detailed CI check list
get_repo_pr_statuses(path: String, include_merged: bool) -> Vec<BranchPrStatus>Batch PR status for all branches
approve_pr(repo_path: String, pr_number: i32) -> StringSubmit approving review via GitHub API
get_all_pr_statuses(path: String) -> Vec<BranchPrStatus>Batch PR status for all branches (includes merged)
get_pr_diff(repo_path: String, pr_number: i32) -> StringGet PR diff content; falls back to a local-clone git diff when GitHub rejects oversized diffs
merge_pr_via_github(repo_path: String, pr_number: i32, merge_method: String) -> StringMerge PR via GitHub API
fetch_ci_failure_logs(repo_path: String, run_id: i64) -> StringFetch failure logs from a GitHub Actions run for CI auto-heal
run_improvement_scan(repo_path: String, focus: ImprovementFocus) -> ImprovementScanResultHeadless-slot one-shot AI scan for refactor/testing/perf proposals; emits proposals-ready
create_issue_from_proposal(repo_path: String, proposal: ImprovementProposal) -> CreatedIssueExplicit issue creation from a proposal; scan never creates issues automatically
check_github_circuit(path: String) -> CircuitStateCheck GitHub API circuit breaker state

Circuit breaker coverage

Every call out to GitHub goes through the account’s circuit breaker: GraphQL via graphql_with_retry, gh api writes via run_gh_write, and direct REST via send_rest_with_breaker (close/reopen issue, merge PR, approve PR, fetch_github_json, PR diff, PR refs). The availability breaker counts transport errors and 5xx responses, not deterministic 4xx caller outcomes such as a missing issue, merge conflict, validation failure, or permission denial. Rate limits use their separate backoff: 429, primary-limit 403 headers, retry-after, or a secondary/abuse-limit message in an otherwise ambiguous 403 body. Non-rate-limit bodies remain available to caller-specific error formatting.

Cached viewer login

state.github_viewer_login backs author:@me in the viewer-PR search and the assignee/creator/mentioned issue filters. It is dropped by github::invalidate_viewer_login on logout, disconnect and a successful device-flow login — without that, switching accounts kept showing the previous account’s PRs and issues for the rest of the session. Named accounts cache their own login in ghe_state and are deliberately untouched by that invalidation.

Data Types

GitHubStatus

#![allow(unused)]
fn main() {
struct GitHubStatus {
    has_remote: bool,
    current_branch: String,
    pr_status: Option<PrStatus>,
    ci_status: Option<CiStatus>,
    ahead: i32,
    behind: i32,
}
}

PrStatus

#![allow(unused)]
fn main() {
struct PrStatus {
    number: i32,
    title: String,
    state: String,    // "OPEN", "CLOSED", "MERGED"
    url: String,
}
}

BranchPrStatus (Batch Endpoint)

Full PR data for a single branch, returned by get_repo_pr_statuses:

#![allow(unused)]
fn main() {
struct BranchPrStatus {
    branch: String,
    number: i32,
    title: String,
    state: String,
    url: String,
    additions: i32,
    deletions: i32,
    checks: CheckSummary,        // passed/failed/pending/total
    author: String,
    commits: i32,
    mergeable: String,           // "MERGEABLE", "CONFLICTING", "UNKNOWN"
    merge_state_status: String,  // "CLEAN", "DIRTY", "BEHIND", etc.
    review_decision: String,     // "APPROVED", "CHANGES_REQUESTED", etc.
    labels: Vec<PrLabel>,        // Labels with pre-computed colors
    is_draft: bool,
    base_ref_name: String,
    created_at: String,
    updated_at: String,
    merge_state_label: Option<StateLabel>,   // Pre-classified display label
    review_state_label: Option<StateLabel>,  // Pre-classified display label
}
}

PrLabel

#![allow(unused)]
fn main() {
struct PrLabel {
    name: String,
    color: String,            // Hex color from GitHub
    text_color: String,       // Computed: black or white based on luminance
    background_color: String, // Computed: hex_to_rgba with alpha
}
}

CheckSummary

#![allow(unused)]
fn main() {
struct CheckSummary {
    passed: u32,
    failed: u32,
    pending: u32,
    total: u32,
}
}

StateLabel

#![allow(unused)]
fn main() {
struct StateLabel {
    label: String,     // Human-readable text (e.g., "Approved", "Behind")
    css_class: String, // CSS class for styling
}
}

Utility Functions

parse_pr_list_json(json_str: &str) -> Vec<BranchPrStatus>

Parses the JSON output from gh pr list --json ... and enriches with computed fields (merge state classification, review state classification, label colors).

classify_merge_state(mergeable, merge_state_status) -> Option<StateLabel>

Maps GitHub merge state to display labels:

mergeablemerge_state_statusLabelCSS Class
MERGEABLECLEANReady to mergemerge-ready
MERGEABLEUNSTABLEChecks failingmerge-unstable
CONFLICTING*Has conflictsmerge-conflict
*BEHINDBehind basemerge-behind
*BLOCKEDBlockedmerge-blocked
*DRAFTDraftmerge-draft

classify_review_state(review_decision) -> Option<StateLabel>

review_decisionLabelCSS Class
APPROVEDApprovedreview-approved
CHANGES_REQUESTEDChanges requestedreview-changes
REVIEW_REQUIREDReview requiredreview-required

hex_to_rgba(hex: &str, alpha: f64) -> String

Converts hex color (e.g., “#ff0000”) to rgba string (e.g., “rgba(255, 0, 0, 0.5)”).

is_light_color(hex: &str) -> bool

Calculates relative luminance using the sRGB formula to determine if a color is light (for choosing black vs white text).

Tauri Commands — Issues

CommandSignatureDescription
poll_issues(repos: Vec<(String, String, String)>, login: String, filter: String) -> Vec<RepoIssues>Fetch issues for multiple repos using GitHub Search API
close_issue(repo_path: String, issue_number: i32) -> StringClose an issue via GitHub GraphQL mutation
reopen_issue(repo_path: String, issue_number: i32) -> StringReopen a closed issue via GitHub GraphQL mutation

GitHubIssue

#![allow(unused)]
fn main() {
struct GitHubIssue {
    number: i32,
    title: String,
    state: String,           // "OPEN", "CLOSED"
    url: String,
    created_at: String,
    updated_at: String,
    author: String,
    labels: Vec<PrLabel>,    // Reuses PrLabel with computed colors
    assignees: Vec<String>,
    milestone: Option<String>,
    comments_count: u32,
}
}

Issue Filter Modes

The filter parameter in poll_issues controls which issues are fetched:

FilterGitHub Search QualifierDescription
assignedassignee:{login}Issues assigned to the authenticated user (default)
createdauthor:{login}Issues created by the authenticated user
mentionedmentions:{login}Issues mentioning the authenticated user
all(no user qualifier)All open issues in the repo
disabled(no query)Issue fetching disabled

Issue Query Construction

build_multi_repo_issues_query constructs a GitHub Search API query per repo:

  • Format: repo:{owner}/{name} is:issue is:open {user_qualifier}
  • Results parsed via parse_issue_node which extracts labels with hex_to_rgba color computation (same opacity constant LABEL_BG_OPACITY = 0.7 as PRs)

GraphQL Batching

get_repo_pr_statuses uses gh pr list with extensive --json fields to fetch all open PRs in a single call. This is efficient: 1 API call returns all branches with PR data.

Polling budget: ~2 calls/min/repo = 1,200/hr for 10 repos, well within GitHub’s 5,000/hr rate limit.

PR Approval & Merge

approve_pr

Submits an approving review on a pull request via gh api. Used by the remote-only PR popover.

PR Diff Fetching

PR diff reads use the GitHub REST diff representation first. If GitHub returns the oversized-diff 406 Not Acceptable response, the backend fetches the PR refs into the local clone and returns a local git diff base...head unified diff instead, so AI Review can still run on PRs that exceed GitHub’s rendered diff file cap.

CI Auto-Heal (fetch_ci_failure_logs)

Lists workflow runs for the branch’s latest head commit, inspects their jobs, and downloads logs for every completed failed job through the GitHub Actions jobs API. Job-level retrieval works while sibling jobs are still running, before the containing workflow has a final failure conclusion. Used by the CI auto-heal hook (useCiHeal) to inject failure context into agent terminals for automatic fix cycles (up to 3 delivered attempts per cycle).

GitHub Actions only. The aggregated PR check summary (which triggers ci_failed) also counts external CI — CircleCI, Codacy, etc. — but this fetcher reads only GitHub Actions logs. When the red checks are all external, it returns a clear error naming them (… failing checks run on external CI (not supported): ci/circleci: lint-blades, on_pr …) instead of the misleading “no jobs found”. The auto-heal hook surfaces that message as a warn toast and does not consume an attempt (attempts increment only after a fix prompt is delivered). Provider is classified from the check’s detail link (is_github_actions_link: GHA links contain /actions/runs/).

Stale PR Filtering

When include_merged is true, get_repo_pr_statuses includes recently merged PRs. Stale merged PRs are filtered: if a branch has been recreated after a PR was merged (detected via branch creation timestamp vs PR merge timestamp), the old merged PR is excluded to prevent ghost badges.