Skip to content

Fix runtime GitHub work item details#6473

Merged
OrcaWin merged 6 commits into
stablyai:mainfrom
brennanb2025:brennanb2025/bug-error-invoking-remote-method-gh-workitemdeta
Jun 29, 2026
Merged

Fix runtime GitHub work item details#6473
OrcaWin merged 6 commits into
stablyai:mainfrom
brennanb2025:brennanb2025/bug-error-invoking-remote-method-gh-workitemdeta

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #6429: on Windows in Orca 1.4.102.0, opening a GitHub work item from a runtime/remote source could fail with Error invoking remote method 'gh:workItemDetails': Error: Access denied: unknown repository path.

Runtime/source-scoped GitHub work item details now route through runtime RPC instead of failing local registered-repo path checks. Follow-up PR/issue actions also preserve the source context across comments, reviewers, checks, file viewed state, body/state edits, merge/auto-merge, and project-origin item flows.

Original Report

Why this PR is large

The original failure was in details loading, but the same local-only repoPath assumption existed across the neighboring GitHub work item surfaces. GitHubItemDialog and PullRequestPage currently duplicate much of that details/action logic, so runtime-safe routing had to be applied in both places.

To keep this as a bug fix rather than a broad refactor, this PR does not merge those UI surfaces. It only extracts the repeated runtime/source decision helpers into github-source-runtime-context.ts so future edits are less likely to fix one surface and miss the other.

Design Approach

  • Added source-aware lookup for GitHub work item details.
  • Added narrow helpers for GitHub runtime host detection, runtime repo id selection, and repo-context availability.
  • Source-scoped details caches now include the task source scope and receive cross-window invalidations.
  • Runtime-owned mutations use the runtime repo identity (sourceContext.repoId) and sibling-only cache invalidation when the current renderer already applies optimistic state.
  • Local IPC mutation broadcasts include repo ids so renderer cache keys can be matched reliably.

Risk Notes

  • Medium risk because this touches many GitHub work item controls in two duplicated renderer surfaces.
  • Main risk is cache invalidation/source-context drift, which is now covered by focused boundary tests plus the helper extraction.
  • Local GitHub flows still use the existing Electron IPC path; runtime flows bypass local repo-path authorization.
  • This does not touch worktree cleanup, destructive deletion, git subprocess scans, WSL/SSH git providers, or cleanup modal progress.

Brennan Pipeline

  • Design doc: .tmp/issue-6429-runtime-gh-work-item-details-design.md (scratch/ignored, not committed).
  • Design review: completed before implementation.
  • Implementation: completed with focused source-routing and cache invalidation changes; no intentional deviations from the design.
  • Completeness verification: completed before code review.
  • Code review loop: multiple two-reviewer rounds; final round returned clean from both reviewers.
  • brennan-test-changes: verdict was land, with the live runtime/SSH mutation gap explicitly accepted.

Validation

  • Focused Vitest: 8 files, 114 tests passed
  • pnpm run typecheck:web
  • pnpm run typecheck:node
  • Changed-file oxlint
  • git diff --check
  • Git crash/perf audit: no git/worktree cleanup crash surface touched

Electron Evidence

  • Original repro screenshot: C:\Users\neil\AppData\Local\Temp\orca-6429-electron-api-repro.png
  • After-fix screenshot: C:\Users\neil\AppData\Local\Temp\orca-6429-electron-after-fix.png
  • Brennan validation screenshot: C:\Users\neil\AppData\Local\Temp\orca-6429-brennan-validation.png

Validated in this worktree via Electron/CDP that source-aware lookup with runtime path /home/aixz/data/hxf/bigmodel/ai_code/test bypasses local repo path matching and reaches runtime dispatch (Unknown environment: repro-env), while the direct old IPC path still reproduces Access denied: unknown repository path.

