* fix: avoid incorrect PR status on default branch Hide sidebar PR status when current branch is the repo default branch. Tighten PR fallback matching to avoid picking unrelated PRs with the same branch name. * fix: make sidebar PR status more reliable Resolve PRs more reliably across remotes and repo networks. Persist sidebar PR status across tab reloads while keeping updates fresh. Reduce noisy GitHub errors and correct blocked styling in PR view. * fix: correct blocked PR styling in sidebar Stop treating missing merge permissions as a blocked PR state in the sidebar. Align sidebar PR status colors and labels with the main PR view. * refactor: make GitHub PR status updates feel more current Refresh PR state on focus, visibility, and repo changes Add follow-up PR refreshes after create, merge, ready, and update actions Document GitHub PR resolution, consumers, polling, and triggers
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/.