7.7 KiB
7.7 KiB
GitHub Module Documentation
Purpose
- This module owns GitHub auth, Octokit access, repo resolution, and Pull Request status resolution for OpenChamber.
- From user perspective, this is the layer that lets the app know which PR belongs to a local branch and keeps that UI feeling current.
Entrypoints and structure
packages/web/server/lib/github/index.js: public server entrypoint.packages/web/server/lib/github/auth.js: auth storage, multi-account support, client id, scope config.packages/web/server/lib/github/device-flow.js: OAuth device flow.packages/web/server/lib/github/octokit.js: Octokit factory for the current auth.packages/web/server/lib/github/repo/index.js: remote URL parsing and directory-to-repo resolution.packages/web/server/lib/github/pr-status.js: PR lookup across remotes, forks, and upstreams.packages/web/server/index.js: API route layer that calls this module.packages/web/src/api/github.ts: web client wrapper for GitHub endpoints.
Public exports
Auth
getGitHubAuth(): current auth entry.getGitHubAuthAccounts(): all configured accounts.setGitHubAuth({ accessToken, scope, tokenType, user, accountId }): save or update account.activateGitHubAuth(accountId): switch active account.clearGitHubAuth(): clear current account.getGitHubClientId(): resolve client id.getGitHubScopes(): resolve scopes.GITHUB_AUTH_FILE: auth file path.
Device flow
startDeviceFlow({ clientId, scope }): request device code.exchangeDeviceCode({ clientId, deviceCode }): poll for access token.
Octokit
getOctokitOrNull(): current Octokit ornull.
Repo
parseGitHubRemoteUrl(raw): parse SSH or HTTPS remote URL into{ owner, repo, url }.resolveGitHubRepoFromDirectory(directory, remoteName): resolve GitHub repo from a local git remote.
Auth storage and config
- Auth storage:
~/.config/openchamber/github-auth.json - Writes are atomic and file mode is
0o600. - Client ID resolution order:
OPENCHAMBER_GITHUB_CLIENT_ID->settings.json-> default. - Scope resolution order:
OPENCHAMBER_GITHUB_SCOPES->settings.json-> default. - Account id resolution order: explicit
accountId-> user login -> user id -> token prefix.
PR integration overview
- The UI asks
github.prStatus(directory, branch, remote?)frompackages/web/src/api/github.ts. - That hits
GET /api/github/pr/statusinpackages/web/server/index.js. - The route calls
resolveGitHubPrStatus(...)inpackages/web/server/lib/github/pr-status.js. - The resolver finds the most likely repo and PR for a local branch.
- The route then enriches that result with checks, mergeability, and permission-related fields.
- The client caches and shares the result between sidebar and Git view.
Consumers of PR data
packages/ui/src/components/session/SessionSidebar.tsxreads all PR entries and maps them todirectory::branch.packages/ui/src/components/session/sidebar/SessionGroupSection.tsxrenders the compact badge, PR number, title, checks summary, and GitHub link.packages/ui/src/components/views/git/PullRequestSection.tsxuses the same shared entry for the full PR workflow.packages/ui/src/components/ui/MemoryDebugPanel.tsxreads request counters for debugging.
How PR resolution works
- It reads local git status and remotes first.
- It ranks remotes in this order: explicit remote, tracking remote,
origin,upstream, then the rest. - It resolves those remotes into GitHub repos.
- It expands each repo through
parentandsourceso PRs in upstream repos can still be found. - It skips PR lookup when the current branch matches that repo's default branch.
- It first searches for PRs by likely source owner plus exact head branch.
- If that fails, it falls back to broader GitHub search for the branch name.
403and404during repo lookups are treated as expected gaps, not hard errors.
Shared client state model
- Client key is effectively
directory::branch. - One entry stores last known status, loading state, error, timestamps, watcher count, identity, and resolved remote.
- Requests are deduplicated by branch signature, not by component instance.
- This keeps sidebar and Git view aligned and avoids duplicated fetches.
Persistence
- PR state is persisted in local storage under
openchamber.github-pr-status. - Persisted fields include status, timestamps, identity, and resolved remote.
- Runtime-only details are not persisted.
- Persisted entries expire after 12 hours.
- On reload, users get last known state first, then background refresh resumes.
Polling and refresh model
- There are two layers: entry-level polling in
useGitHubPrStatusStoreand repo scanning inuseGitHubPrBackgroundTracking. - Entry-level polling decides when a known branch should revalidate PR state.
- Background tracking decides which directories and branches should even be watched.
Entry-level polling rules
- Start watching -> immediate refresh.
- If no PR is found yet -> retry after
2sand5s. - Still no PR -> discovery refresh every
5m. - Open PR with pending checks -> refresh about every
1m. - Open PR with non-pending checks -> refresh about every
5m. - Open PR without a stable checks signal -> refresh about every
2m. - Closed or merged PR -> stop regular polling.
- Hidden tab -> skip polling.
- Non-forced refreshes use a
90sTTL.
Background tracking rules
- Track up to
50likely directories. - Sources are current directory, projects, worktrees, active sessions, and archived sessions.
- Active directory branch TTL is
15s. - Background directory branch TTL is
2m. - Background scan wakes every
15s, but only fetches directories whose TTL expired. - Each scan reads
branch,tracking,ahead, andbehindfrom git status. - If any of those branch signals change, that branch's PR status refreshes immediately.
- After that, one more delayed refresh runs after
5sto catch GitHub eventual consistency.
UI refresh triggers
- App or tab becomes visible.
- Window regains focus.
- Current branch changes.
- Tracking branch changes.
- Ahead or behind changes.
- User selects a different remote in Git view.
- GitHub auth state changes.
Action-based refreshes in Git view
- After
Create PR-> refresh now, then after2sand5s. - After
Merge PR-> refresh now, then after2sand5s. - After
Mark ready for review-> refresh now, then after2sand5s. - After
Update PR-> refresh now, then after2sand5s.
Sidebar behavior
- Sidebar shows only compact PR state.
- Aggregation is by
directory::branch, so multiple sessions on one branch share one signal. - If multiple entries exist, sidebar keeps the strongest visible PR state.
- Visual state is based on PR health, not merge permissions.
Git view behavior
- Git view watches one branch directly.
- It supports create, edit, mark ready, and merge.
- It can probe alternate remotes so fork-heavy setups still find the right PR.
- It uses the same shared store as the sidebar.
Failure handling
- If GitHub is disconnected, API returns
connected: false. - If a repo is private or inaccessible, resolver calls may quietly return no PR.
- Sidebar stays quiet on missing or inaccessible PR state.
- Git view is where explicit PR-level problems should be shown.
Notes for contributors
- Keep the UI calm. Do not add noisy diagnostics to the sidebar.
- Prefer shared state over per-component fetches.
- Prefer event-shaped refreshes over blind frequent polling.
- Prefer correctness for fork and multi-remote setups over assuming
originis enough. - Device flow handles GitHub
authorization_pendingat caller level. - Repo parser supports
git@github.com:,ssh://git@github.com/, andhttps://github.com/.