Skip to content

fix: quick fixes 3 — terminal, chat, files, state improvements#146

Merged
arul28 merged 4 commits intomainfrom
quick-fixes-3
Apr 10, 2026
Merged

fix: quick fixes 3 — terminal, chat, files, state improvements#146
arul28 merged 4 commits intomainfrom
quick-fixes-3

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented Apr 10, 2026

Assorted fixes across main process, renderer components, and tests including terminal session handling, agent chat UX, file watcher, and store updates.

Summary by CodeRabbit

  • New Features

    • Per-project context management that keeps active projects responsive and evicts long-idle contexts.
    • Feedback form now accepts optional "reasoning effort" and falls back gracefully on generation errors.
  • Improvements

    • Parked terminals preserve state and replay buffered output on resume.
    • Single-focused terminal rendering and suspended session "resume" previews.
    • Chat history sizing/retention and locked-session refreshes.
    • Files page LRU session caching and workspace state preservation.
    • CI/Checks treat skipped/neutral as passing.
  • Settings

    • Terminal scrollback max reduced to 30,000.

Greptile Summary

This PR delivers a broad set of correctness and memory-management improvements: per-project context eviction in the main process, terminal output buffering while a view is unmounted, chat-event history caps with LRU pruning, files-page session LRU, and CI/Checks treating skipped/neutral as passing. All changes are well-scoped and the added tests cover the new behaviours.

The only items worth a second look are both P2: the settled guard in providerTaskRunner appears to be dead code as written (it is never set true in child-exit handlers), and the prService catch blocks use console.warn instead of the structured logger.

Confidence Score: 5/5

Safe to merge — all findings are P2 style/cleanup issues with no correctness impact.

No P0 or P1 issues found. Terminal buffering, context eviction, chat-event trimming, and CI-check reclassification are all correctly implemented and covered by new tests. The three P2 findings (dead settled guard, console.warn instead of logger, unbounded dirty-tab accumulation) do not affect runtime correctness.

apps/desktop/src/main/services/ai/providerTaskRunner.ts (settled guard), apps/desktop/src/main/services/prs/prService.ts (console.warn)

Important Files Changed

Filename Overview
apps/desktop/src/main/main.ts Adds per-project LRU context management with idle eviction and workload probing; TOCTOU is mitigated with re-checks before eviction and a serial promise queue.
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx Adds chat event trimming (1200 selected / 240 background), LRU session record pruning, and extracts refreshLockedSessionSummary to handle locked-mode refreshes independently.
apps/desktop/src/renderer/components/terminals/TerminalView.tsx Extracts scheduleFrameWriteFlush; PTY output is now buffered while the view is unmounted (refs === 0) and replayed on remount, preserving live terminal TUI state.
apps/desktop/src/renderer/components/terminals/WorkViewArea.tsx Adds suspended prop to SessionSurface; non-active grid tiles render a lightweight preview card instead of mounting a full terminal/chat pane.
apps/desktop/src/renderer/components/lanes/LaneTerminalsPanel.tsx Only the focused terminal session is rendered; switching away unmounts the view while the parked runtime keeps xterm.js state alive.
apps/desktop/src/renderer/components/files/FilesPage.tsx Introduces LRU eviction (max 8 scopes) for file-page session state; sessions with unsaved tabs are skipped during eviction to prevent data loss.
apps/desktop/src/main/services/prs/issueInventoryService.ts Replaces regex list with a KNOWN_BOT_ALIASES map and auto-detects unknown bots via [bot] suffix; exports detectSource for testing.
apps/desktop/src/main/services/ai/providerTaskRunner.ts Adds settled flag to the timeout handler; the guard may be incomplete if child-exit handlers don't also set settled = true.
apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx Adds 60s polling for action runs/activity/threads; neutral and skipped conclusions now count as passing; unified check list merges check-runs and action-runs APIs.
apps/desktop/src/renderer/components/terminals/useSessionDelta.ts Converts deltaCache from a plain Map to an LRU cache capped at 128 entries using touchDeltaCacheEntry.
apps/desktop/src/main/services/prs/prService.ts Adds console.warn logging to previously silent catch blocks; inconsistent with the structured logger used elsewhere in the file.
apps/desktop/src/renderer/state/appStore.ts Reduces terminal scrollback max from 100,000 to 30,000 lines; matching settings UI and new test.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[User switches active project] --> B[setActiveProject\nupdates projectLastActivatedAt]
    B --> C[scheduleProjectContextRebalance\nchains onto serial promise queue]
    C --> D[rebalanceProjectContexts]
    D --> E{For each non-active\nproject context}
    E --> F[hasActiveProjectWorkloads?]
    F -->|Yes| G[Retain — policy: active_workload]
    F -->|No| H[Add to idleRoots]
    H --> I[Sort by projectLastActivatedAt desc]
    I --> J{In top MAX_WARM_IDLE=1?}
    J -->|Yes| K[Retain — policy: warm_idle]
    J -->|No| L[Re-check workloads TOCTOU guard]
    L -->|Became active| M[Retain — policy: became_active_during_rebalance]
    L -->|Still idle| N[closeProjectContext\nevict + delete from map]

    subgraph TerminalParking
        T1[TerminalView unmounts] --> T2[refs dec to 0\nno RAF scheduled for flush]
        T2 --> T3[PTY data buffered in\nframeWriteChunks]
        T3 --> T4[TerminalView remounts]
        T4 --> T5[scheduleFrameWriteFlush\nreplays buffered output]
    end
Loading

Comments Outside Diff (1)

  1. apps/desktop/src/renderer/components/files/FilesPage.tsx, line 489-514 (link)

    P2 Session-switch effect captures stale state by design — worth a comment

    This useEffect deliberately omits workspaceId, openTabs, activeTabPath, etc. from its dependency array — it captures their values at the moment sessionKey changes, which is the intended "snapshot before leaving" pattern. Without an explanatory comment the exhaustive-deps lint rule will flag these as missing, and future readers will be tempted to add them (which would break the guard if (prevSessionKeyRef.current === sessionKey) return). A brief // eslint-disable-next-line react-hooks/exhaustive-deps – intentional snapshot of outgoing state would protect the intent.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/components/files/FilesPage.tsx
    Line: 489-514
    
    Comment:
    **Session-switch effect captures stale state by design — worth a comment**
    
    This `useEffect` deliberately omits `workspaceId`, `openTabs`, `activeTabPath`, etc. from its dependency array — it captures their values at the moment `sessionKey` changes, which is the intended "snapshot before leaving" pattern. Without an explanatory comment the exhaustive-deps lint rule will flag these as missing, and future readers will be tempted to add them (which would break the guard `if (prevSessionKeyRef.current === sessionKey) return`). A brief `// eslint-disable-next-line react-hooks/exhaustive-deps – intentional snapshot of outgoing state` would protect the intent.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

    Fix in Claude Code