Accepted Gaps

  • Did not mutate a real disposable GitHub issue/PR through a live runtime environment.
  • Did not run a live brennan-test-ssh pass.
  • Boundary tests are mostly source-routing assertions and do not replace a full end-to-end runtime GitHub integration test.

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds sourceContext-aware GitHub IPC, lookup, cache, and mutation routing across main and renderer code. It threads sourceContext from wrapper components into pull request and GitHub dialog flows, and adds tests for the broadcast, lookup, cache, and runtime-routing paths.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description has a summary and validation, but it does not follow the required template and omits Screenshots, AI Review Report, Security Audit, and Notes. Rewrite the PR description to match the template and add the missing sections, including Screenshots, AI Review Report, Security Audit, and Notes.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main change to runtime GitHub work item details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/renderer/src/components/GitHubItemDialog.tsx (2)

4122-4130: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Auto-merge menu item still gated on !repoPath, blocking runtime environments.

handleAutoMerge now allows runtime targets via canMergeWithRepoContext (Line 4011), and the merge-method items below use disabled={mergeDisabled} (which permits mergeTarget.kind === 'environment'). But this auto-merge item keeps disabled={!repoPath || mergePending}, so for a runtime-backed PR with no local repoPath the action is unreachable even though the handler supports it — inconsistent with the merge-method items.

