You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
GitHub: link and "Reconnect for GitHub Issues" scope handoff #76
GitHub can be linked for sign-in only or with the Issues scope. GitHub-backed lists need Issues. The web surfaces a "Reconnect for GitHub Issues" action in Connected Accounts to grant the extra scope.
Endpoints
GET /api/auth/github/authorize · GET /api/auth/github/callback · GET /api/auth/github/status (reports whether the server has GitHub OAuth configured and its public client id) · POST /api/auth/{provider}/link.
Per CLAUDE.md, /api/auth/{provider}/status is a red herring for per-user link state — the real state is GET /api/user/identities, which ConnectedAccountsViewModel already uses. Keep that distinction.
OAuth stays external
No WebView2 in this app: linking opens the OS default browser and the user returns and refreshes — the pattern already used in Views/ConnectedAccountsView.xaml.
Acceptance criteria
A "Reconnect for GitHub Issues" action in Connected Accounts, opening the authorize URL in the OS browser.
Link state is read from GET /api/user/identities, not from /api/auth/github/status.
After returning, a refresh reflects the new scope and unlocks the GitHub-backed list flow.
If the scope cannot be detected from /api/user/identities, say so plainly rather than guessing.
Live probe results from the service layer (#72 / PR #142)
⚠️ Correction to the epic's premise: the test account IS GitHub-linked
GET /api/user/identities shows provider github, username InterlinedListMessenger. The 200 [] from /repos and /orgs means that GitHub account owns no repos and joins no orgs — not that nothing is linked. So:
The genuinely-unlinked response shape is still unobserved.
Link state must come from /api/user/identities (GetGitHubLinkStateAsync → GitHubLinkState), never from an empty collection. Inferring "not linked" from [] would show a Connect prompt to an already-connected user.
ClassifyGitHubFailure / GitHubFailureReason exist as a backstop, with NotLinked flagged unverified.
All nine endpoints, live
Endpoint
Status
Shape
GET /repos
200
bare array, 0 items
GET /repos?org=github
200
559 items in one array
GET /repos?org=dotnet
200
278
GET /repos?org=microsoft | google | apache
200
exactly 2000 each
GET /repos?org=Adron (a user)
404
not_found
GET /orgs
200
bare array, 0 items — item shape never observed
GET /issues (no repo, no default)
400
"Repository required. Set default repo in Settings or pass repo parameter (owner/repo)."
GET /issues?repo=o/r&state=all
200
bare array, 13 items
GET /issues?…&state=OPEN
422
validation_failed — state is case-sensitive
GET /repos/{o}/{r}/assignees
200
bare array, capped at 30
GET /repos/{o}/{r}/labels
200
bare array, capped at 30
GET /repos/{o}/{r}/next-issue-number
200
{"nextNumber":14}; 1 for an issue-less repo
POST /issues, the PATCH, and the comments endpoint were not called — they write to real GitHub repositories.
#73 specifically: no client paging needed, but an ?org= is
The epic said "documented as fully paginated; implement paging". Wrong in both directions. The server walks GitHub's pages itself and returns the complete set in one bare array (559 for ?org=github, well past GitHub's 100/page ceiling). There is no envelope, no Link header, and no page/per_page parameter — ?page=2&per_page=5 changed nothing. Only ?org= exists.
But there's an apparent server cap of 2000 (three large orgs each returned exactly 2000; aws returned a natural 552), so the repo picker should show a "first 2000" hint rather than implying completeness.
Also: /repos returns a slim server projection, not GitHub's repo object — exactly {full_name, name, private, owner_login}, uniform across all 2837 items checked.
Conversely, issues / labels / assignees are stuck on GitHub's first 30 with no way to page further. That's a real limitation for #75.
/api/auth/github/authorize requests only scope=user:email read:user. ?link=true is what requests repo read:org — i.e. the Issues scope. A ?scope= of your own is ignored, and an arbitrary redirect_uri is rejected.
Separately, /api/auth/github/status carries manageOrgAccessUrl — the documented fix for missing org repos that a reconnect cannot solve (org-level third-party app approval). #76 should surface that as a distinct action, not just "reconnect".
12 of 13 items from a real repo with state=all were pull requests. GitHubIssue.IsPullRequest handles it — filter or label them, or a GitHub-backed list will silently fill with PRs.
Implemented in #178 — work continues in the PR from here.
The reconnect handoff ships as a self-contained GitHubConnectionPanel hosted with one line in ConnectedAccountsView.xaml; ConnectedAccountsViewModel needed no changes at all, which should keep it clear of #154's LinkedIn Pages work in the same files. ?link=true was re-verified live by reading the 307 Location both ways — it is what upgrades the requested scope from user:email read:user to user:email read:user repo read:org — and "Manage organization access" is surfaced from manageOrgAccessUrl as a distinct action, since re-running OAuth with unchanged scopes cannot grant an org's approval.
Link state comes from GET /api/user/identities via a shared GitHubLinkViewModel, never from an empty collection. On the last criterion: the Issues scope genuinely cannot be detected — identities carries no scope field and /api/auth/github/status reports server config, not user grants — so the panel says so plainly and a "Check GitHub access" action reports exactly what a real read returned, ambiguity included. The OAuth return leg (callback/link) was deliberately not completed; it would re-grant scopes on the shared test account's real GitHub identity.
Activity
Adron commentedon Sep 16, 2026
Live probe results from the service layer (#72 / PR #142)
GET /api/user/identitiesshows providergithub, usernameInterlinedListMessenger. The200 []from/reposand/orgsmeans that GitHub account owns no repos and joins no orgs — not that nothing is linked. So:/api/user/identities(GetGitHubLinkStateAsync→GitHubLinkState), never from an empty collection. Inferring "not linked" from[]would show a Connect prompt to an already-connected user.ClassifyGitHubFailure/GitHubFailureReasonexist as a backstop, withNotLinkedflagged unverified.All nine endpoints, live
GET /reposGET /repos?org=githubGET /repos?org=dotnetGET /repos?org=microsoft|google|apacheGET /repos?org=Adron(a user)not_foundGET /orgsGET /issues(norepo, no default)"Repository required. Set default repo in Settings or pass repo parameter (owner/repo)."GET /issues?repo=o/r&state=allGET /issues?…&state=OPENvalidation_failed— state is case-sensitiveGET /repos/{o}/{r}/assigneesGET /repos/{o}/{r}/labelsGET /repos/{o}/{r}/next-issue-number{"nextNumber":14};1for an issue-less repoPOST /issues, thePATCH, and the comments endpoint were not called — they write to real GitHub repositories.#73 specifically: no client paging needed, but an
?org=isThe epic said "documented as fully paginated; implement paging". Wrong in both directions. The server walks GitHub's pages itself and returns the complete set in one bare array (559 for
?org=github, well past GitHub's 100/page ceiling). There is no envelope, noLinkheader, and nopage/per_pageparameter —?page=2&per_page=5changed nothing. Only?org=exists.But there's an apparent server cap of 2000 (three large orgs each returned exactly 2000;
awsreturned a natural 552), so the repo picker should show a "first 2000" hint rather than implying completeness.Also:
/reposreturns a slim server projection, not GitHub's repo object — exactly{full_name, name, private, owner_login}, uniform across all 2837 items checked.Conversely, issues / labels / assignees are stuck on GitHub's first 30 with no way to page further. That's a real limitation for #75.
#76 specifically:
?link=trueis load-bearing/api/auth/github/authorizerequests onlyscope=user:email read:user.?link=trueis what requestsrepo read:org— i.e. the Issues scope. A?scope=of your own is ignored, and an arbitraryredirect_uriis rejected.Separately,
/api/auth/github/statuscarriesmanageOrgAccessUrl— the documented fix for missing org repos that a reconnect cannot solve (org-level third-party app approval). #76 should surface that as a distinct action, not just "reconnect".#74/#75: pull requests arrive as issues
12 of 13 items from a real repo with
state=allwere pull requests.GitHubIssue.IsPullRequesthandles it — filter or label them, or a GitHub-backed list will silently fill with PRs.Adron commentedon Sep 16, 2026
Implemented in #178 — work continues in the PR from here.
The reconnect handoff ships as a self-contained
GitHubConnectionPanelhosted with one line inConnectedAccountsView.xaml;ConnectedAccountsViewModelneeded no changes at all, which should keep it clear of #154's LinkedIn Pages work in the same files.?link=truewas re-verified live by reading the 307Locationboth ways — it is what upgrades the requested scope fromuser:email read:usertouser:email read:user repo read:org— and "Manage organization access" is surfaced frommanageOrgAccessUrlas a distinct action, since re-running OAuth with unchanged scopes cannot grant an org's approval.Link state comes from
GET /api/user/identitiesvia a sharedGitHubLinkViewModel, never from an empty collection. On the last criterion: the Issues scope genuinely cannot be detected — identities carries no scope field and/api/auth/github/statusreports server config, not user grants — so the panel says so plainly and a "Check GitHub access" action reports exactly what a real read returned, ambiguity included. The OAuth return leg (callback/link) was deliberately not completed; it would re-grant scopes on the shared test account's real GitHub identity.Merge pull request #178 from CompositeCode/issue-76-github-reconnect