Fix All in Claude Code

Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/main/services/ai/providerTaskRunner.ts
Line: 88-96

Comment:
**`settled` flag is only set inside the timeout — guard is unreachable**

`settled` is initialized to `false` and the only place it is set to `true` is inside the timeout callback itself. A `setTimeout` callback fires at most once, so `if (settled) return` can never be `true` when the handler executes — making the guard dead code.

The intended protection (preventing a double-kill/double-reject if the child already exited) requires setting `settled = true` in the child-process exit and error handlers as well:

```ts
child.on("exit", (code) => {
  if (settled) return;
  settled = true;
  clearTimeout(timeoutHandle);
  resolve(/**/);
});
child.on("error", (err) => {
  if (settled) return;
  settled = true;
  clearTimeout(timeoutHandle);
  reject(err);
});
```

As-is, `clearTimeout(timeoutHandle)` in those handlers is the only guard against the timeout firing after the process exits — but the `settled` variable as written adds no additional safety.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/main/services/prs/prService.ts
Line: 1515

Comment:
**`console.warn` bypasses the structured logger**

Three catch blocks now use `console.warn("[prService] …")` while every other error path in this file goes through the injected `logger` service. This skips Sentry capture, log-level filtering, and structured context fields. The same applies to the `computeStatus` and `getChecks` call sites at lines ~1578 and ~1630.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/components/files/FilesPage.tsx
Line: 131-154

Comment:
**LRU eviction silently stalls when every cached scope has unsaved tabs**

`setFilesPageSession` breaks out of the eviction loop as soon as `evictIndex < 0` (no clean candidate found). If a user works across nine or more lanes/projects without saving any file, every subsequent call silently keeps all scopes in memory with no cap, log, or user signal for this growth. Consider at minimum logging a warning so it is observable in production.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/main/services/prs/issueInventoryService.ts
Line: 63-84

Comment:
**`[bot]`-stripping regex matches every string — `?? name` fallback is dead code**

```ts
const botMatch = name.match(/^(.+?)(?:\[bot\])?$/i);
const baseName = (botMatch?.[1] ?? name).toLowerCase().trim();
```

`(?:\[bot\])?` is optional and `$` anchors the end, so the regex matches every non-empty string — `botMatch` is never `null`. The `?? name` fallback is unreachable. A clearer explicit approach:

```ts
const hasBotSuffix = /\[bot\]$/i.test(name);
const baseName = hasBotSuffix
  ? name.replace(/\[bot\]$/i, "").toLowerCase().trim()
  : name.toLowerCase().trim();
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (4): Last reviewed commit: "test: update detectSource tests for auto..." | Re-trigger Greptile

Assorted fixes across main process, renderer components, and tests including
terminal session handling, agent chat UX, file watcher, and store updates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel bot commented Apr 10, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ade Ready Ready Preview, Comment Apr 10, 2026 8:12pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

Adds per-project last-activated timestamps and a serialized project-context rebalance/eviction; exposes active embedded MCP connection counts on AppContext; expands context disposal; tightens chat history sizing/retention and session LRU caches; introduces session suspension UI for inactive tiles; reduces terminal scrollback caps and updates tests.

Changes

Cohort / File(s) Summary
Project context & IPC
apps/desktop/src/main/main.ts, apps/desktop/src/main/services/ipc/registerIpc.ts
Adds per-project last-activated timestamps, serialized rebalance that probes workloads and evicts dormant contexts, exposes getActiveMcpConnectionCount on AppContext, and updates the IPC AppContext type.
Test service
apps/desktop/src/main/services/tests/testService.ts
Adds hasActiveRuns(): boolean to the test service API.
Chat history & session retention
apps/desktop/src/renderer/components/chat/AgentChatPane.tsx, apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx
Introduces transcript byte/event caps, per-session retention trimming, locked-single-session refresh behavior, enforced event caps on queued flushes, and related test mock adjustments.
Files page session LRU & dirty buffers
apps/desktop/src/renderer/components/files/FilesPage.tsx, apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.ts
Implements LRU session caching (max 8), marks dirty tabs by content mismatch, evicts safe LRU sessions and clears dirty buffers for evicted workspace roots; adds clearDirtyBuffersForWorkspace.
Terminal runtime & UI changes
apps/desktop/src/renderer/components/lanes/LaneTerminalsPanel.tsx, apps/desktop/src/renderer/components/lanes/TilingLayout.tsx, apps/desktop/src/renderer/components/terminals/TerminalView.tsx, apps/desktop/src/renderer/components/terminals/TerminalView.test.tsx, apps/desktop/src/renderer/components/terminals/WorkViewArea.tsx
Renders a single focused TerminalView per panel, adds suspended/resume tile UI for inactive sessions, threads hideSessionTabs into chat tiles, buffers PTY events in tests, and updates frame-write flush logic to respect runtime refs.
Session delta cache
apps/desktop/src/renderer/components/terminals/useSessionDelta.ts
Adds bounded LRU eviction for session deltas (MAX_CACHED_SESSION_DELTAS = 128) and updates cache touch semantics so hits refresh recency.
Logs & terminal preferences
apps/desktop/src/renderer/components/project/ProjectHomePage.tsx, apps/desktop/src/renderer/state/appStore.ts, apps/desktop/src/renderer/state/appStore.test.ts
Introduces PROJECT_HOME_LOG_TAIL_MAX_BYTES with incremental append trimming; lowers terminal scrollback max from 100000→30000 and adds test validating clamping.
PRs / checks & feedback types
apps/desktop/src/main/services/prs/issueInventoryService.ts, apps/desktop/src/main/services/prs/prService.ts, apps/desktop/src/main/services/feedback/feedbackReporterService.ts, apps/desktop/src/shared/types/prs.ts, apps/desktop/src/shared/types/feedback.ts
Improves bot/source detection and typing, strengthens error-aware logging for fetch calls, adds fallback behavior for generation failures and a reasoningEffort field across feedback flow and types.
Misc renderer adjustments & tests
apps/desktop/src/renderer/components/prs/..., apps/desktop/src/renderer/components/files/..., other small edits
Minor prop threading, effect dependency fixes, CI/checks UI unification, terminal scrollback option update, and related test updates.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

desktop

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: quick fixes 3 — terminal, chat, files, state improvements' accurately summarizes the main changes across multiple subsystems in the PR, covering the primary technical areas affected.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch quick-fixes-3

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/components/terminals/TerminalView.tsx (1)

501-515: ⚠️ Potential issue | 🟡 Minor

Keep queued frame writes parked when the last view unmounts.

If a flush is already queued when runtime.refs drops to 0, this callback still joins frameWriteChunks and calls term.write() into the parked host. That reintroduces the exact race this change is trying to remove: some output is flushed while unmounted instead of staying buffered until remount. Bail out before draining the queue when there are no active refs; Line 952 already reschedules the flush after remount.

Proposed fix
 function scheduleFrameWriteFlush(runtime: CachedRuntime) {
   if (runtime.flushRafId != null) return;
   runtime.flushRafId = requestAnimationFrame(() => {
     runtime.flushRafId = null;
     if (runtime.disposed) return;
     if (runtime.frameWriteChunks.length === 0) return;
+    if (runtime.refs === 0) return;
     const merged = runtime.frameWriteChunks.join("");
     runtime.frameWriteChunks.length = 0;
     runtime.frameWriteBytes = 0;
     try {
       runtime.term.write(merged);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/terminals/TerminalView.tsx` around lines