🐛 Proposed fix
             {mergePresentation.autoMergeAction && (
               <DropdownMenuItem
-                disabled={!repoPath || mergePending}
+                disabled={!canMergeWithRepoContext || mergePending}
                 onSelect={() => void handleAutoMerge()}
               >

4112-4119: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Merge tooltip is misleading for runtime sources. When !repoPath but mergeTarget.kind === 'environment', merge is now permitted, yet the tooltip still reads "Merge requires a registered local repo." Consider gating the tooltip on !canMergeWithRepoContext instead of !repoPath.

src/renderer/src/components/PullRequestPage.tsx (3)

6442-6455: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Allow runtime-source details lookup without a local repoPath.

lookupGitHubWorkItemDetailsForSource can resolve runtime GitHub details from sourceContext, but these guards still require repoPath. A runtime-only source item will never load body/comments/checks/files. Treat sourceContext with a runtime host as valid lookup context and pass repoPath ?? '' only for the local IPC fallback.

Also applies to: 6621-6655


3356-3387: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Don’t gate comments and replies on repoPath when runtime routing is available.

addIssueCommentForRepo and addPRReviewCommentReplyForRepo already route via sourceContext, but handleReply errors on missing repoPath and the composer only renders when repoPath exists. Remote GitHub source users still can’t comment/reply.

Also applies to: 3796-3806, 6237-6332


4551-4560: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Let checks actions use the runtime path without repoPath.

Refresh, rerun, inline details, and their buttons still require repoPath, even though the implementation below each guard has a runtime RPC branch. Use parsedSourceHost?.kind === 'runtime' as valid context for these checks flows.

Also applies to: 4611-4613, 4728-4730, 4812-4813, 4863-4864


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c17f74b2-aba7-477a-ace0-a368063b8d09

📥 Commits

Reviewing files that changed from the base of the PR and between 360ce67 and 65d5ec7.

📒 Files selected for processing (16)
  • src/main/ipc/github.test.ts
  • src/main/ipc/github.ts
  • src/preload/api-types.ts
  • src/preload/index.ts
  • src/renderer/src/components/GitHubItemDialog.tsx
  • src/renderer/src/components/PullRequestPage.tsx
  • src/renderer/src/components/TaskPage.tsx
  • src/renderer/src/components/github-item-dialog-source-boundary.test.ts
  • src/renderer/src/components/github-project/ProjectViewWrapper.tsx
  • src/renderer/src/components/github-project/project-view-wrapper-source-context-boundary.test.ts
  • src/renderer/src/components/pull-request-page-host-boundary.test.ts
  • src/renderer/src/components/task-page-source-switch-boundary.test.ts
  • src/renderer/src/lib/github-work-item-details-cache-events.ts
  • src/renderer/src/lib/github-work-item-source-lookup.test.ts
  • src/renderer/src/lib/github-work-item-source-lookup.ts
  • src/renderer/src/web/web-preload-api.ts

Comment thread src/main/ipc/github.ts
Comment thread src/renderer/src/components/PullRequestPage.tsx
Comment thread src/renderer/src/components/PullRequestPage.tsx
Comment thread src/renderer/src/components/PullRequestPage.tsx Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/renderer/src/components/GitHubItemDialog.tsx (2)

7039-7045: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Don’t drop unrelated in-flight detail loads on global cache invalidations.

workItemDetailsCacheGeneration is global, so a mutation for another work item can advance it while this fetch is in flight. These branches then return without clearing pending or writing the result, leaving this cache entry stuck loading. Only suppress the write when this specific cache entry was removed/replaced, or track generation per cache key.

Proposed fix
-        const invalidatedMidFlight = workItemDetailsCacheGeneration !== launchedAtGeneration
-        const prev = workItemDetailsCache.get(detailsCacheKey)
-        if (invalidatedMidFlight) {
+        const invalidatedMidFlight = workItemDetailsCacheGeneration !== launchedAtGeneration
+        const prev = workItemDetailsCache.get(detailsCacheKey)
+        if (invalidatedMidFlight && prev?.pending !== inflight) {
           // Why: entry was deliberately dropped; do not recreate it. If the
           // entry still exists (later open repopulated it) leave it alone too.
           return
         }
         const message = err instanceof Error ? err.message : 'Failed to load details'
         const invalidatedMidFlight = workItemDetailsCacheGeneration !== launchedAtGeneration
-        if (invalidatedMidFlight) {
-          return
-        }
         const prev = workItemDetailsCache.get(detailsCacheKey)
+        if (invalidatedMidFlight && prev?.pending !== inflight) {
+          return
+        }

Also applies to: 7069-7072


7602-7627: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Invalidate the current runtime details cache after edit mutations.

These sections now render when runtime context exists without repoPath, but each onMutated invalidation still no-ops under if (repoPath). Runtime mutation helpers emit non-local notifications, so the current window can keep stale source-scoped details after edits. Use detailsCacheKey, or invalidate with repoPath: repoPath ?? '' plus the source-scoped repo context.

Also applies to: 7659-7668, 7691-7713, 7780-7789


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 1c3233d1-2ddd-4690-a9cf-824eaec412b5

📥 Commits

Reviewing files that changed from the base of the PR and between df5be9b and 1cc7d30.

📒 Files selected for processing (6)
  • src/main/ipc/github.test.ts
  • src/main/ipc/github.ts
  • src/renderer/src/components/GitHubItemDialog.tsx
  • src/renderer/src/components/PullRequestPage.tsx
  • src/renderer/src/components/github-item-dialog-source-boundary.test.ts
  • src/renderer/src/components/pull-request-page-host-boundary.test.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/ipc/github.ts
  • src/renderer/src/components/github-item-dialog-source-boundary.test.ts
  • src/renderer/src/components/pull-request-page-host-boundary.test.ts
  • src/renderer/src/components/PullRequestPage.tsx

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Validation (reviewer agent) — issue #6429

Root cause (confirmed on origin/main): the GitHub work-item drawer fetched details via the local Electron IPC gh:workItemDetails for every source, including runtime/remote ones. That handler (src/main/ipc/github.ts) calls assertRegisteredRepo, which rejects any repoPath/repoId not registered locally with Error: Access denied: unknown repository path (the exact error in #6429). A runtime-sourced item's repo path is remote and never resolve()-matches a locally-stored repo path, so the IPC always threw.

Fix confirmed: the renderer now routes runtime-sourced lookups through callRuntimeRpc('github.workItemDetails', …) via the new lookupGitHubWorkItemDetailsForSource, instead of the local IPC. Local and SSH repos keep the local IPC path. parseExecutionHostId(sourceContext.hostId) is only reached when provider === 'github', so GitLab/Linear/Jira and SSH are unaffected.

Security: the only main-process gate relaxation is the new gh:notifyWorkItemMutated handler, which skips assertRegisteredRepo when repoId is supplied. This is safe: it is broadcast-only, uses the registered repo.path/repo.id (the attacker-influenced args.repoPath is discarded), returns false for unknown repo ids, and performs no FS/git/network access on the supplied path. No path-traversal or unauthorized-repo access is introduced. Renderer callers always pass repoId and .catch() the result, so it is best-effort cross-window cache invalidation.

Added a regression test in github-work-item-source-lookup.test.ts asserting a runtime source never reaches the access-denied local IPC.

Verify (in worktree, branch brennanb2025/bug-error-invoking-remote-method-gh-workitemdeta)

Tests — affected files:

$ npx vitest run --config config/vitest.config.ts \
    src/renderer/src/lib/github-work-item-source-lookup.test.ts \
    src/main/ipc/github.test.ts \
    src/renderer/src/components/github-item-dialog-source-boundary.test.ts \
    src/renderer/src/components/pull-request-page-host-boundary.test.ts \
    src/renderer/src/components/github-project/project-view-wrapper-source-context-boundary.test.ts \
    src/renderer/src/components/task-page-source-switch-boundary.test.ts \
    src/renderer/src/web/web-preload-api.test.ts

 Test Files  7 passed (7)
      Tests  110 passed (110)

Lint (changed files):

$ npx oxlint <17 changed files>
oxlint exit=0   (no warnings, no errors)

Typecheck:

$ npm run typecheck
tsgo --noEmit -p config/tsconfig.node.json && ...cli.json && ...web.json
typecheck exit=0

Verdict: fix resolves the reported access-denied error, no security hole, lint/typecheck/tests clean. DO NOT MERGE — author review pending.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

🧒 ELI5 — for your merge decision

Opening a GitHub work-item (PR/issue) drawer for a repo running in a remote/runtime environment failed with "Access denied: unknown repository path", because Orca fetched details through a local channel that only knows locally-registered repos. This PR routes runtime-sourced lookups through the runtime connection instead, while local/SSH repos keep the local path.

What you'll notice: GitHub work-item details load for runtime/remote repos instead of throwing an access-denied error. Local and SSH repos are unaffected.

Behavior change Behavior change, MEDIUM merge risk (large ~4400-line diff bundling broad source-context plumbing). Cross-cutting: an item that resolves to null details now shows an 'unable to load' state instead of appearing loaded-but-empty. Adds a small fire-and-forget cache-invalidation IPC after runtime mutations. Author noted the runtime mutation path wasn't exercised end-to-end against a live GitHub item.

Merge risk: medium

Auto-generated summary to aid review. Reproduction + lint/typecheck/test evidence is in the PR body / checks. This PR was produced and independently reviewed by an automated bug-bash; please verify before merging.

@OrcaWin OrcaWin merged commit 5fd104a into stablyai:main Jun 29, 2026
1 check passed
nwparker added a commit that referenced this pull request Jul 6, 2026
* test(e2e): stabilize chronically-failing e2e suite

The scheduled E2E suite has been red for 3+ weeks with ~19 deterministic
failures across 9/10 shards. All are test-side issues (stale assertions,
CI-timing races, over-strict perf thresholds, and fixture gaps); no product
regressions were found. Two small app changes are test-support only:
a stable data-testid on the GitHub item detail surface, and honoring
prefers-reduced-motion in the sidebar reveal scroll (also an a11y win).

Fixes:
- github-cli-stall / pr-comments / onboarding: update stale assertions to
  current UI (inline GitHub detail, removed 'Open' badge #7338, error-state
  recovery #6473, Host-selector Add Project UI).
- source-control / workspace-space-git-status: poll worktrees.list past the
  5s detection-scan cache; match git-reported store paths (not realpath'd).
- terminal-column-desync / combined-diff: poll to convergence instead of a
  fixed wait; ignore virtualizer remeasurement in the scroll-jump metric.
- terminal-tui-wheel-reports/-drain: space notches past the burst window;
  reduce dense CDP stream + test.slow to fit the 120s budget.
- settings-display-name-ime: commit the IME composition (persist-on-commit
  since #6238). onboarding: broaden step predicate for auto-skipped steps.
- terminal-shortcuts: guard the split before Cmd/Ctrl+W and confirm the
  'Stop and Close' dialog. tab-close: drain late startup terminals.
- artificial-opencode: tolerate a single scheduler spike in the drift gate.
- worktree: resolve create base to the local HEAD branch; assert URL-resolve
  reuse via the lookup count.

Co-authored-by: Orca <help@stably.ai>

* test(e2e): fix second-round CI failures (races + throughput + reveal)

- wheel-drain: 120->60 events; each CDP round-trip is ~2.7s vs the heavy TUI, so 120 overran even the tripled test.slow() budget.
- artificial-opencode hidden-pressure: maxTimerDriftMs 150->250 to match the sibling terminal-load suite; a single tick spiked to 155ms under 8MB backpressure (median/worst latency remain the real guards).
- project-group-manual-sort: poll fetchRepos until all seeded repos register; the awaited fetch could drop its own result via the reposFetchGeneration guard (#7020).
- activity-agent badge: seed the blocked thread on the non-active split pane so useAutoAckViewedAgent can't auto-clear the unread badge before the assertion.
- terminal-panes Set Title: commit on Tab keydown directly instead of relying on browser focus-advance/blur (which doesn't fire in headless/no-focus envs; also hardens SSH).
- worktree reveal: verify an instant reveal scroll actually landed; when the virtualizer's cached scrollHeight lags a freshly-activated row, report not-revealed so the caller re-stages and retries (fixes a real last-row clip).

Co-authored-by: Orca <help@stably.ai>

* test(e2e): converge clipped-workspace reveal + relax hidden-restore drain ceiling

Co-authored-by: Orca <help@stably.ai>

* test(e2e): harden reveal + shared-page setup against CI-saturation flakes

- worktree-scroll reveal (:107): re-click reveal until strictly contained,
  recovering from virtualizer scrollHeight lag under CI CPU saturation.
- worktree-scroll filter test (:178): drop over-specified empty-DOM setup
  assertions (filter row-hiding is covered by visible-worktrees.test.ts);
  keeps the reveal-clears-filter contract.
- shared-page setup: make the initial all-repos worktree fetch best-effort so
  a hydration-time navigation ('context destroyed') doesn't fail setup; the
  authoritative seeded-worktree poll below remains the real wait.
- worktree-sidebar-reveal: keep reduced-motion 'smooth'->'auto' conversion
  (headless never ticks smooth scroll); revert unvalidatable clamp/verify.

Co-authored-by: Orca <help@stably.ai>

* test(e2e): drop synthetic pixel-precision reveal test; relax hidden-PTY worst-echo

- worktree-scroll: remove 'clipped in the production sidebar' test — it forced a
  ~44px synthetic viewport and asserted ±1px scroll precision the row virtualizer
  cannot guarantee under CI saturation (not a real-user scenario). Reveal-into-view
  stays covered by the 'outside the virtualized window' test.
- artificial-opencode hidden-pressure: relax worst single-key echo 300->3000ms as a
  catastrophic-hang detector (worst echo under 8MB synthetic backpressure is
  CI-environment-dominated, observed ~2s; median<75 + timer-drift<250 remain the
  responsiveness guards). Aligns with ssh-docker-relay-perf's 2s worst-key budget.

Co-authored-by: Orca <help@stably.ai>

* test(e2e): poll for visible Monaco diff line before clicking

clickVisibleDiffLine read Monaco's virtualized .view-line set in a single
evaluate right after a tab switch, but Monaco re-lays-out its diff lines
asynchronously. On a contended CI shard the visible set is briefly empty, so
the evaluate threw 'visible combined diff line not found' before Monaco
painted. Poll until a line is in the viewport instead of failing on first miss.

Co-authored-by: Orca <help@stably.ai>

* test(e2e): relax worst-key latency under injected multi-pane load

The same-workspace/cross-workspace/scale/main-pressure OpenCode load scenarios
share MAX_WORST_KEY_LATENCY_MS=300 for their worst single-key echo. On a
CPU-starved OSS shard that worst sample is environment-dominated (seen at
~3.1s) even while median typing stays <75ms — the median is the real
responsiveness guard. Add MAX_WORST_KEY_LATENCY_UNDER_LOAD_MS=3000 as a
catastrophic-hang detector for the load scenarios (keeping the no-load baseline
worst tight at 300), and widen the per-key marker wait so a slow echo is
measured and asserted rather than throwing a confusing 'did not contain'.
Mirrors the hidden-pressure scenario's relaxed worst budget.

Co-authored-by: Orca <help@stably.ai>

---------

Co-authored-by: Orca <help@stably.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Error invoking remote method 'gh:workItemDetails': Error: Access denied: unknown repository path

3 participants