feat(clients): add GitHub issue browsing - #8046
Conversation
- Add issue listing and detail panels across server and web - Resolve the GitHub CLI reliably on Windows
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 268826c2a4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| readonly q?: string; | ||
| } | ||
|
|
||
| export const Route = createFileRoute("/_chat/issues")({ |
There was a problem hiding this comment.
Add the GitHub issue browser to mobile
This new route implements issue browsing only in the web client, which desktop inherits; a repo-wide search under apps/mobile finds no GitHub-issue RPC, navigation, list, detail, or handoff implementation. Consequently, iOS and Android users cannot access this newly advertised client feature at all, so add the corresponding mobile surface or explicitly scope the capability and documentation to web/desktop.
AGENTS.md reference: AGENTS.md:L67-L70
Useful? React with 👍 / 👎.
| const primaryEnvironment = usePrimaryEnvironment(); | ||
| const environmentId = primaryEnvironment?.environmentId ?? null; | ||
| const capabilityKnown = primaryEnvironment !== null && primaryEnvironment.serverConfig !== null; | ||
| const supported = | ||
| primaryEnvironment?.serverConfig?.environment.capabilities.githubIssues === true; |
There was a problem hiding this comment.
Read issues from every capable environment
In a multi-environment workspace, this page binds exclusively to the primary environment. This conflicts with SidebarChrome.tsx:169-171, which shows the GitHub Issues entry when any environment supports it: if the primary server is older but a secondary server is capable, the visible destination incorrectly reports the feature unavailable, and even when the primary is capable all GitHub projects and issues on secondary environments are omitted. Aggregate capable environments or carry the selected environment through the list/detail references.
AGENTS.md reference: AGENTS.md:L74-L74
Useful? React with 👍 / 👎.
| const unavailable = batches.find( | ||
| (batch) => "error" in batch && batch.error._tag === "GitHubIssueUnavailableError", | ||
| ); | ||
| if (unavailable && "error" in unavailable) return yield* unavailable.error; |
There was a problem hiding this comment.
Preserve successful repositories when one host is unauthenticated
When a workspace contains an authenticated GitHub repository alongside a GitHub Enterprise host for which gh is not authenticated, the latter batch becomes GitHubIssueUnavailableError and this branch fails the entire list RPC, discarding the successful repository's issues. Authentication is host-specific, unlike a genuinely missing CLI, and the result already supports per-project errors, so only fail globally for CLI absence or when no repository can be read.
Useful? React with 👍 / 👎.
| void navigate({ | ||
| search: (current) => { |
There was a problem hiding this comment.
Replace history entries while editing issue filters
When a user types a multi-character issue search, every keystroke calls this navigation without replace: true, so each intermediate query is pushed into browser history. Pressing Back then walks through the search one character at a time and repeatedly reruns debounced issue queries instead of leaving the page; use replacement navigation for these transient filter updates, as the pull-request route does.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Effect service conventions review of the new GitHub issues feature. Two findings, both in newly added code; the rest of the service/layer/import structure (Context.Service tag with inline interface, make, layer, namespace imports, Foo["Service"] typing, Layer.mock test seams) matches the conventions.
Posted via Macroscope — Effect Service Conventions
There was a problem hiding this comment.
Reviewed the new GitHub Issues UI for consistency with the shared component system and Tailwind ownership. Four findings, all in the new web surfaces: two native filter controls that bypass the shared control primitives, a hand-rebuilt input group whose padding lands on the wrong element, a duplicated empty state, and a state tone in the right-panel tab that cannot be correct for closed issues.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
UI consistency review
Four consistency findings, all in the new GitHub Issues surface. The most important one is the page header: it relies on a workspace-topbar class that does not exist anywhere in the repo, so the top bar loses its flex layout and titlebar geometry entirely.
apps/web/src/routes/_chat.issues.tsx— header applies an undefinedworkspace-topbarclass instead ofWorkspacePageHeaderapps/web/src/routes/_chat.issues.tsx— two raw<select>filters reconstruct the sharedSelectprimitiveapps/web/src/routes/_chat.issues.tsx— search field hand-rollsInputGroup's leading addon, andpl-8lands onInput's wrapper rather than the inputapps/web/src/routes/_chat.issues.tsx— empty state duplicated across the route and the detail panel, with the action outsideEmptyContent
Suggested gates for the fixes: typecheck plus a production build and a quick real-app check of the header at the collapsed-sidebar and Electron/window-controls-overlay states.
Posted via Macroscope — UI Consistency
ApprovabilityVerdict: Not approved Macroscope's review found this PR not approvable — This is a substantial new GitHub issue workflow spanning contracts, server RPC/CLI execution, web navigation, persistence, and agent handoff, with additional shared process and authorization changes. Missing mobile and multi-environment coverage plus unresolved issue-list and UI handling concerns require human review. Not approved because:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
- Share the GitHub issue empty state across list and detail views - Use shared workspace controls and primary-environment capability gating - Distinguish missing and unauthenticated GitHub CLI errors
There was a problem hiding this comment.
One responsive-geometry finding on the new issue detail header; everything else (shared workspace topbar, InputGroup, Select, the single GitHubIssueEmptyState, the neutral issue tab glyph) now matches the shared component system.
Posted via Macroscope — UI Consistency
An unauthenticated GitHub Enterprise remote failed the whole issue list, discarding the repositories that had answered; only a missing `gh` ends the request now, and a locked-out host becomes a per-project error naming the hostname to sign in to. On the web side the detail header wrapped its actions into the title in the narrow right panel, the project filter read an empty sentinel, filter edits pushed a history entry per keystroke, and the "no projects" gate waited on every environment while the page reads only the primary. Model: Claude Opus 5 (1M context) via Claude Code Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| </div> | ||
| </div> | ||
|
|
||
| {selectedRef ? ( |
There was a problem hiding this comment.
🟡 Medium routes/_chat.issues.tsx:345
On narrow screens, a selected issue still opens the full-screen detail overlay when GitHub issues are unsupported, so the overlay displays “Select an issue” and hides the actual “GitHub issues unavailable” state until Back is pressed. The overlay is gated only by selectedRef, while detailQuery is disabled when supported is false; gate it with supported as well.
- {selectedRef ? (
+ {selectedRef && supported ? (🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/routes/_chat.issues.tsx around line 345:
On narrow screens, a selected issue still opens the full-screen detail overlay when GitHub issues are unsupported, so the overlay displays “Select an issue” and hides the actual “GitHub issues unavailable” state until `Back` is pressed. The overlay is gated only by `selectedRef`, while `detailQuery` is disabled when `supported` is false; gate it with `supported` as well.
| updateFilters({ | ||
| projectId: | ||
| value && value !== ALL_PROJECTS_VALUE ? (value as ProjectId) : undefined, | ||
| }) |
There was a problem hiding this comment.
Auth fix hint hidden
Medium Severity
Partial list success now keeps issues from signed-in hosts and puts host-specific gh auth login --hostname text on each failed project, but the issues page only renders a count banner when entries is non-empty. That remediation string never appears in the case this change targets, so a locked-out Enterprise remote stays unexplained beside a working list.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit d196efe. Configure here.
There was a problem hiding this comment.
Three UI consistency findings in the new GitHub issues surfaces. Everything raised in earlier runs (shared topbar, InputGroup/Select primitives, single owned empty state, neutral issue tab glyph) is addressed.
Posted via Macroscope — UI Consistency
The unavailable-projects banner hard-coded bg-warning/8, so it missed the dark bump and the runtime theme bridge that --warning-surface carries. The row hover tone applied to selected rows too, washing the open issue's selection back out on hover. And the right panel borrowed PullRequestDetailGhost while the capability was unknown, announcing "Loading pull request" over PR-shaped chrome that an issue layout then replaced; the issue module now owns a ghost in its own geometry, used for both of this surface's pending states. Model: Claude Opus 5 (1M context) via Claude Code Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Two layout findings on the new GitHub issue surfaces. The first is a real behavioral regression in the right panel; the second is an inert flex class in the issues route.
Posted via Macroscope — UI Consistency
| description="Add a project backed by a GitHub repository and its issues will appear here." | ||
| /> | ||
| ) : listQuery.isPending && listQuery.data === null ? ( | ||
| <div className="flex flex-1 items-center justify-center gap-2 text-muted-foreground text-sm"> |
There was a problem hiding this comment.
flex-1 is inert here: body is rendered into <div className="min-h-0 flex-1 overflow-y-auto">, a block container, so this row cannot grow or centre vertically. With no vertical padding either, "Loading issues..." renders as a bare ~20px line flush under the filter bar, unlike the sibling connecting state (py-12) and the empty states (Empty className="min-h-72") in the same slot.
| <div className="flex flex-1 items-center justify-center gap-2 text-muted-foreground text-sm"> | |
| <div className="flex items-center justify-center gap-2 py-12 text-muted-foreground text-sm"> |
Posted via Macroscope — UI Consistency
| return ( | ||
| <GitHubIssueDetailContent | ||
| environmentId={environmentId} | ||
| detail={query.data} | ||
| error={query.error} | ||
| loading={query.isPending} | ||
| onRetry={query.refresh} | ||
| /> | ||
| ); |
There was a problem hiding this comment.
This panel never establishes a scroll container, but the right panel does not scroll for it. RightPanelTabs renders surfaces into <div className="flex min-h-0 flex-1 flex-col" data-right-panel-surface-content> and PreviewPanelShell is flex h-full min-h-0 flex-col — neither sets overflow, so every other surface owns its own scroller (PullRequestDetailPanel: min-h-0 flex-1 overflow-hidden + inner scroller; AgentsPanel: ScrollArea className="min-h-0 flex-1"; FilePreviewPanel likewise). GitHubIssueDetailContent returns a plain <article> sized by its content, so an issue with a long body or discussion grows past the panel and is clipped by the workspace's overflow-hidden with no way to reach it.
The route call sites already wrap the content in their own overflow-y-auto sections, so the smallest fix is to give this right-panel-only wrapper the scroller:
| return ( | |
| <GitHubIssueDetailContent | |
| environmentId={environmentId} | |
| detail={query.data} | |
| error={query.error} | |
| loading={query.isPending} | |
| onRetry={query.refresh} | |
| /> | |
| ); | |
| return ( | |
| <div className="min-h-0 flex-1 overflow-y-auto"> | |
| <GitHubIssueDetailContent | |
| environmentId={environmentId} | |
| detail={query.data} | |
| error={query.error} | |
| loading={query.isPending} | |
| onRetry={query.refresh} | |
| /> | |
| </div> | |
| ); |
Posted via Macroscope — UI Consistency
| } | ||
| const sortedEntries = entries.toSorted((left, right) => | ||
| right.updatedAt.localeCompare(left.updatedAt), | ||
| ); |
There was a problem hiding this comment.
Issue list sorts by string time
Medium Severity
GitHubIssueService.list orders merged issues with updatedAt.localeCompare, so cross-repo ranking follows string order rather than instant order. Mixed timezone offsets can invert which issues appear in the newest window and which get truncated.
Triggered by learned rule: Use parseTimestampDate for ISO string parsing, return null for invalid
Reviewed by Cursor Bugbot for commit b2a3a06. Configure here.
There was a problem hiding this comment.
Two consistency findings on the new GitHub issues UI. Earlier findings from previous runs on this PR (shared controls, bg-warning-surface, exclusive row hover, wrapping detail header, neutral tab glyph, issue-shaped ghost) all look addressed.
Still open from a previous run and not repeated inline: GitHubIssueDetailContent/GitHubIssueDetailGhost establish no scroll container, and the right panel's surface slot (flex min-h-0 flex-1 flex-col in RightPanelTabs) does not scroll for them, so a long issue body is unreachable in the thread panel; and the Loading issues... row in _chat.issues.tsx uses flex-1 inside a block scroll container with no vertical padding, so it renders as a bare line unlike the sibling states in the same slot.
Posted via Macroscope — UI Consistency
| <div className="fixed inset-0 z-50 overflow-y-auto bg-background md:hidden"> | ||
| <div className="sticky top-0 z-10 flex h-12 items-center border-b border-border bg-background/95 px-3 backdrop-blur"> |
There was a problem hiding this comment.
This mobile detail overlay is position: fixed, so it resolves against the viewport and escapes #root's padding-top: max(env(safe-area-inset-top), 0px) (index.css). The app opts into viewport-fit=cover, so on a notched device the sticky "Back to issues" bar renders under the status bar — every other edge-to-edge surface compensates (the mobile sidebar sheet uses pb-safe pt-safe; dialogs, sheets and the command palette inset their content instead of sitting flush at top: 0).
Suggest padding the bar itself rather than the scroller, so the inset survives while the bar is stuck, with min-h-12 so the padding does not eat its content box. Adding pb-safe to the overlay would also keep the last comment clear of the home indicator.
| <div className="fixed inset-0 z-50 overflow-y-auto bg-background md:hidden"> | |
| <div className="sticky top-0 z-10 flex h-12 items-center border-b border-border bg-background/95 px-3 backdrop-blur"> | |
| <div className="sticky top-0 z-10 flex min-h-12 items-center border-b border-border bg-background/95 px-3 pt-safe backdrop-blur"> |
Posted via Macroscope — UI Consistency
| <GitHubIssueEmptyState | ||
| title="Could not load this issue" | ||
| description={error} | ||
| action={<Button onClick={onRetry}>Try again</Button>} |
There was a problem hiding this comment.
This retry is a default-size, solid primary button, which is a heavier control than every other empty-state action in the app: PullRequestsUnavailableState, PullRequestListEmptyState and SourceControlSettings all use <Button size="sm" variant="outline"> with a RefreshCwIcon className="size-3.5". Since this state sits in the same slots as the pull-request one, the difference reads as a different kind of action.
Suggest size="sm" variant="outline" plus the refresh icon here and at the two matching call sites in routes/_chat.issues.tsx (the list error and the per-project error states), which already import RefreshCwIcon.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 3 total unresolved issues (including 2 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 16afd1b. Configure here.
| "--json", | ||
| ISSUE_LIST_FIELDS, | ||
| ...(input.query === undefined ? [] : ["--search", input.query]), | ||
| ], |
There was a problem hiding this comment.
Issue list misses recently updated issues
High Severity
gh issue list is called with a per-repo --limit but no sort:updated-desc search qualifier. GitHub’s default list/search order is not last-updated, so recently updated older issues can fall outside the fetched window before the later updatedAt merge. The UI still presents the result as the newest issues.
Reviewed by Cursor Bugbot for commit 16afd1b. Configure here.
There was a problem hiding this comment.
One remaining consistency finding on the GitHub issues UI. Earlier findings from previous runs on this PR (mobile detail overlay safe-area inset, empty-state retry button weight, detail panel scroll ownership, list loading-state padding) are still open in the current head and are not repeated here.
Posted via Macroscope — UI Consistency
| const StateIcon = issue.state === "open" ? CircleDotIcon : CircleSlash2Icon; | ||
| return ( | ||
| <button | ||
| type="button" | ||
| aria-current={selected ? "true" : undefined} | ||
| className={cn( | ||
| "grid w-full grid-cols-[auto_minmax(0,1fr)_auto] gap-3 px-4 py-3 text-left transition-colors [contain-intrinsic-block-size:72px] [content-visibility:auto] focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", | ||
| // Exclusive, or hovering the open issue would wash its own selection back out. | ||
| selected ? "bg-accent" : "hover:bg-accent/60", | ||
| )} | ||
| onClick={() => onSelect(issue)} | ||
| > | ||
| <StateIcon | ||
| className={cn( | ||
| "mt-0.5 size-4", | ||
| issue.state === "open" ? "text-success-foreground" : "text-muted-foreground", | ||
| )} | ||
| /> |
There was a problem hiding this comment.
The state glyph mapping is now duplicated: this row re-derives CircleDotIcon/CircleSlash2Icon and the open/closed tone inline, while the detail panel routes the same decision through the exported GitHubIssueStateIcon (added for exactly this reason — the panel previously drew CircleDotIcon for closed issues too). The pull request feature keeps this in one owner (PullRequestStateGlyph) shared by row and detail. Worth reusing the helper here so the two surfaces cannot drift again — and, since state is carried by icon shape/colour alone in a row whose text never says "open"/"closed", giving the helper an accessible name the way PullRequestStateGlyph does (role="img" + label) would close the same gap in all three call sites at once.
- <StateIcon
+ <GitHubIssueStateIcon
+ state={issue.state}
className={cn(
"mt-0.5 size-4",
issue.state === "open" ? "text-success-foreground" : "text-muted-foreground",
)}
/>The local const StateIcon = ... line and the CircleDotIcon/CircleSlash2Icon imports can then go, and GitHubIssueStateIcon joins the existing import from ../components/githubIssue/GitHubIssueDetailPanel.
Posted via Macroscope — UI Consistency
There was a problem hiding this comment.
One finding on the new GitHubIssueService: the per-project read recovers its entire error channel and then re-raises one tag by string comparison, where Effect.catchTags on the survivable tags would express the same policy.
Posted via Macroscope — Effect Service Conventions
| Effect.match({ | ||
| onFailure: (error) => ({ project, error }), | ||
| onSuccess: (value) => value, | ||
| }), | ||
| ), | ||
| { concurrency: PROJECT_CONCURRENCY }, | ||
| ); | ||
|
|
||
| // A missing `gh` is the one failure no repository can survive, so it ends the request. | ||
| const cliMissing = batches.find( | ||
| (batch) => "error" in batch && batch.error._tag === "GitHubIssueCliMissingError", | ||
| ); | ||
| if (cliMissing && "error" in cliMissing) return yield* cliMissing.error; |
There was a problem hiding this comment.
Effect.match recovers the whole error channel per project, and the one failure that must not be recovered (GitHubIssueCliMissingError) is then re-raised below by inspecting _tag on the collected value. These are statically known tagged failures, so consider recovering only the survivable ones with Effect.catchTags({ GitHubIssueCliUnauthenticatedError: (error) => Effect.succeed({ project, error }), GitHubIssueOperationError: (error) => Effect.succeed({ project, error }) }). A missing gh then short-circuits Effect.forEach on its own, the batches.find(...) / "error" in cliMissing re-raise disappears, and the narrowed batch union no longer needs a tag string test to stay correct.
Posted via Macroscope — Effect Service Conventions


Summary
Testing
Before and after
Before
After
Generated with GPT-5.6 Sol in T3 Code via the Codex harness.
Note
Medium Risk
New read-only RPC and
ghspawning across workspace repos, plus Windows executable resolution and default stdin handling for all child processes. Auth is scoped to orchestration read, but process and capability negotiation changes can affect existing GitHub CLI flows.Overview
Lets users browse GitHub issues in the app and hand one to an agent, gated by a new optional
githubIssuesserver capability.The server lists and views issues through
ghfor GitHub-backed workspace projects (githubIssues.list/githubIssues.detail, read scope). Lists merge across repos, sort by recency, keep per-host auth failures, and fail the whole request ifghis missing. Detail includes body and comments.The client adds a primary-environment
/issuespage (search, state, project filters), agithub-issueright-panel tab, sidebar entry, and in-app opening of issue URLs that match a workspace project. Fix in a thread opens the project and seeds the composer.Also verifies the real GitHub CLI on Windows (skip npm
ghshims), classifiesgh.exe/ absolute paths as GitHub CLI, and ignores unused child stdin to avoid WindowsECONNRESETcrashes. Pull-request refresh no longer double-refreshes the same cached query.Reviewed by Cursor Bugbot for commit 61becff. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add GitHub issue browsing with list/detail APIs, web UI, and Windows CLI fix
GitHubIssueServiceserver service that lists and fetches issue details across workspace GitHub projects via theghCLI, with typed error handling for missing/unauthenticated CLI statesgithubIssue.ts), WebSocket RPC methods (githubIssues.list,githubIssues.detail), capability flaggithubIssuesinExecutionEnvironmentCapabilities, and client-runtime atoms for environment-scoped queries/_chat/issuesroute listing up to 50 issues with debounced search, aGitHubIssueDetailPanelright-panel surface, sidebar navigation entry, and in-app interception of GitHub issue linksresolveGitHubCliExecutableto probewhere.execandidates on Windows and classifygh.exe/ absolute-path invocations as GitHub CLI inclassifyNonZeroExitRIGHT_PANEL_STORAGE_VERSIONto 12 with a migrator that normalizes legacy singletongithub-issuesurfaces to reference-keyed idsRIGHT_PANEL_STORAGE_VERSIONbump to 12 in rightPanelStore.ts drops persistedgithub-issueentries lacking validprojectId/repository/number;processRunner.runProcessCorenow setsstdin: "ignore"by default, which may affect downstream spawners relying on inherited stdinMacroscope summarized 61becff.