501 - 515, The scheduled flush in scheduleFrameWriteFlush drains
frameWriteChunks and calls runtime.term.write even if there are no active views,
reintroducing the race; update the function to check runtime.refs and return
early (do not join or clear frameWriteChunks or call term.write) when
runtime.refs === 0 (and still respect runtime.disposed and empty-chunk checks),
so queued writes remain buffered until remount (the remount logic already
reschedules the flush).
🧹 Nitpick comments (2)
apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.ts (1)

9-18: Redundant normalization can be avoided.

clearWorkspaceEntries normalizes rootPath on line 10, but when called from replaceDirtyBuffersForWorkspace (line 28), the path is already normalized on line 27. Consider accepting an already-normalized path to avoid the redundant operation.

♻️ Optional: Skip normalization for internal calls
-function clearWorkspaceEntries(rootPath: string): void {
-  const rootNorm = path.normalize(rootPath);
+function clearWorkspaceEntries(rootNorm: string): void {
   const prefix = rootNorm.endsWith(path.sep) ? rootNorm : `${rootNorm}${path.sep}`;
   for (const key of [...dirtyByAbsPath.keys()]) {
     const kn = path.normalize(key);
     if (kn === rootNorm || kn.startsWith(prefix)) {
       dirtyByAbsPath.delete(key);
     }
   }
 }

Then update clearDirtyBuffersForWorkspace to normalize before calling:

 export function clearDirtyBuffersForWorkspace(rootPath: string): void {
-  clearWorkspaceEntries(rootPath);
+  clearWorkspaceEntries(path.normalize(rootPath));
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.ts` around lines 9 - 18,
clearWorkspaceEntries currently re-normalizes rootPath unnecessarily; change it
to assume the caller provides a normalized path (remove the path.normalize call
and use rootPath directly for rootNorm/prefix), and update callers that may pass
raw paths—specifically ensure replaceDirtyBuffersForWorkspace (or
clearDirtyBuffersForWorkspace) performs path.normalize(rootPath) before calling
clearWorkspaceEntries so only the caller normalizes once.
apps/desktop/src/renderer/components/files/FilesPage.tsx (1)

521-533: Consider debouncing high-frequency session snapshots.

This effect runs on every openTabs change, including content updates during typing (line 1245 updates tab content on editor change). With 8 cached scopes and simple operations, performance should be acceptable, but if profiling reveals issues, consider debouncing the snapshot:

♻️ Optional: Debounce session persistence for typing performance
// At module level
let snapshotTimer: number | null = null;

// In effect
useEffect(() => {
  if (snapshotTimer) window.clearTimeout(snapshotTimer);
  snapshotTimer = window.setTimeout(() => {
    setFilesPageSession(sessionKey, snapshotFilesPageSessionState({
      workspaceId,
      workspaceRootPath: activeWorkspace?.rootPath ?? null,
      // ... rest of properties
    }));
  }, 100);
  return () => {
    if (snapshotTimer) window.clearTimeout(snapshotTimer);
  };
}, [sessionKey, workspaceId, activeWorkspace?.rootPath, /* ... */]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/files/FilesPage.tsx` around lines 521 -
533, The effect that calls setFilesPageSession with
snapshotFilesPageSessionState runs on every openTabs change (including
high-frequency editor updates) and should be debounced: add a module-scoped
timer variable (e.g., snapshotTimer), replace the direct call inside the
useEffect with a window.setTimeout that delays invoking setFilesPageSession by
~100ms, clear any existing timeout before scheduling, and return a cleanup that
clears the timeout to avoid leaks; keep the same dependency array (sessionKey,
workspaceId, activeWorkspace?.rootPath, allowPrimaryEdit, selectedNodePath,
openTabs, activeTabPath, mode, searchQuery, editorTheme) so snapshots still run
after the delay.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/main.ts`:
- Around line 885-905: Before evicting each projectRoot from idleRoots,
revalidate the live state: reread currentActiveRoot and ctx (use
projectContexts.get(projectRoot)), skip if activeProjectRoot !==
currentActiveRoot or projectRoot === activeProjectRoot or
warmRoots.has(projectRoot), and explicitly check the context for any live work
(e.g., session, mission, sync peer, MCP connection on ctx — inspect whatever
fields/methods represent activeSession/activeMission/syncPeer/mcpConnection) and
only then log "project.context_evicted" and call
closeProjectContext(projectRoot); this prevents tearing down a context that
became active between the probe and eviction.
- Around line 3139-3148: Before disposing embeddingService, ensure the
embeddingWorkerService finishes queued work by awaiting its idle state: call
await embeddingWorkerService.waitForIdle() (or equivalent) after stopping health
checks and before calling ctx.embeddingService.dispose(), so any pending
embeddingService.embed() calls complete; keep the existing try/catch around
stopHealthCheck and dispose but insert the await
embeddingWorkerService.waitForIdle() between them, referencing
ctx.embeddingService and embeddingWorkerService.waitForIdle to locate where to
change.

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx`:
- Around line 505-507: trimChatEventHistory currently does a blind tail slice
which can drop unresolved control events that deriveRuntimeState() relies on
(e.g., status.started, pending input/steer markers) causing turnActive to be
recomputed incorrectly; update trimChatEventHistory to retain the newest
unresolved control events instead of naively taking the last N events: scan
backwards from the end of events to include any trailing control events needed
by deriveRuntimeState (status.started, queued/unresolved input markers, pending
steers) and expand the kept window to include their corresponding start markers
so the replayed log preserves in-progress turns and pending state; keep
references to trimChatEventHistory, deriveRuntimeState, AgentChatEventEnvelope,
and the status.started/pending markers when implementing this logic.
- Around line 1170-1190: The current refreshLockedSessionSummary reuses
initialSessionSummary every call when sessionId matches, causing stale fields;
add a consumed flag (e.g., seededInitialSummaryRef) and use
initialSessionSummary only once: if seededInitialSummaryRef.current is false and
initialSessionSummary?.sessionId === lockSessionId then use
initialSessionSummary and set seededInitialSummaryRef.current = true, otherwise
always call window.ade.agentChat.getSummary({ sessionId: lockSessionId }); keep
the rest of the state updates (setSessions, setTurnActiveBySession,
draftSelectionLockedRef.current, setSelectedSessionId) unchanged so subsequent
refreshes read fresh data.

In `@apps/desktop/src/renderer/components/terminals/WorkViewArea.tsx`:
- Around line 47-52: The fallback preview string for suspended tiles currently
always tells the user to "resume the live ... view" even for non-resumable or
ended sessions; update the logic that builds preview (where session.summary,
session.lastOutputPreview and isChatToolType(session.toolType) are used) to
first inspect session.status or a resumability check (e.g., a small helper like
canResume(session) or checking a ended/disposed flag) and choose messaging
accordingly—if the session is resumable keep the current "Select this tile to
resume..." copy, otherwise reuse the ended-state wording (or a concrete,
stateful message like "This session has ended and cannot be resumed") so
non-resumable tiles never show a prompt to resume. Ensure you update the preview
assignment in the same block that references secondarySessionLabel and
sessionStatusDot.

In `@apps/desktop/src/renderer/state/appStore.ts`:
- Line 247: The store clamps scrollback to a maximum of 30_000 (return
Math.max(..., Math.min(30_000, ...))) while the settings UI (GeneralSection
component) still offers 50_000; update either side so they match — either change
the settings option value(s) in GeneralSection to use 30_000 instead of 50_000,
or raise the clamp constant in the appStore scrollback calculation to
50_000—make the single source of truth consistent and keep the numeric literal
(30_000/50_000) in sync.

---

Outside diff comments:
In `@apps/desktop/src/renderer/components/terminals/TerminalView.tsx`:
- Around line 501-515: The scheduled flush in scheduleFrameWriteFlush drains
frameWriteChunks and calls runtime.term.write even if there are no active views,
reintroducing the race; update the function to check runtime.refs and return
early (do not join or clear frameWriteChunks or call term.write) when
runtime.refs === 0 (and still respect runtime.disposed and empty-chunk checks),
so queued writes remain buffered until remount (the remount logic already
reschedules the flush).

---

Nitpick comments:
In `@apps/desktop/src/renderer/components/files/FilesPage.tsx`:
- Around line 521-533: The effect that calls setFilesPageSession with
snapshotFilesPageSessionState runs on every openTabs change (including
high-frequency editor updates) and should be debounced: add a module-scoped
timer variable (e.g., snapshotTimer), replace the direct call inside the
useEffect with a window.setTimeout that delays invoking setFilesPageSession by
~100ms, clear any existing timeout before scheduling, and return a cleanup that
clears the timeout to avoid leaks; keep the same dependency array (sessionKey,
workspaceId, activeWorkspace?.rootPath, allowPrimaryEdit, selectedNodePath,
openTabs, activeTabPath, mode, searchQuery, editorTheme) so snapshots still run
after the delay.

In `@apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.ts`:
- Around line 9-18: clearWorkspaceEntries currently re-normalizes rootPath
unnecessarily; change it to assume the caller provides a normalized path (remove
the path.normalize call and use rootPath directly for rootNorm/prefix), and
update callers that may pass raw paths—specifically ensure
replaceDirtyBuffersForWorkspace (or clearDirtyBuffersForWorkspace) performs
path.normalize(rootPath) before calling clearWorkspaceEntries so only the caller
normalizes once.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: aaeb35ba-ab73-4372-9e34-6b54988c69a5

📥 Commits

Reviewing files that changed from the base of the PR and between 3d6c578 and 1af8fef.

📒 Files selected for processing (15)
  • apps/desktop/src/main/main.ts
  • apps/desktop/src/main/services/ipc/registerIpc.ts
  • apps/desktop/src/main/services/tests/testService.ts
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/desktop/src/renderer/components/files/FilesPage.tsx
  • apps/desktop/src/renderer/components/lanes/LaneTerminalsPanel.tsx
  • apps/desktop/src/renderer/components/lanes/TilingLayout.tsx
  • apps/desktop/src/renderer/components/project/ProjectHomePage.tsx
  • apps/desktop/src/renderer/components/terminals/TerminalView.test.tsx
  • apps/desktop/src/renderer/components/terminals/TerminalView.tsx
  • apps/desktop/src/renderer/components/terminals/WorkViewArea.tsx
  • apps/desktop/src/renderer/components/terminals/useSessionDelta.ts
  • apps/desktop/src/renderer/lib/dirtyWorkspaceBuffers.ts
  • apps/desktop/src/renderer/state/appStore.test.ts
  • apps/desktop/src/renderer/state/appStore.ts

Comment on lines +3139 to +3148
try {
ctx.embeddingService?.stopHealthCheck?.();
} catch {
// ignore
}
try {
await ctx.embeddingService?.dispose?.();
} catch {
// ignore
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

worker_file="$(fd 'embeddingWorkerService\.ts$' apps/desktop/src/main/services/memory | head -n1)"
main_file="apps/desktop/src/main/main.ts"

printf '== embedding worker API ==\n'
sed -n '1,260p' "$worker_file" | rg -n -C2 'createEmbeddingWorkerService|return \{|start\(|stop\(|dispose\('

printf '\n== current teardown sites in main.ts ==\n'
rg -n -C3 'embeddingWorkerService|embeddingService\?\.dispose|stopHealthCheck' "$main_file"

Repository: arul28/ADE

Length of output: 3471


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find and read the complete embeddingWorkerService.ts file
worker_file="$(fd 'embeddingWorkerService\.ts$' apps/desktop/src/main/services/memory | head -n1)"

echo "=== Complete embeddingWorkerService.ts ==="
wc -l "$worker_file"
cat -n "$worker_file"

Repository: arul28/ADE

Length of output: 11665


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Get the complete teardown section around lines 3139-3148 in main.ts
echo "=== Teardown section in main.ts (lines 3100-3200) ==="
sed -n '3100,3200p' apps/desktop/src/main/main.ts | cat -n

Repository: arul28/ADE

Length of output: 2654


Wait for embedding worker to complete processing before disposing embeddingService.

The worker queue is in-memory and can have pending embedding work. Currently, embeddingService is disposed without ensuring the worker has finished processing. If the worker calls embeddingService.embed() on queued items after the service is disposed, it will fail.

The embeddingWorkerService does not expose a stop() or dispose() method. Instead, call await embeddingWorkerService.waitForIdle() before disposing embeddingService to ensure all queued work completes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/main.ts` around lines 3139 - 3148, Before disposing
embeddingService, ensure the embeddingWorkerService finishes queued work by
awaiting its idle state: call await embeddingWorkerService.waitForIdle() (or
equivalent) after stopping health checks and before calling
ctx.embeddingService.dispose(), so any pending embeddingService.embed() calls
complete; keep the existing try/catch around stopHealthCheck and dispose but
insert the await embeddingWorkerService.waitForIdle() between them, referencing
ctx.embeddingService and embeddingWorkerService.waitForIdle to locate where to
change.

Comment on lines +505 to +507
function trimChatEventHistory(events: AgentChatEventEnvelope[], maxEvents: number): AgentChatEventEnvelope[] {
return events.length > maxEvents ? events.slice(-maxEvents) : events;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Do not prune away the state transition that deriveRuntimeState() depends on.

trimChatEventHistory() is a blind tail slice, but deriveRuntimeState() recomputes turnActive, pending inputs, and pending steers by replaying the retained log. A long active turn can exceed 240/1200 events before done arrives, which drops the last status.started or unresolved queued markers from the kept window and makes the pane think the session is idle mid-turn. Pruning needs to preserve the newest unresolved control events, or runtime state needs to be tracked incrementally before the trim.

Also applies to: 1391-1396, 1694-1699

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/chat/AgentChatPane.tsx` around lines 505
- 507, trimChatEventHistory currently does a blind tail slice which can drop
unresolved control events that deriveRuntimeState() relies on (e.g.,
status.started, pending input/steer markers) causing turnActive to be recomputed
incorrectly; update trimChatEventHistory to retain the newest unresolved control
events instead of naively taking the last N events: scan backwards from the end
of events to include any trailing control events needed by deriveRuntimeState
(status.started, queued/unresolved input markers, pending steers) and expand the
kept window to include their corresponding start markers so the replayed log
preserves in-progress turns and pending state; keep references to
trimChatEventHistory, deriveRuntimeState, AgentChatEventEnvelope, and the
status.started/pending markers when implementing this logic.

Comment on lines +47 to +52
if (suspended) {
const secondary = secondarySessionLabel(session);
const status = sessionStatusDot(session);
const preview = session.summary?.trim()
|| session.lastOutputPreview?.trim()
|| (isChatToolType(session.toolType) ? "Select this tile to resume the live chat view." : "Select this tile to resume the live terminal view.");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid “resume live … view” copy for non-resumable tiles.

This fallback runs for every suspended tile, including ended/disposed sessions, so it can tell the user to resume something that is no longer live. Derive the message from session.status/resume capability, or reuse the ended-state wording here.

As per coding guidelines, "Keep user-facing copy concrete and stateful: say what changed, what is blocked, and what the next action is".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/terminals/WorkViewArea.tsx` around lines
47 - 52, The fallback preview string for suspended tiles currently always tells
the user to "resume the live ... view" even for non-resumable or ended sessions;
update the logic that builds preview (where session.summary,
session.lastOutputPreview and isChatToolType(session.toolType) are used) to
first inspect session.status or a resumability check (e.g., a small helper like
canResume(session) or checking a ended/disposed flag) and choose messaging
accordingly—if the session is resumable keep the current "Select this tile to
resume..." copy, otherwise reuse the ended-state wording (or a concrete,
stateful message like "This session has ended and cannot be resumed") so
non-resumable tiles never show a prompt to resume. Ensure you update the preview
assignment in the same block that references secondarySessionLabel and
sessionStatusDot.

- Fix test-desktop shard 1 failures: tests now use status: "idle" for
  sessions where the Send button is expected (refreshLockedSessionSummary
  now correctly derives turnActive from session status)
- Fix stale locked session summary: use initialSessionSummary only for
  the first render seed, then always call getSummary for fresh data
- Add getSummary and editSteer mocks to test harness
- Remove stale expect(list).toHaveBeenCalled() assertions (locked mode
  no longer calls list)
- Sync scrollback options: GeneralSection now offers 30k max to match
  appStore clamp
- Add TOCTOU guard in rebalanceProjectContexts: re-check workloads
  immediately before eviction
- Add eslint-disable comment for intentional dep omission in FilesPage
  session-switch effect
- Guard terminal flush when unmounted: skip frame writes when refs === 0
  to keep data buffered until remount

https://claude.ai/code/session_01QxY6yqm5AEtA4E4JijBdH4
@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Apr 10, 2026

Tip:

Greploop — Automatically fix all review issues by running /greploops in Claude Code. It iterates: fix, push, re-review, repeat until 5/5 confidence.

Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal.

- Merge readiness + stats sidebar now use unified checks (check-runs + action-runs)
  instead of only check-runs API, fixing "No checks" / "0/0 passing" when action
  runs have data but check-runs returns empty
- Add 60s polling for actionRuns/activity/reviewThreads (previously loaded once)
- Auto-detect review bot sources from [bot] suffix instead of hardcoded patterns;
  Greptile, Seer, and any future GitHub App bots work without code changes
- Add error logging to fetchCheckRuns/fetchCombinedStatus (were silently swallowed)
- Fix summarizeChecks + convergence panel to count skipped/neutral as passing
- Widen IssueSource type to accept arbitrary bot source strings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/renderer/components/files/FilesPage.tsx`:
- Around line 147-151: The code currently reads evictedSession.workspaceRootPath
directly and can see null during interim snapshots; update the logic that checks
evictedSession?.workspaceRootPath so it uses a cached/previous value when
available (e.g., store lastKnownWorkspaceRoot for the session) until
activeWorkspace/lookup finishes, then call
hasFilesPageSessionForWorkspaceRoot(lastKnownWorkspaceRoot, evicted) and only
call clearDirtyBuffersForWorkspace(lastKnownWorkspaceRoot) if that lookup
confirms no active session; apply the same change to the other occurrences
referencing evictedSession.workspaceRootPath (the blocks that call
hasFilesPageSessionForWorkspaceRoot and clearDirtyBuffersForWorkspace) so we
never pass null and prematurely clear buffers.
- Around line 114-117: getFilesPageSession currently calls touchFilesPageSession
during render, mutating filesPageSessionLru; change it to perform a non-touching
read by returning filesPageSessionByScope.get(sessionKey) without calling
touchFilesPageSession, and add a useEffect in the FilesPage component that calls
touchFilesPageSession(sessionKey) after mount/commit and whenever sessionKey
changes (the existing touch in the sessionKey-change effect at line ~506 can
remain); ensure all other touches are moved to committed effects so no LRU
mutations happen during render.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0535d6db-8ac4-4647-9d87-56d88476e3e3

📥 Commits

Reviewing files that changed from the base of the PR and between 1af8fef and 7ffd9bc.

📒 Files selected for processing (6)
  • apps/desktop/src/main/main.ts
  • apps/desktop/src/renderer/components/chat/AgentChatPane.submit.test.tsx
  • apps/desktop/src/renderer/components/chat/AgentChatPane.tsx
  • apps/desktop/src/renderer/components/files/FilesPage.tsx
  • apps/desktop/src/renderer/components/settings/GeneralSection.tsx
  • apps/desktop/src/renderer/components/terminals/TerminalView.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/renderer/components/settings/GeneralSection.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/components/terminals/TerminalView.tsx

Comment on lines +114 to +117
function getFilesPageSession(sessionKey: string): FilesPageSessionState | undefined {
const session = filesPageSessionByScope.get(sessionKey);
if (session) touchFilesPageSession(sessionKey);
return session;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n -C2 'createRoot|StrictMode' apps/desktop/src

Repository: arul28/ADE

Length of output: 4531


🏁 Script executed:

cat -n apps/desktop/src/renderer/components/files/FilesPage.tsx | sed -n '110,125p'

Repository: arul28/ADE

Length of output: 790


🏁 Script executed:

cat -n apps/desktop/src/renderer/components/files/FilesPage.tsx | sed -n '415,430p'

Repository: arul28/ADE

Length of output: 1231


🏁 Script executed:

rg -n -B5 -A5 'filesPageSessionLru|filesPageSessionByScope' apps/desktop/src/renderer/components/files/FilesPage.tsx

Repository: arul28/ADE

Length of output: 2626


🏁 Script executed:

rg -n 'touchFilesPageSession' apps/desktop/src/renderer/components/files/FilesPage.tsx

Repository: arul28/ADE

Length of output: 211


🏁 Script executed:

cat -n apps/desktop/src/renderer/components/files/FilesPage.tsx | sed -n '415,500p'

Repository: arul28/ADE

Length of output: 5370


🏁 Script executed:

rg -n 'useEffect|useCallback' apps/desktop/src/renderer/components/files/FilesPage.tsx | head -20

Repository: arul28/ADE

Length of output: 1070


🏁 Script executed:

cat -n apps/desktop/src/renderer/components/files/FilesPage.tsx | sed -n '487,520p'

Repository: arul28/ADE

Length of output: 1710


🏁 Script executed:

rg -n 'getFilesPageSession|touchFilesPageSession' apps/desktop/src/renderer/components/files/FilesPage.tsx

Repository: arul28/ADE

Length of output: 420


🏁 Script executed:

cat -n apps/desktop/src/renderer/components/files/FilesPage.tsx | sed -n '482,540p'

Repository: arul28/ADE

Length of output: 2633


Use a non-touching read for the initial snapshot; move the LRU touch to an effect.

getFilesPageSession() mutates filesPageSessionLru at line 421 during render. In React 18, discarded or replayed renders can reorder that LRU, making later evictions inconsistent with actual committed scope switches. Extract a non-touching read (e.g., filesPageSessionByScope.get(sessionKey)) for the initial snapshot, and add a useEffect to touch the session after mount. The touch at line 506 (inside the sessionKey change effect) is already safe, but for consistency, move all LRU touches to committed effects only.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/files/FilesPage.tsx` around lines 114 -
117, getFilesPageSession currently calls touchFilesPageSession during render,
mutating filesPageSessionLru; change it to perform a non-touching read by
returning filesPageSessionByScope.get(sessionKey) without calling
touchFilesPageSession, and add a useEffect in the FilesPage component that calls
touchFilesPageSession(sessionKey) after mount/commit and whenever sessionKey
changes (the existing touch in the sessionKey-change effect at line ~506 can
remain); ensure all other touches are moved to committed effects so no LRU
mutations happen during render.

Comment on lines +147 to +151
if (
evictedSession?.workspaceRootPath
&& !hasFilesPageSessionForWorkspaceRoot(evictedSession.workspaceRootPath, evicted)
) {
clearDirtyBuffersForWorkspace(evictedSession.workspaceRootPath);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve the last known workspaceRootPath until the workspace lookup resolves.

Both snapshot paths can write workspaceRootPath: null even when this session already has a known root from a previous snapshot. If cache trimming runs in that window, hasFilesPageSessionForWorkspaceRoot() can miss the active session and clearDirtyBuffersForWorkspace() will clear that workspace’s dirty-buffer entries. Please keep the previously cached root until activeWorkspace is available.

Suggested fix
   useEffect(() => {
     if (prevSessionKeyRef.current === sessionKey) return;
     // Save current state under the old scope before switching
+    const previousWorkspaceRootPath =
+      activeWorkspace?.rootPath
+      ?? filesPageSessionByScope.get(prevSessionKeyRef.current)?.workspaceRootPath
+      ?? null;
     setFilesPageSession(prevSessionKeyRef.current, snapshotFilesPageSessionState({
       workspaceId,
-      workspaceRootPath: activeWorkspace?.rootPath ?? null,
+      workspaceRootPath: previousWorkspaceRootPath,
       allowPrimaryEdit,
       selectedNodePath,
       openTabs,
@@
   useEffect(() => {
+    const currentWorkspaceRootPath =
+      activeWorkspace?.rootPath
+      ?? filesPageSessionByScope.get(sessionKey)?.workspaceRootPath
+      ?? null;
     setFilesPageSession(sessionKey, snapshotFilesPageSessionState({
       workspaceId,
-      workspaceRootPath: activeWorkspace?.rootPath ?? null,
+      workspaceRootPath: currentWorkspaceRootPath,
       allowPrimaryEdit,
       selectedNodePath,
       openTabs,

Also applies to: 493-496, 523-526

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/files/FilesPage.tsx` around lines 147 -
151, The code currently reads evictedSession.workspaceRootPath directly and can
see null during interim snapshots; update the logic that checks
evictedSession?.workspaceRootPath so it uses a cached/previous value when
available (e.g., store lastKnownWorkspaceRoot for the session) until
activeWorkspace/lookup finishes, then call
hasFilesPageSessionForWorkspaceRoot(lastKnownWorkspaceRoot, evicted) and only
call clearDirtyBuffersForWorkspace(lastKnownWorkspaceRoot) if that lookup
confirms no active session; apply the same change to the other occurrences
referencing evictedSession.workspaceRootPath (the blocks that call
hasFilesPageSessionForWorkspaceRoot and clearDirtyBuffersForWorkspace) so we
never pass null and prematurely clear buffers.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.tsx (1)

1037-1041: ⚠️ Potential issue | 🟡 Minor

neutral/skipped are treated as passing in header, but not in row rendering/order.

With this change, the summary can show “all passing” while individual checks still render as non-passing (Eye) and stay in otherChecks. Align the per-check pass predicate everywhere.

💡 Suggested consistency fix
+function isPassingConclusion(conclusion: PrCheck["conclusion"] | null | undefined): boolean {
+  return conclusion === "success" || conclusion === "neutral" || conclusion === "skipped";
+}

 function CheckRow({ check }: { check: PrCheck }) {
-  const isPassing = check.conclusion === "success";
+  const isPassing = isPassingConclusion(check.conclusion);
   const isFailing = check.conclusion === "failure";
-const allChecksPassing = checks.length > 0 && checks.every((c) => c.conclusion === "success" || c.conclusion === "neutral" || c.conclusion === "skipped");
-const passingChecks = checks.filter((c) => c.conclusion === "success");
+const allChecksPassing = checks.length > 0 && checks.every((c) => isPassingConclusion(c.conclusion));
+const passingChecks = checks.filter((c) => isPassingConclusion(c.conclusion));
 const otherChecks = checks.filter(
-  (c) => c.conclusion !== "failure" && c.conclusion !== "success" && c.status !== "in_progress",
+  (c) => c.conclusion !== "failure" && !isPassingConclusion(c.conclusion) && c.status !== "in_progress",
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.tsx`
around lines 1037 - 1041, The header uses the predicate that treats "neutral"
and "skipped" as passing (allChecksPassing), but row/order code uses different
predicates; update the per-check predicates to match: change passingChecks to
include conclusions "success", "neutral", and "skipped" (use the same predicate
as allChecksPassing for individual pass detection) and change otherChecks to
exclude those three conclusions (and still exclude in_progress) so rows/order
and header are consistent; refer to the variables allChecksPassing,
passingChecks, otherChecks and the checks array when making the changes.
🧹 Nitpick comments (2)
apps/desktop/src/main/services/prs/prService.ts (1)

1518-1518: Prefer structured logger.warn over console.warn for fetch fallbacks.

These catches are a good resiliency improvement, but console.warn bypasses structured main-process logging. Use logger.warn with consistent event keys and context (prId, sha, call site), while keeping the same safe fallback returns.

♻️ Suggested logging pattern
- headSha ? fetchCheckRuns(repo, headSha).catch((err) => { console.warn("[prService] fetchCheckRuns failed in refreshOne:", err?.message ?? err); return []; }) : Promise.resolve([]),
+ headSha
+   ? fetchCheckRuns(repo, headSha).catch((error) => {
+       logger.warn("prs.fetch_check_runs_failed", {
+         scope: "refreshOne",
+         prId: row.id,
+         headSha,
+         error: getErrorMessage(error),
+       });
+       return [];
+     })
+   : Promise.resolve([]),

- fetchCombinedStatus(repo, headSha).catch((err) => { console.warn("[prService] fetchCombinedStatus failed in getChecks:", err?.message ?? err); return { state: "", statuses: [] }; }),
+ fetchCombinedStatus(repo, headSha).catch((error) => {
+   logger.warn("prs.fetch_combined_status_failed", {
+     scope: "getChecks",
+     prId,
+     headSha,
+     error: getErrorMessage(error),
+   });
+   return { state: "", statuses: [] };
+ }),

Also applies to: 1581-1581, 1633-1634

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/services/prs/prService.ts` at line 1518, Replace
console.warn fallbacks with the structured logger.warn and include contextual
fields: when calling fetchCheckRuns(headSha) in refreshOne (and the similar
catch sites for fetchCheckSuites / fetchCheckRuns elsewhere), catch the error
and call logger.warn with a descriptive message and an object containing prId
(or pr.id), sha/headSha, and a callSite or tag like
"prService:refreshOne:fetchCheckRuns", then return the same safe fallback ([]).
Update the other catch blocks noted (the other fetchCheckRuns/fetchCheckSuites
catches in the same file) to follow the same pattern so all fallback warnings
use structured logging and consistent event keys.
apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx (1)

1684-1684: Consider memoizing unified checks to avoid redundant computation.

buildUnifiedChecks(checks, actionRuns) is called here in the tab definition, and again inside OverviewTab (line 2262) and ChecksTab (line 3166). Since DETAIL_TABS is recalculated on every render, this means the same deduplication/sorting logic runs multiple times per render cycle.

For a small number of checks this is negligible, but consider lifting a single useMemo call to PrDetailPane and passing the result down to child components.

♻️ Suggested refactor
+ const unifiedChecks = React.useMemo(
+   () => buildUnifiedChecks(checks, actionRuns),
+   [checks, actionRuns],
+ );

  const DETAIL_TABS: Array<{ id: DetailTab; label: string; icon: React.ElementType; count?: number }> = [
    { id: "overview", label: "Overview", icon: Eye },
    { id: "convergence", label: "Path to Merge", icon: Sparkle, count: newIssueCount > 0 ? newIssueCount : undefined },
    { id: "files", label: "Files", icon: Code, count: files.length },
-   { id: "checks", label: "CI / Checks", icon: Play, count: buildUnifiedChecks(checks, actionRuns).length },
+   { id: "checks", label: "CI / Checks", icon: Play, count: unifiedChecks.length },
    { id: "activity", label: "Activity", icon: ClockCounterClockwise, count: activity.length > 0 ? activity.length : (comments.length + reviews.length) },
  ];

Then pass unifiedChecks to OverviewTab and ChecksTab instead of having each compute it independently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx` at line
1684, DETAIL_TABS currently calls buildUnifiedChecks multiple times per render
(also invoked inside OverviewTab and ChecksTab); lift this into PrDetailPane by
computing const unifiedChecks = useMemo(() => buildUnifiedChecks(checks,
actionRuns), [checks, actionRuns]) and pass unifiedChecks as a prop into
OverviewTab and ChecksTab (and replace other direct buildUnifiedChecks calls),
so the deduplication/sorting runs once per relevant dependency change rather
than on every render.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/desktop/src/main/services/prs/issueInventoryService.ts`:
- Around line 75-79: The current logic in issueInventoryService (when checking
the GitHub App bot via the name variable and returning baseName) can produce raw
bot slugs that collide with reserved literals; change the fallback to namespace
bot-derived sources (e.g. return a namespaced value like `bot:${baseName}`
instead of baseName) or persist the raw app name separately, and update the
shared IssueSource type to include a `bot:${string}` pattern and explicit
fallbacks (e.g. "bot" | "unknown" | specific providers) so downstream filtering
and the reopen exemption logic (referencing the IssueSource type) treat bot apps
distinctly from human/known sources.

---

Outside diff comments:
In `@apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.tsx`:
- Around line 1037-1041: The header uses the predicate that treats "neutral" and
"skipped" as passing (allChecksPassing), but row/order code uses different
predicates; update the per-check predicates to match: change passingChecks to
include conclusions "success", "neutral", and "skipped" (use the same predicate
as allChecksPassing for individual pass detection) and change otherChecks to
exclude those three conclusions (and still exclude in_progress) so rows/order
and header are consistent; refer to the variables allChecksPassing,
passingChecks, otherChecks and the checks array when making the changes.

---

Nitpick comments:
In `@apps/desktop/src/main/services/prs/prService.ts`:
- Line 1518: Replace console.warn fallbacks with the structured logger.warn and
include contextual fields: when calling fetchCheckRuns(headSha) in refreshOne
(and the similar catch sites for fetchCheckSuites / fetchCheckRuns elsewhere),
catch the error and call logger.warn with a descriptive message and an object
containing prId (or pr.id), sha/headSha, and a callSite or tag like
"prService:refreshOne:fetchCheckRuns", then return the same safe fallback ([]).
Update the other catch blocks noted (the other fetchCheckRuns/fetchCheckSuites
catches in the same file) to follow the same pattern so all fallback warnings
use structured logging and consistent event keys.

In `@apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx`:
- Line 1684: DETAIL_TABS currently calls buildUnifiedChecks multiple times per
render (also invoked inside OverviewTab and ChecksTab); lift this into
PrDetailPane by computing const unifiedChecks = useMemo(() =>
buildUnifiedChecks(checks, actionRuns), [checks, actionRuns]) and pass
unifiedChecks as a prop into OverviewTab and ChecksTab (and replace other direct
buildUnifiedChecks calls), so the deduplication/sorting runs once per relevant
dependency change rather than on every render.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 473fef6d-becd-411b-bf0e-c49819f44af3

📥 Commits

Reviewing files that changed from the base of the PR and between 7ffd9bc and 92f19d9.

📒 Files selected for processing (9)
  • apps/desktop/src/main/services/ai/providerTaskRunner.ts
  • apps/desktop/src/main/services/feedback/feedbackReporterService.ts
  • apps/desktop/src/main/services/prs/issueInventoryService.ts
  • apps/desktop/src/main/services/prs/prService.ts
  • apps/desktop/src/renderer/components/app/FeedbackReporterModal.tsx
  • apps/desktop/src/renderer/components/prs/detail/PrDetailPane.tsx
  • apps/desktop/src/renderer/components/prs/shared/PrConvergencePanel.tsx
  • apps/desktop/src/shared/types/feedback.ts
  • apps/desktop/src/shared/types/prs.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/shared/types/feedback.ts

Comment on lines +75 to 79
// Auto-detect any GitHub App bot (has [bot] suffix or "bot" in the name)
if (/\[bot\]/i.test(name)) {
// Return the base name as-is (e.g. "sonarqube-review" → "sonarqube-review")
return baseName;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don’t persist raw bot slugs as first-class sources.

Returning baseName verbatim can collide with reserved literals. A bot named human[bot] or ade[bot] would be stored as "human"/"ade", which changes downstream filtering and the Line 776 reopen exemption. Namespace the generic fallback or store the raw app name separately.

Suggested direction
-  if (/\[bot\]/i.test(name)) {
-    // Return the base name as-is (e.g. "sonarqube-review" → "sonarqube-review")
-    return baseName;
-  }
+  if (/\[bot\]/i.test(name)) {
+    return `bot:${baseName}`;
+  }

Then tighten the shared type to an explicit fallback such as:

export type IssueSource =
  | "coderabbit"
  | "codex"
  | "copilot"
  | "human"
  | "ade"
  | "greptile"
  | "seer"
  | "bot"
  | "unknown"
  | `bot:${string}`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/services/prs/issueInventoryService.ts` around lines 75
- 79, The current logic in issueInventoryService (when checking the GitHub App
bot via the name variable and returning baseName) can produce raw bot slugs that
collide with reserved literals; change the fallback to namespace bot-derived
sources (e.g. return a namespaced value like `bot:${baseName}` instead of
baseName) or persist the raw app name separately, and update the shared
IssueSource type to include a `bot:${string}` pattern and explicit fallbacks
(e.g. "bot" | "unknown" | specific providers) so downstream filtering and the
reopen exemption logic (referencing the IssueSource type) treat bot apps
distinctly from human/known sources.

- Fix stale test expecting greptile-review[bot] → "unknown" (now → "greptile")
- Add 6 focused tests: null/empty, known aliases, bot suffix auto-detect,
  unknown bots, "bot" keyword fallback, human authors

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@arul28 arul28 merged commit 5a77889 into main Apr 10, 2026
23 checks passed
@arul28 arul28 deleted the quick-fixes-3 branch April 14, 2026 07:52
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.

2 participants